@@ -159,12 +159,9 @@ export class MatTabGroup extends _MatTabGroupMixinBase implements AfterContentIn
159159 * a new selected tab should transition in (from the left or right).
160160 */
161161 ngAfterContentChecked ( ) {
162- // Clamp the next selected index to the bounds of 0 and the tabs length.
163- // Note the `|| 0`, which ensures that values like NaN can't get through
164- // and which would otherwise throw the component into an infinite loop
165- // (since Math.max(NaN, 0) === NaN).
166- let indexToSelect = this . _indexToSelect =
167- Math . min ( this . _tabs . length - 1 , Math . max ( this . _indexToSelect || 0 , 0 ) ) ;
162+ // Don't clamp the `indexToSelect` immediately in the setter because it can happen that
163+ // the amount of tabs changes before the actual change detection runs.
164+ const indexToSelect = this . _indexToSelect = this . _clampTabIndex ( this . _indexToSelect ) ;
168165
169166 // If there is a change in selected index, emit a change event. Should not trigger if
170167 // the selected index has not yet been initialized.
@@ -200,16 +197,21 @@ export class MatTabGroup extends _MatTabGroupMixinBase implements AfterContentIn
200197 // Subscribe to changes in the amount of tabs, in order to be
201198 // able to re-render the content as new tabs are added or removed.
202199 this . _tabsSubscription = this . _tabs . changes . subscribe ( ( ) => {
203- const tabs = this . _tabs . toArray ( ) ;
204-
205- // Maintain the previously-selected tab if a new tab is added or removed.
206- for ( let i = 0 ; i < tabs . length ; i ++ ) {
207- if ( tabs [ i ] . isActive ) {
208- // Assign both to the `_indexToSelect` and `_selectedIndex` so we don't fire a changed
209- // event, otherwise the consumer may end up in an infinite loop in some edge cases like
210- // adding a tab within the `selectedIndexChange` event.
211- this . _indexToSelect = this . _selectedIndex = i ;
212- break ;
200+ const indexToSelect = this . _clampTabIndex ( this . _indexToSelect ) ;
201+
202+ // Maintain the previously-selected tab if a new tab is added or removed and there is no
203+ // explicit change that selects a different tab.
204+ if ( indexToSelect === this . _selectedIndex ) {
205+ const tabs = this . _tabs . toArray ( ) ;
206+
207+ for ( let i = 0 ; i < tabs . length ; i ++ ) {
208+ if ( tabs [ i ] . isActive ) {
209+ // Assign both to the `_indexToSelect` and `_selectedIndex` so we don't fire a changed
210+ // event, otherwise the consumer may end up in an infinite loop in some edge cases like
211+ // adding a tab within the `selectedIndexChange` event.
212+ this . _indexToSelect = this . _selectedIndex = i ;
213+ break ;
214+ }
213215 }
214216 }
215217
@@ -261,6 +263,14 @@ export class MatTabGroup extends _MatTabGroupMixinBase implements AfterContentIn
261263 } ) ;
262264 }
263265
266+ /** Clamps the given index to the bounds of 0 and the tabs length. */
267+ private _clampTabIndex ( index : number | null ) : number {
268+ // Note the `|| 0`, which ensures that values like NaN can't get through
269+ // and which would otherwise throw the component into an infinite loop
270+ // (since Math.max(NaN, 0) === NaN).
271+ return Math . min ( this . _tabs . length - 1 , Math . max ( index || 0 , 0 ) ) ;
272+ }
273+
264274 /** Returns a unique id for each tab label element */
265275 _getTabLabelId ( i : number ) : string {
266276 return `mat-tab-label-${ this . _groupId } -${ i } ` ;
0 commit comments