Skip to content

refactor(tree): rework to account for ivy changes #15503

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 18, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 29 additions & 8 deletions src/cdk/tree/nested-node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

/**
Expand Down Expand Up @@ -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. */
Expand All @@ -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>,
Expand Down Expand Up @@ -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
Expand All @@ -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);
}
}
}
14 changes: 13 additions & 1 deletion src/cdk/tree/outlet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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) {}
}
25 changes: 18 additions & 7 deletions src/cdk/tree/padding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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>();

Expand Down Expand Up @@ -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() {
Expand All @@ -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;
}
}
}
19 changes: 9 additions & 10 deletions src/cdk/tree/toggle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand All @@ -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)
Expand Down
21 changes: 16 additions & 5 deletions src/cdk/tree/tree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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>();

Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -333,6 +343,7 @@ export class CdkTreeNode<T> implements FocusableOption, OnDestroy {
CdkTreeNode.mostRecentTreeNode = null;
}

this._dataChanges.complete();
this._destroyed.next();
this._destroyed.complete();
}
Expand Down
28 changes: 21 additions & 7 deletions src/lib/tree/node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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 =
Expand Down Expand Up @@ -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>,
Expand Down
8 changes: 6 additions & 2 deletions src/lib/tree/outlet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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) {}
}
3 changes: 0 additions & 3 deletions src/lib/tree/toggle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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> {
Expand Down
4 changes: 3 additions & 1 deletion src/lib/tree/tree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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> {
Expand Down
8 changes: 6 additions & 2 deletions tools/public_api_guard/cdk/tree.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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>;
Expand All @@ -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> {
Expand All @@ -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;
}

Expand Down
3 changes: 2 additions & 1 deletion tools/public_api_guard/lib/tree.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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> {
Expand Down