@@ -23,6 +23,7 @@ import {
23
23
import {
24
24
AfterContentChecked ,
25
25
AfterContentInit ,
26
+ AfterViewInit ,
26
27
ChangeDetectionStrategy ,
27
28
ChangeDetectorRef ,
28
29
Component ,
@@ -111,7 +112,13 @@ type RenderingData<T> =
111
112
imports : [ CdkTreeNodeOutlet ] ,
112
113
} )
113
114
export class CdkTree < T , K = T >
114
- implements AfterContentChecked , AfterContentInit , CollectionViewer , OnDestroy , OnInit
115
+ implements
116
+ AfterContentChecked ,
117
+ AfterContentInit ,
118
+ AfterViewInit ,
119
+ CollectionViewer ,
120
+ OnDestroy ,
121
+ OnInit
115
122
{
116
123
/** Subject that emits when the component has been destroyed. */
117
124
private readonly _onDestroy = new Subject < void > ( ) ;
@@ -248,6 +255,7 @@ export class CdkTree<T, K = T>
248
255
249
256
/** The key manager for this tree. Handles focus and activation based on user keyboard input. */
250
257
_keyManager : TreeKeyManagerStrategy < CdkTreeNode < T , K > > ;
258
+ private _viewInit = false ;
251
259
252
260
constructor (
253
261
private _differs : IterableDiffers ,
@@ -280,14 +288,20 @@ export class CdkTree<T, K = T>
280
288
this . _dataSubscription = null ;
281
289
}
282
290
283
- this . _keyManager . destroy ( ) ;
291
+ // In certain tests, the tree might be destroyed before this is initialized
292
+ // in `ngAfterContentInit`.
293
+ this . _keyManager ?. destroy ( ) ;
284
294
}
285
295
286
296
ngOnInit ( ) {
287
297
this . _checkTreeControlUsage ( ) ;
288
298
this . _initializeDataDiffer ( ) ;
289
299
}
290
300
301
+ ngAfterViewInit ( ) {
302
+ this . _viewInit = true ;
303
+ }
304
+
291
305
private _updateDefaultNodeDefinition ( ) {
292
306
const defaultNodeDefs = this . _nodeDefs . filter ( def => ! def . when ) ;
293
307
if ( defaultNodeDefs . length > 1 && ( typeof ngDevMode === 'undefined' || ngDevMode ) ) {
@@ -449,7 +463,9 @@ export class CdkTree<T, K = T>
449
463
}
450
464
451
465
private _initializeDataDiffer ( ) {
452
- this . _dataDiffer = this . _differs . find ( [ ] ) . create ( this . trackBy ) ;
466
+ // Provide a default trackBy based on `_getExpansionKey` if one isn't provided.
467
+ const trackBy = this . trackBy ?? ( ( _index : number , item : T ) => this . _getExpansionKey ( item ) ) ;
468
+ this . _dataDiffer = this . _differs . find ( [ ] ) . create ( trackBy ) ;
453
469
}
454
470
455
471
private _checkTreeControlUsage ( ) {
@@ -484,11 +500,19 @@ export class CdkTree<T, K = T>
484
500
parentData ?: T ,
485
501
) {
486
502
const changes = dataDiffer . diff ( data ) ;
487
- if ( ! changes ) {
503
+
504
+ // Some tree consumers expect change detection to propagate to nodes
505
+ // even when the array itself hasn't changed; we explicitly detect changes
506
+ // anyways in order for nodes to update their data.
507
+ //
508
+ // However, if change detection is called while the component's view is
509
+ // still initing, then the order of child views initing will be incorrect;
510
+ // to prevent this, we only exit early if the view hasn't initialized yet.
511
+ if ( ! changes && ! this . _viewInit ) {
488
512
return ;
489
513
}
490
514
491
- changes . forEachOperation (
515
+ changes ? .forEachOperation (
492
516
(
493
517
item : IterableChangeRecord < T > ,
494
518
adjustedPreviousIndex : number | null ,
@@ -498,12 +522,6 @@ export class CdkTree<T, K = T>
498
522
this . insertNode ( data [ currentIndex ! ] , currentIndex ! , viewContainer , parentData ) ;
499
523
} else if ( currentIndex == null ) {
500
524
viewContainer . remove ( adjustedPreviousIndex ! ) ;
501
- const set = this . _getAriaSet ( item . item ) ;
502
- const key = this . _getExpansionKey ( item . item ) ;
503
- set . splice (
504
- set . findIndex ( groupItem => this . _getExpansionKey ( groupItem ) === key ) ,
505
- 1 ,
506
- ) ;
507
525
} else {
508
526
const view = viewContainer . get ( adjustedPreviousIndex ! ) ;
509
527
viewContainer . move ( view ! , currentIndex ) ;
@@ -682,12 +700,12 @@ export class CdkTree<T, K = T>
682
700
683
701
/** Level accessor, used for compatibility between the old Tree and new Tree */
684
702
_getLevelAccessor ( ) {
685
- return this . treeControl ?. getLevel ?? this . levelAccessor ;
703
+ return this . treeControl ?. getLevel ?. bind ( this . treeControl ) ?? this . levelAccessor ;
686
704
}
687
705
688
706
/** Children accessor, used for compatibility between the old Tree and new Tree */
689
707
_getChildrenAccessor ( ) {
690
- return this . treeControl ?. getChildren ?? this . childrenAccessor ;
708
+ return this . treeControl ?. getChildren ?. bind ( this . treeControl ) ?? this . childrenAccessor ;
691
709
}
692
710
693
711
/**
@@ -1094,7 +1112,7 @@ export class CdkTree<T, K = T>
1094
1112
'[attr.aria-setsize]' : '_getSetSize()' ,
1095
1113
'[tabindex]' : '_tabindex' ,
1096
1114
'role' : 'treeitem' ,
1097
- '(click)' : '_focusItem ()' ,
1115
+ '(click)' : '_setActiveItem ()' ,
1098
1116
'(focus)' : '_focusItem()' ,
1099
1117
} ,
1100
1118
standalone : true ,
@@ -1172,6 +1190,13 @@ export class CdkTreeNode<T, K = T> implements OnDestroy, OnInit, TreeKeyManagerI
1172
1190
readonly _dataChanges = new Subject < void > ( ) ;
1173
1191
1174
1192
private _inputIsExpandable : boolean = false ;
1193
+ /**
1194
+ * Flag used to determine whether or not we should be focusing the actual element based on
1195
+ * some user interaction (click or focus). On click, we don't forcibly focus the element
1196
+ * since the click could trigger some other component that wants to grab its own focus
1197
+ * (e.g. menu, dialog).
1198
+ */
1199
+ private _shouldFocus = true ;
1175
1200
private _parentNodeAriaLevel : number ;
1176
1201
1177
1202
/** The tree node's data. */
@@ -1273,7 +1298,9 @@ export class CdkTreeNode<T, K = T> implements OnDestroy, OnInit, TreeKeyManagerI
1273
1298
/** Focuses this data node. Implemented for TreeKeyManagerItem. */
1274
1299
focus ( ) : void {
1275
1300
this . _tabindex = 0 ;
1276
- this . _elementRef . nativeElement . focus ( ) ;
1301
+ if ( this . _shouldFocus ) {
1302
+ this . _elementRef . nativeElement . focus ( ) ;
1303
+ }
1277
1304
1278
1305
this . _changeDetectorRef . markForCheck ( ) ;
1279
1306
}
@@ -1314,6 +1341,15 @@ export class CdkTreeNode<T, K = T> implements OnDestroy, OnInit, TreeKeyManagerI
1314
1341
this . _tree . _keyManager . focusItem ( this ) ;
1315
1342
}
1316
1343
1344
+ _setActiveItem ( ) {
1345
+ if ( this . isDisabled ) {
1346
+ return ;
1347
+ }
1348
+ this . _shouldFocus = false ;
1349
+ this . _tree . _keyManager . focusItem ( this ) ;
1350
+ this . _shouldFocus = true ;
1351
+ }
1352
+
1317
1353
_emitExpansionState ( expanded : boolean ) {
1318
1354
this . expandedChange . emit ( expanded ) ;
1319
1355
}
0 commit comments