@@ -22,6 +22,8 @@ import {
2222 ViewEncapsulation ,
2323 inject ,
2424 HostAttributeToken ,
25+ signal ,
26+ computed ,
2527} from '@angular/core' ;
2628import {
2729 MAT_RIPPLE_GLOBAL_OPTIONS ,
@@ -39,7 +41,7 @@ import {BehaviorSubject, Subject} from 'rxjs';
3941import { startWith , takeUntil } from 'rxjs/operators' ;
4042import { ENTER , SPACE } from '@angular/cdk/keycodes' ;
4143import { MAT_TABS_CONFIG , MatTabsConfig } from '../tab-config' ;
42- import { MatPaginatedTabHeader } from '../paginated-tab-header' ;
44+ import { MatPaginatedTabHeader , MatPaginatedTabHeaderItem } from '../paginated-tab-header' ;
4345import { CdkObserveContent } from '@angular/cdk/observers' ;
4446import { _CdkPrivateStyleLoader } from '@angular/cdk/private' ;
4547
@@ -70,6 +72,8 @@ import {_CdkPrivateStyleLoader} from '@angular/cdk/private';
7072 imports : [ MatRipple , CdkObserveContent ] ,
7173} )
7274export class MatTabNav extends MatPaginatedTabHeader implements AfterContentInit , AfterViewInit {
75+ _focusedItem = signal < MatPaginatedTabHeaderItem | null > ( null ) ;
76+
7377 /** Whether the ink bar should fit its width to the size of the tab label content. */
7478 @Input ( { transform : booleanAttribute } )
7579 get fitInkBarToContent ( ) : boolean {
@@ -183,6 +187,11 @@ export class MatTabNav extends MatPaginatedTabHeader implements AfterContentInit
183187 . subscribe ( ( ) => this . updateActiveLink ( ) ) ;
184188
185189 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+ ) ;
186195 }
187196
188197 override ngAfterViewInit ( ) {
@@ -203,12 +212,13 @@ export class MatTabNav extends MatPaginatedTabHeader implements AfterContentInit
203212 for ( let i = 0 ; i < items . length ; i ++ ) {
204213 if ( items [ i ] . active ) {
205214 this . selectedIndex = i ;
206- this . _changeDetectorRef . markForCheck ( ) ;
207-
208215 if ( this . tabPanel ) {
209216 this . tabPanel . _activeTabId = items [ i ] . id ;
210217 }
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 ( ) ;
212222 return ;
213223 }
214224 }
@@ -242,7 +252,7 @@ export class MatTabNav extends MatPaginatedTabHeader implements AfterContentInit
242252 '[attr.aria-disabled]' : 'disabled' ,
243253 '[attr.aria-selected]' : '_getAriaSelected()' ,
244254 '[attr.id]' : 'id' ,
245- '[attr.tabIndex]' : '_getTabIndex ()' ,
255+ '[attr.tabIndex]' : '_tabIndex ()' ,
246256 '[attr.role]' : '_getRole()' ,
247257 '[class.mat-mdc-tab-disabled]' : 'disabled' ,
248258 '[class.mdc-tab--active]' : 'active' ,
@@ -264,6 +274,10 @@ export class MatTabLink
264274 /** Whether the tab link is active or not. */
265275 protected _isActive : boolean = false ;
266276
277+ protected _tabIndex = computed ( ( ) =>
278+ this . _tabNavBar . _focusedItem ( ) === this ? this . tabIndex : - 1 ,
279+ ) ;
280+
267281 /** Whether the link is active. */
268282 @Input ( { transform : booleanAttribute } )
269283 get active ( ) : boolean {
@@ -397,10 +411,6 @@ export class MatTabLink
397411 _getRole ( ) : string | null {
398412 return this . _tabNavBar . tabPanel ? 'tab' : this . elementRef . nativeElement . getAttribute ( 'role' ) ;
399413 }
400-
401- _getTabIndex ( ) : number {
402- return this . _tabNavBar . _hasFocus ( this ) ? this . tabIndex : - 1 ;
403- }
404414}
405415
406416/**
0 commit comments