mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
refactor(tree): rework to account for ivy changes (#15503)
Reworks the `MatTree` and `CdkTree` components to account for some of the changes in Ivy. This includes: * Changing the padding directive to account for a different time at which static inputs are initialized. * Changing the toggle directive to account for inheritance of host listeners working differently. * Switching to `Default` change detection, because the way embedded views are checked is slightly different. * Using `descendants: true` to find the nested node outlet in order to handle a slight difference in how indirect content children are matched.
This commit is contained in:
committed by
mmalerba
parent
c8ddaa80e7
commit
1421184ed7
@@ -10,16 +10,16 @@ import {
|
||||
ContentChildren,
|
||||
Directive,
|
||||
ElementRef,
|
||||
IterableDiffers,
|
||||
IterableDiffer,
|
||||
IterableDiffers,
|
||||
OnDestroy,
|
||||
QueryList,
|
||||
} from '@angular/core';
|
||||
import {Observable} from 'rxjs';
|
||||
import {takeUntil} from 'rxjs/operators';
|
||||
|
||||
import {CDK_TREE_NODE_OUTLET_NODE, CdkTreeNodeOutlet} from './outlet';
|
||||
import {CdkTree, CdkTreeNode} from './tree';
|
||||
import {CdkTreeNodeOutlet} from './outlet';
|
||||
import {getTreeControlFunctionsMissingError} from './tree-errors';
|
||||
|
||||
/**
|
||||
@@ -51,7 +51,10 @@ import {getTreeControlFunctionsMissingError} from './tree-errors';
|
||||
'[attr.role]': 'role',
|
||||
'class': 'cdk-tree-node cdk-nested-tree-node',
|
||||
},
|
||||
providers: [{provide: CdkTreeNode, useExisting: CdkNestedTreeNode}]
|
||||
providers: [
|
||||
{provide: CdkTreeNode, useExisting: CdkNestedTreeNode},
|
||||
{provide: CDK_TREE_NODE_OUTLET_NODE, useExisting: CdkNestedTreeNode}
|
||||
]
|
||||
})
|
||||
export class CdkNestedTreeNode<T> extends CdkTreeNode<T> implements AfterContentInit, OnDestroy {
|
||||
/** Differ used to find the changes in the data provided by the data source. */
|
||||
@@ -61,7 +64,12 @@ export class CdkNestedTreeNode<T> extends CdkTreeNode<T> implements AfterContent
|
||||
protected _children: T[];
|
||||
|
||||
/** The children node placeholder. */
|
||||
@ContentChildren(CdkTreeNodeOutlet) nodeOutlet: QueryList<CdkTreeNodeOutlet>;
|
||||
@ContentChildren(CdkTreeNodeOutlet, {
|
||||
// We need to use `descendants: true`, because Ivy will no longer match
|
||||
// indirect descendants if it's left as false.
|
||||
descendants: true
|
||||
})
|
||||
nodeOutlet: QueryList<CdkTreeNodeOutlet>;
|
||||
|
||||
constructor(protected _elementRef: ElementRef<HTMLElement>,
|
||||
protected _tree: CdkTree<T>,
|
||||
@@ -92,11 +100,12 @@ export class CdkNestedTreeNode<T> extends CdkTreeNode<T> implements AfterContent
|
||||
|
||||
/** Add children dataNodes to the NodeOutlet */
|
||||
protected updateChildrenNodes(children?: T[]): void {
|
||||
const outlet = this._getNodeOutlet();
|
||||
if (children) {
|
||||
this._children = children;
|
||||
}
|
||||
if (this.nodeOutlet.length && this._children) {
|
||||
const viewContainer = this.nodeOutlet.first.viewContainer;
|
||||
if (outlet && this._children) {
|
||||
const viewContainer = outlet.viewContainer;
|
||||
this._tree.renderNodeChanges(this._children, this._dataDiffer, viewContainer, this._data);
|
||||
} else {
|
||||
// Reset the data differ if there's no children nodes displayed
|
||||
@@ -106,9 +115,21 @@ export class CdkNestedTreeNode<T> extends CdkTreeNode<T> implements AfterContent
|
||||
|
||||
/** Clear the children dataNodes. */
|
||||
protected _clear(): void {
|
||||
if (this.nodeOutlet && this.nodeOutlet.first) {
|
||||
this.nodeOutlet.first.viewContainer.clear();
|
||||
const outlet = this._getNodeOutlet();
|
||||
if (outlet) {
|
||||
outlet.viewContainer.clear();
|
||||
this._dataDiffer.diff([]);
|
||||
}
|
||||
}
|
||||
|
||||
/** Gets the outlet for the current node. */
|
||||
private _getNodeOutlet() {
|
||||
const outlets = this.nodeOutlet;
|
||||
|
||||
if (outlets) {
|
||||
// Note that since we use `descendants: true` on the query, we have to ensure
|
||||
// that we don't pick up the outlet of a child node by accident.
|
||||
return outlets.find(outlet => !outlet._node || outlet._node === this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+13
-1
@@ -7,9 +7,19 @@
|
||||
*/
|
||||
import {
|
||||
Directive,
|
||||
Inject,
|
||||
InjectionToken,
|
||||
Optional,
|
||||
ViewContainerRef,
|
||||
} from '@angular/core';
|
||||
|
||||
/**
|
||||
* Injection token used to provide a `CdkTreeNode` to its outlet.
|
||||
* Used primarily to avoid circular imports.
|
||||
* @docs-private
|
||||
*/
|
||||
export const CDK_TREE_NODE_OUTLET_NODE = new InjectionToken<{}>('CDK_TREE_NODE_OUTLET_NODE');
|
||||
|
||||
/**
|
||||
* Outlet for nested CdkNode. Put `[cdkTreeNodeOutlet]` on a tag to place children dataNodes
|
||||
* inside the outlet.
|
||||
@@ -18,5 +28,7 @@ import {
|
||||
selector: '[cdkTreeNodeOutlet]'
|
||||
})
|
||||
export class CdkTreeNodeOutlet {
|
||||
constructor(public viewContainer: ViewContainerRef) {}
|
||||
constructor(
|
||||
public viewContainer: ViewContainerRef,
|
||||
@Inject(CDK_TREE_NODE_OUTLET_NODE) @Optional() public _node?: any) {}
|
||||
}
|
||||
|
||||
+18
-7
@@ -24,6 +24,9 @@ const cssUnitPattern = /([A-Za-z%]+)$/;
|
||||
selector: '[cdkTreeNodePadding]',
|
||||
})
|
||||
export class CdkTreeNodePadding<T> implements OnDestroy {
|
||||
/** Current padding value applied to the element. Used to avoid unnecessarily hitting the DOM. */
|
||||
private _currentPadding: string|null;
|
||||
|
||||
/** Subject that emits when the component has been destroyed. */
|
||||
private _destroyed = new Subject<void>();
|
||||
|
||||
@@ -68,8 +71,13 @@ export class CdkTreeNodePadding<T> implements OnDestroy {
|
||||
@Optional() private _dir: Directionality) {
|
||||
this._setPadding();
|
||||
if (_dir) {
|
||||
_dir.change.pipe(takeUntil(this._destroyed)).subscribe(() => this._setPadding());
|
||||
_dir.change.pipe(takeUntil(this._destroyed)).subscribe(() => this._setPadding(true));
|
||||
}
|
||||
|
||||
// In Ivy the indentation binding might be set before the tree node's data has been added,
|
||||
// which means that we'll miss the first render. We have to subscribe to changes in the
|
||||
// data to ensure that everything is up to date.
|
||||
_treeNode._dataChanges.subscribe(() => this._setPadding());
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
@@ -86,13 +94,16 @@ export class CdkTreeNodePadding<T> implements OnDestroy {
|
||||
return level ? `${level * this._indent}${this.indentUnits}` : null;
|
||||
}
|
||||
|
||||
_setPadding() {
|
||||
const element = this._element.nativeElement;
|
||||
_setPadding(forceChange = false) {
|
||||
const padding = this._paddingIndent();
|
||||
const paddingProp = this._dir && this._dir.value === 'rtl' ? 'paddingRight' : 'paddingLeft';
|
||||
const resetProp = paddingProp === 'paddingLeft' ? 'paddingRight' : 'paddingLeft';
|
||||
|
||||
this._renderer.setStyle(element, paddingProp, padding);
|
||||
this._renderer.setStyle(element, resetProp, '');
|
||||
if (padding !== this._currentPadding || forceChange) {
|
||||
const element = this._element.nativeElement;
|
||||
const paddingProp = this._dir && this._dir.value === 'rtl' ? 'paddingRight' : 'paddingLeft';
|
||||
const resetProp = paddingProp === 'paddingLeft' ? 'paddingRight' : 'paddingLeft';
|
||||
this._renderer.setStyle(element, paddingProp, padding);
|
||||
this._renderer.setStyle(element, resetProp, null);
|
||||
this._currentPadding = padding;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+9
-10
@@ -7,21 +7,14 @@
|
||||
*/
|
||||
|
||||
import {coerceBooleanProperty} from '@angular/cdk/coercion';
|
||||
import {
|
||||
Directive,
|
||||
Input,
|
||||
} from '@angular/core';
|
||||
import {Directive, HostListener, Input} from '@angular/core';
|
||||
|
||||
import {CdkTree, CdkTreeNode} from './tree';
|
||||
|
||||
/**
|
||||
* Node toggle to expand/collapse the node.
|
||||
*/
|
||||
@Directive({
|
||||
selector: '[cdkTreeNodeToggle]',
|
||||
host: {
|
||||
'(click)': '_toggle($event)',
|
||||
}
|
||||
})
|
||||
@Directive({selector: '[cdkTreeNodeToggle]'})
|
||||
export class CdkTreeNodeToggle<T> {
|
||||
/** Whether expand/collapse the node recursively. */
|
||||
@Input('cdkTreeNodeToggleRecursive')
|
||||
@@ -32,6 +25,12 @@ export class CdkTreeNodeToggle<T> {
|
||||
constructor(protected _tree: CdkTree<T>,
|
||||
protected _treeNode: CdkTreeNode<T>) {}
|
||||
|
||||
// We have to use a `HostListener` here in order to support both Ivy and ViewEngine.
|
||||
// In Ivy the `host` bindings will be merged when this class is extended, whereas in
|
||||
// ViewEngine they're overwritten.
|
||||
// TODO(crisbeto): we move this back into `host` once Ivy is turned on by default.
|
||||
// tslint:disable-next-line:no-host-decorator-in-concrete
|
||||
@HostListener('click', ['$event'])
|
||||
_toggle(event: Event): void {
|
||||
this.recursive
|
||||
? this._tree.treeControl.toggleDescendants(this._treeNode.data)
|
||||
|
||||
+16
-5
@@ -54,10 +54,14 @@ import {
|
||||
'role': 'tree',
|
||||
},
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
|
||||
// The "OnPush" status for the `CdkTree` component is effectively a noop, so we are removing it.
|
||||
// The view for `CdkTree` consists entirely of templates declared in other views. As they are
|
||||
// declared elsewhere, they are checked when their declaration points are checked.
|
||||
// tslint:disable-next-line:validate-decorators
|
||||
changeDetection: ChangeDetectionStrategy.Default
|
||||
})
|
||||
export class CdkTree<T>
|
||||
implements AfterContentChecked, CollectionViewer, OnDestroy, OnInit {
|
||||
export class CdkTree<T> implements AfterContentChecked, CollectionViewer, OnDestroy, OnInit {
|
||||
/** Subject that emits when the component has been destroyed. */
|
||||
private _onDestroy = new Subject<void>();
|
||||
|
||||
@@ -299,11 +303,17 @@ export class CdkTreeNode<T> implements FocusableOption, OnDestroy {
|
||||
/** Subject that emits when the component has been destroyed. */
|
||||
protected _destroyed = new Subject<void>();
|
||||
|
||||
/** Emits when the node's data has changed. */
|
||||
_dataChanges = new Subject<void>();
|
||||
|
||||
/** The tree node's data. */
|
||||
get data(): T { return this._data; }
|
||||
set data(value: T) {
|
||||
this._data = value;
|
||||
this._setRoleFromData();
|
||||
if (value !== this._data) {
|
||||
this._data = value;
|
||||
this._setRoleFromData();
|
||||
this._dataChanges.next();
|
||||
}
|
||||
}
|
||||
protected _data: T;
|
||||
|
||||
@@ -333,6 +343,7 @@ export class CdkTreeNode<T> implements FocusableOption, OnDestroy {
|
||||
CdkTreeNode.mostRecentTreeNode = null;
|
||||
}
|
||||
|
||||
this._dataChanges.complete();
|
||||
this._destroyed.next();
|
||||
this._destroyed.complete();
|
||||
}
|
||||
|
||||
+21
-7
@@ -6,7 +6,13 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {CdkNestedTreeNode, CdkTree, CdkTreeNode, CdkTreeNodeDef} from '@angular/cdk/tree';
|
||||
import {
|
||||
CDK_TREE_NODE_OUTLET_NODE,
|
||||
CdkNestedTreeNode,
|
||||
CdkTree,
|
||||
CdkTreeNode,
|
||||
CdkTreeNodeDef,
|
||||
} from '@angular/cdk/tree';
|
||||
import {
|
||||
AfterContentInit,
|
||||
Attribute,
|
||||
@@ -19,12 +25,14 @@ import {
|
||||
QueryList,
|
||||
} from '@angular/core';
|
||||
import {
|
||||
CanDisable, CanDisableCtor,
|
||||
CanDisable,
|
||||
CanDisableCtor,
|
||||
HasTabIndex,
|
||||
HasTabIndexCtor,
|
||||
mixinDisabled,
|
||||
mixinTabIndex,
|
||||
} from '@angular/material/core';
|
||||
|
||||
import {MatTreeNodeOutlet} from './outlet';
|
||||
|
||||
export const _MatTreeNodeMixinBase: HasTabIndexCtor & CanDisableCtor & typeof CdkTreeNode =
|
||||
@@ -90,15 +98,21 @@ export class MatTreeNodeDef<T> extends CdkTreeNodeDef<T> {
|
||||
inputs: ['disabled', 'tabIndex'],
|
||||
providers: [
|
||||
{provide: CdkNestedTreeNode, useExisting: MatNestedTreeNode},
|
||||
{provide: CdkTreeNode, useExisting: MatNestedTreeNode}
|
||||
{provide: CdkTreeNode, useExisting: MatNestedTreeNode},
|
||||
{provide: CDK_TREE_NODE_OUTLET_NODE, useExisting: MatNestedTreeNode}
|
||||
]
|
||||
})
|
||||
export class MatNestedTreeNode<T> extends _MatNestedTreeNodeMixinBase<T>
|
||||
implements AfterContentInit, CanDisable, HasTabIndex, OnDestroy {
|
||||
|
||||
export class MatNestedTreeNode<T> extends _MatNestedTreeNodeMixinBase<T> implements
|
||||
AfterContentInit, CanDisable, HasTabIndex, OnDestroy {
|
||||
@Input('matNestedTreeNode') node: T;
|
||||
|
||||
@ContentChildren(MatTreeNodeOutlet) nodeOutlet: QueryList<MatTreeNodeOutlet>;
|
||||
/** The children node placeholder. */
|
||||
@ContentChildren(MatTreeNodeOutlet, {
|
||||
// We need to use `descendants: true`, because Ivy will no longer match
|
||||
// indirect descendants if it's left as false.
|
||||
descendants: true
|
||||
})
|
||||
nodeOutlet: QueryList<MatTreeNodeOutlet>;
|
||||
|
||||
constructor(protected _elementRef: ElementRef<HTMLElement>,
|
||||
protected _tree: CdkTree<T>,
|
||||
|
||||
@@ -5,9 +5,11 @@
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import {CdkTreeNodeOutlet} from '@angular/cdk/tree';
|
||||
import {CDK_TREE_NODE_OUTLET_NODE, CdkTreeNodeOutlet} from '@angular/cdk/tree';
|
||||
import {
|
||||
Directive,
|
||||
Inject,
|
||||
Optional,
|
||||
ViewContainerRef,
|
||||
} from '@angular/core';
|
||||
|
||||
@@ -19,5 +21,7 @@ import {
|
||||
selector: '[matTreeNodeOutlet]'
|
||||
})
|
||||
export class MatTreeNodeOutlet implements CdkTreeNodeOutlet {
|
||||
constructor(public viewContainer: ViewContainerRef) {}
|
||||
constructor(
|
||||
public viewContainer: ViewContainerRef,
|
||||
@Inject(CDK_TREE_NODE_OUTLET_NODE) @Optional() public _node?: any) {}
|
||||
}
|
||||
|
||||
@@ -14,9 +14,6 @@ import {Directive, Input} from '@angular/core';
|
||||
*/
|
||||
@Directive({
|
||||
selector: '[matTreeNodeToggle]',
|
||||
host: {
|
||||
'(click)': '_toggle($event)',
|
||||
},
|
||||
providers: [{provide: CdkTreeNodeToggle, useExisting: MatTreeNodeToggle}]
|
||||
})
|
||||
export class MatTreeNodeToggle<T> extends CdkTreeNodeToggle<T> {
|
||||
|
||||
@@ -24,7 +24,9 @@ import {MatTreeNodeOutlet} from './outlet';
|
||||
},
|
||||
styleUrls: ['tree.css'],
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
// See note on CdkTree for explanation on why this uses the default change detection strategy.
|
||||
// tslint:disable-next-line:validate-decorators
|
||||
changeDetection: ChangeDetectionStrategy.Default,
|
||||
providers: [{provide: CdkTree, useExisting: MatTree}]
|
||||
})
|
||||
export class MatTree<T> extends CdkTree<T> {
|
||||
|
||||
Vendored
+6
-2
@@ -16,6 +16,8 @@ export declare abstract class BaseTreeControl<T> implements TreeControl<T> {
|
||||
toggleDescendants(dataNode: T): void;
|
||||
}
|
||||
|
||||
export declare const CDK_TREE_NODE_OUTLET_NODE: InjectionToken<{}>;
|
||||
|
||||
export declare class CdkNestedTreeNode<T> extends CdkTreeNode<T> implements AfterContentInit, OnDestroy {
|
||||
protected _children: T[];
|
||||
protected _differs: IterableDiffers;
|
||||
@@ -53,6 +55,7 @@ export declare class CdkTreeModule {
|
||||
|
||||
export declare class CdkTreeNode<T> implements FocusableOption, OnDestroy {
|
||||
protected _data: T;
|
||||
_dataChanges: Subject<void>;
|
||||
protected _destroyed: Subject<void>;
|
||||
protected _elementRef: ElementRef<HTMLElement>;
|
||||
protected _tree: CdkTree<T>;
|
||||
@@ -75,8 +78,9 @@ export declare class CdkTreeNodeDef<T> {
|
||||
}
|
||||
|
||||
export declare class CdkTreeNodeOutlet {
|
||||
_node?: any;
|
||||
viewContainer: ViewContainerRef;
|
||||
constructor(viewContainer: ViewContainerRef);
|
||||
constructor(viewContainer: ViewContainerRef, _node?: any);
|
||||
}
|
||||
|
||||
export declare class CdkTreeNodeOutletContext<T> {
|
||||
@@ -95,7 +99,7 @@ export declare class CdkTreeNodePadding<T> implements OnDestroy {
|
||||
level: number;
|
||||
constructor(_treeNode: CdkTreeNode<T>, _tree: CdkTree<T>, _renderer: Renderer2, _element: ElementRef<HTMLElement>, _dir: Directionality);
|
||||
_paddingIndent(): string | null;
|
||||
_setPadding(): void;
|
||||
_setPadding(forceChange?: boolean): void;
|
||||
ngOnDestroy(): void;
|
||||
}
|
||||
|
||||
|
||||
Vendored
+2
-1
@@ -61,8 +61,9 @@ export declare class MatTreeNodeDef<T> extends CdkTreeNodeDef<T> {
|
||||
}
|
||||
|
||||
export declare class MatTreeNodeOutlet implements CdkTreeNodeOutlet {
|
||||
_node?: any;
|
||||
viewContainer: ViewContainerRef;
|
||||
constructor(viewContainer: ViewContainerRef);
|
||||
constructor(viewContainer: ViewContainerRef, _node?: any);
|
||||
}
|
||||
|
||||
export declare class MatTreeNodePadding<T> extends CdkTreeNodePadding<T> {
|
||||
|
||||
Reference in New Issue
Block a user