@@ -22,6 +22,8 @@ import {
22
22
ViewEncapsulation ,
23
23
inject ,
24
24
HostAttributeToken ,
25
+ signal ,
26
+ computed ,
25
27
} from '@angular/core' ;
26
28
import {
27
29
MAT_RIPPLE_GLOBAL_OPTIONS ,
@@ -39,7 +41,7 @@ import {BehaviorSubject, Subject} from 'rxjs';
39
41
import { startWith , takeUntil } from 'rxjs/operators' ;
40
42
import { ENTER , SPACE } from '@angular/cdk/keycodes' ;
41
43
import { MAT_TABS_CONFIG , MatTabsConfig } from '../tab-config' ;
42
- import { MatPaginatedTabHeader } from '../paginated-tab-header' ;
44
+ import { MatPaginatedTabHeader , MatPaginatedTabHeaderItem } from '../paginated-tab-header' ;
43
45
import { CdkObserveContent } from '@angular/cdk/observers' ;
44
46
import { _CdkPrivateStyleLoader } from '@angular/cdk/private' ;
45
47
@@ -70,6 +72,8 @@ import {_CdkPrivateStyleLoader} from '@angular/cdk/private';
70
72
imports : [ MatRipple , CdkObserveContent ] ,
71
73
} )
72
74
export class MatTabNav extends MatPaginatedTabHeader implements AfterContentInit , AfterViewInit {
75
+ _focusedItem = signal < MatPaginatedTabHeaderItem | null > ( null ) ;
76
+
73
77
/** Whether the ink bar should fit its width to the size of the tab label content. */
74
78
@Input ( { transform : booleanAttribute } )
75
79
get fitInkBarToContent ( ) : boolean {
@@ -183,6 +187,11 @@ export class MatTabNav extends MatPaginatedTabHeader implements AfterContentInit
183
187
. subscribe ( ( ) => this . updateActiveLink ( ) ) ;
184
188
185
189
super . ngAfterContentInit ( ) ;
190
+
191
+ // Turn the `change` stream into a signal to try and avoid "changed after checked" errors.
192
+ this . _keyManager ! . change . pipe ( startWith ( null ) , takeUntil ( this . _destroyed ) ) . subscribe ( ( ) =>
193
+ this . _focusedItem . set ( this . _keyManager ?. activeItem || null ) ,
194
+ ) ;
186
195
}
187
196
188
197
override ngAfterViewInit ( ) {
@@ -203,12 +212,13 @@ export class MatTabNav extends MatPaginatedTabHeader implements AfterContentInit
203
212
for ( let i = 0 ; i < items . length ; i ++ ) {
204
213
if ( items [ i ] . active ) {
205
214
this . selectedIndex = i ;
206
- this . _changeDetectorRef . markForCheck ( ) ;
207
-
208
215
if ( this . tabPanel ) {
209
216
this . tabPanel . _activeTabId = items [ i ] . id ;
210
217
}
211
-
218
+ // Updating the `selectedIndex` won't trigger the `change` event on
219
+ // the key manager so we need to set the signal from here.
220
+ this . _focusedItem . set ( items [ i ] ) ;
221
+ this . _changeDetectorRef . markForCheck ( ) ;
212
222
return ;
213
223
}
214
224
}
@@ -242,7 +252,7 @@ export class MatTabNav extends MatPaginatedTabHeader implements AfterContentInit
242
252
'[attr.aria-disabled]' : 'disabled' ,
243
253
'[attr.aria-selected]' : '_getAriaSelected()' ,
244
254
'[attr.id]' : 'id' ,
245
- '[attr.tabIndex]' : '_getTabIndex ()' ,
255
+ '[attr.tabIndex]' : '_tabIndex ()' ,
246
256
'[attr.role]' : '_getRole()' ,
247
257
'[class.mat-mdc-tab-disabled]' : 'disabled' ,
248
258
'[class.mdc-tab--active]' : 'active' ,
@@ -264,6 +274,10 @@ export class MatTabLink
264
274
/** Whether the tab link is active or not. */
265
275
protected _isActive : boolean = false ;
266
276
277
+ protected _tabIndex = computed ( ( ) =>
278
+ this . _tabNavBar . _focusedItem ( ) === this ? this . tabIndex : - 1 ,
279
+ ) ;
280
+
267
281
/** Whether the link is active. */
268
282
@Input ( { transform : booleanAttribute } )
269
283
get active ( ) : boolean {
@@ -397,10 +411,6 @@ export class MatTabLink
397
411
_getRole ( ) : string | null {
398
412
return this . _tabNavBar . tabPanel ? 'tab' : this . elementRef . nativeElement . getAttribute ( 'role' ) ;
399
413
}
400
-
401
- _getTabIndex ( ) : number {
402
- return this . _tabNavBar . _hasFocus ( this ) ? this . tabIndex : - 1 ;
403
- }
404
414
}
405
415
406
416
/**
0 commit comments