-
Notifications
You must be signed in to change notification settings - Fork 6.8k
feat(tree): Add Cdk Tree component #7984
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
Conversation
src/cdk/collections/tree-adapter.ts
Outdated
@@ -0,0 +1,18 @@ | |||
/** | |||
* @license | |||
* Copyright Google Inc. All Rights Reserved. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The license changed recently; it's Google LLC
now
src/cdk/collections/tree-adapter.ts
Outdated
|
||
expandFlattenedNodes(nodes: T[], expansionModel: SelectionModel<T>): T[]; | ||
|
||
nodeDecedents(node: T, nodes: T[], onlyExpandable: boolean); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add JsDoc description for these methods?
src/cdk/tree/nested-node.ts
Outdated
|
||
|
||
/** | ||
* Nested node, add children to `cdkNodePlaceholder` in template. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Clarify that this is a child of <cdk-tree>
and what is means to be a "nested node"? I'm also not sure exactly what "add children to `cdkNodePlaceholder` in template."
means
src/cdk/tree/nested-node.ts
Outdated
protected _childrenSubscription: Subscription; | ||
|
||
/** Subscribes to node placeholder changes. */ | ||
protected _placeholderSubscription: Subscription; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One pattern we've been using more lately is to create a subject like _destroyed
and then use takeUntil(this._destroyed)
to avoid needing to manually keep track of the subscription and unsubscribe
src/cdk/tree/nested-node.ts
Outdated
@ContentChildren(CdkNodePlaceholder) nodePlaceholder: QueryList<CdkNodePlaceholder>; | ||
|
||
/** The Children nodes data. */ | ||
protected _children: T[]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not very clear on what this represents exactly
src/cdk/tree/tree-control.ts
Outdated
|
||
/** Expands a subtree rooted at given `node` recursively. */ | ||
expandDecedents(node: T) { | ||
let decedents = this.getDecedents(node); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Prefer const
(here and elsewhere)
src/cdk/tree/tree-control.ts
Outdated
*/ | ||
expandAll() { | ||
this.expansionModel.clear(); | ||
this.nodes.forEach((node) => this._expandDecedents(node)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: don't need parens for a single argument with inferred type (here and elsewhere)
src/cdk/tree/tree-control.ts
Outdated
if (children) { | ||
children.forEach((child) => { | ||
this.collapseDecedents(child) | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't need to use braces here
children.forEach(child => this.collapseDecedents(child));
(similar elsewhere)
src/cdk/tree/tree-data.ts
Outdated
|
||
/** Interface for data in flat tree. Tree users should implement this interface to use CdkTree. */ | ||
export interface FlatNode { | ||
level: number; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we do a getLevel
method instead of a property? It might be annoying if someone has a level
property in their data that means something else. (Same for expandable
-> isExpandable
).
I also wonder if there's a way to could express this in the template (similar to when
) instead of requiring an interface, but I forget if we already explored that option
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We didn't explore that option.
I can try to express it in the template, but TreeControl
is also using that to expand/collapse subtrees/the whole tree.
If we want to express this in the template, we can also pass that info to TreeControl
, or put getLevel
getChildren
and isExpandable
functions in TreeControl
and let other components use functions in TreeControl
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now moved getLevel
isExpandable
and getChildren
to TreeControl
. Removed NestedNode
and FlatNode
types and users can use any type of data.
src/cdk/tree/tree.spec.ts
Outdated
SimpleCdkTreeApp, | ||
// DynamicDataSourceCdkTreeApp, | ||
// NodeContextCdkTreeApp, | ||
// WhenNodeCdkTreeApp |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add TODO:
in front of each of these? That will change them from commented-out-code to just comments
Comments addressed. Please take a look. Thanks! |
cd453e3
to
0267610
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some quick comments before I I have to run
src/cdk/collections/tree-adapter.ts
Outdated
*/ | ||
export interface TreeAdapter<T> { | ||
/** Flatten structured data to an array of data. */ | ||
flattenNodes( structuredData: any[]): T[]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Extra space
src/cdk/collections/tree-adapter.ts
Outdated
* Put node descendents of node in array. | ||
* If `onlyExpandable` is true, then only process expandable descendents. | ||
*/ | ||
nodeDecedents(node: T, nodes: T[], onlyExpandable: boolean); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"Decedents" -> "Descendants"
(here and elsewhere)
src/cdk/collections/tree-adapter.ts
Outdated
@@ -0,0 +1,28 @@ | |||
/** | |||
* @license | |||
* Copyright Google LLC. All Rights Reserved. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Extra .
after LLC
nodes: T[]; | ||
|
||
/** Expansion info: the changes */ | ||
expandChange = new BehaviorSubject<T[]>([]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe expansionChange
? @andrewseguin thoughts?
} | ||
|
||
/** Whether a given node is expanded or not. Returns true if the node is expanded. */ | ||
expanded(node: T) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
isExpanded
?
src/cdk/tree/node.ts
Outdated
/** The tree node data */ | ||
@Input('cdkTreeNode') | ||
set data(v: T) { | ||
this._data = v; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Break the content of this setter into a separate function?
src/cdk/tree/node.ts
Outdated
} | ||
} | ||
get data(): T { return this._data; } | ||
_data: T; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Make this private?
src/cdk/tree/node.ts
Outdated
_data: T; | ||
|
||
/** The offset top of the element. Used by CdkTree to decide the order of the nodes. [Focus] */ | ||
private get offsetTop() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This this is private
, make it a method?
src/cdk/tree/padding.ts
Outdated
return this._dir && this._dir.value === 'rtl' ? this._paddingIndent() : null; | ||
} | ||
|
||
constructor(private _treeNode: CdkTreeNode<T>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Move constructor above methods and below properties
src/cdk/tree/padding.ts
Outdated
|
||
constructor(private _treeNode: CdkTreeNode<T>, | ||
private _tree: CdkTree<T>, | ||
@Optional() private _dir: Directionality) {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably want to have dir.change
as well
src/cdk/tree/trigger.ts
Outdated
}) | ||
export class CdkNodeTrigger<T> { | ||
/** Whether expand/collapse the node recursively. */ | ||
@Input('cdkNodeTriggerRecursive') recursive: boolean = true; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
coerceBooleanProperty
?
src/cdk/tree/trigger.ts
Outdated
* Node trigger to expand/collapse the node. | ||
*/ | ||
@Directive({ | ||
selector: '[cdkNodeTrigger]', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cdkNodeTrigger
doesn't seem quite specific enough. Maybe cdkTreeNodeToggle
?
import {CdkTree} from './tree'; | ||
|
||
|
||
describe('CdkTree', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In a follow-up PR, it would be good to explore creating a syntax-sugar way of testing tree content. The table, for example, has this:
expectTableToMatchContent(tableElement, [
['Column A', 'Column B', 'Column C'],
['a_1', 'b_1', 'c_1'],
['a_2', 'b_2', 'c_2'],
['a_3', 'b_3', 'c_3'],
]);
For the tree, we'd need to capture level and expansion state
src/cdk/tree/tree-module.ts
Outdated
import {CdkNodeDef, CdkTreeNode} from './node'; | ||
import {CdkNestedTreeNode} from './nested-node'; | ||
|
||
const EXPORTED_DECLARATIONS = [ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe just call this TREE_DIRECTIVES
?
bd7b0c0
to
2df887c
Compare
expansionModel: SelectionModel<T> = new SelectionModel<T>(true); | ||
|
||
/** Toggles one single node. Expands a collapsed node or collapse an expanded node. */ | ||
toggle(node: T) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All of these public methods need return types. Same goes for the other files.
/** Expands one single node. */ | ||
expand(node: T) { | ||
this.expansionModel.select(node); | ||
this.expansionChange.next(this.expansionModel.selected); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You should be able to replace the expansionChange
event with the onChange
from the SelectionModel
.
*/ | ||
expandAll() { | ||
this.expansionModel.clear(); | ||
this.nodes.forEach((node) => this._expandDescendants(node)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: you don't need the parentheses around node
.
children.forEach((child: T) => this._getDescendants(descendants, child)); | ||
} | ||
}); | ||
subscription.unsubscribe(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rather than unsubscribing manually here, you could use the first
operators instead.
if (!this._tree.treeControl.getChildren) { | ||
throw getTreeControlFunctionsMissingError(); | ||
} | ||
takeUntil.call(this._tree.treeControl.getChildren(this._data), this._destroyed) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since this subscription is set up by the setter, shouldn't it unsubscribe from any previous subscriptions? Otherwise you could end up with multiple subscriptions if the data
is set multiple times.
src/cdk/tree/node.ts
Outdated
} | ||
|
||
/** The offset top of the element. Used by CdkTree to decide the order of the nodes. [Focus] */ | ||
private getOffsetTop() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see this method being used anywhere, is it still relevant?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed.
src/cdk/tree/tree.ts
Outdated
|
||
ngAfterViewInit() { | ||
// For key traversal in correct order | ||
this.items.changes.subscribe(items => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems like you're never unsubscribing from this one and I'm not sure whether Angular will complete the stream on destroy.
src/cdk/tree/tree.ts
Outdated
this.orderedNodes.reset(nodes); | ||
|
||
const activeItem = this._keyManager ? this._keyManager.activeItem : null; | ||
this._keyManager = new FocusKeyManager(this.orderedNodes); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since you're mutating the same QueryList
, you shouldn't need to re-create the key manager every time the items change. You may have to reset the active index though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reset it because the order of the items in the QueryList
is wrong. For example:
- node 1
- node 2
After expanded node 1, it becomes - node 1
---- node 3
---- node 4
-node 2
in item list, and UP_ARROW and DOWN_ARROW will jump from one node to another.
Resetting the nodes make it become - node 1
---- node 2
---- node 3 - node 4
in key manager.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed key manager related part.
src/cdk/tree/tree.ts
Outdated
if (event.keyCode == UP_ARROW) { | ||
this._keyManager.setPreviousItemActive(); | ||
} else if (event.keyCode == DOWN_ARROW) { | ||
this._keyManager.setNextItemActive(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The UP_ARROW and DOWN_ARROW behavior should be handled by the key manager already.
8918a41
to
437566f
Compare
Please take a look. Thanks for review! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, a few nits left.
this._addChildrenNodes(); | ||
}); | ||
takeUntil.call(this.nodeOutlet.changes, this._destroyed) | ||
.subscribe((_) => this._addChildrenNodes()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The _
param here is unnecessary. We may have a lint rule for unused parameters that would complain about this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like this is still here
src/cdk/tree/node.ts
Outdated
*/ | ||
when: (nodeData: T, index: number) => boolean; | ||
|
||
/** @docs-private */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You should be able to set this on the directive doc, rather than the constructor.
src/cdk/tree/padding.ts
Outdated
} | ||
|
||
/** The left padding indent value for the tree node. */ | ||
paddingIndentLeft(): string|null { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rather than having two properties and bindings for left and right, you could set the padding-left
/padding-right
instead. E.g.:
setPadding() {
const padding = this._paddingIndent();
const paddingProp = this._dir && this._dir.value === 'rtl' ? 'padding-right' : 'padding-left';
this._renderer.setStyle(this._element.nativeElement, paddingProp, padding);
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated padding.ts
. Now set the properties and listen to dir.change
.
So there's good news and bad news. 👍 The good news is that everyone that needs to sign a CLA (the pull request submitter and all commit authors) have done so. Everything is all good there. 😕 The bad news is that it appears that one or more commits were authored by someone other than the pull request submitter. We need to confirm that they're okay with their commits being contributed to this project. Please have them confirm that here in the pull request. Note to project maintainer: This is a terminal state, meaning the |
Comments addressed. Please take another look. Thanks! |
* Add Cdk Tree component * Put tree control in control folder. Removed NestedNode FlatNode types and add getLevel getChildren isExpandable in TreeControl. * Use static varaible to pass node data * fix lint * another round * update documents according to comments
* Add Cdk Tree component * Put tree control in control folder. Removed NestedNode FlatNode types and add getLevel getChildren isExpandable in TreeControl. * Use static varaible to pass node data * fix lint * another round * update documents according to comments
* Add Cdk Tree component * Put tree control in control folder. Removed NestedNode FlatNode types and add getLevel getChildren isExpandable in TreeControl. * Use static varaible to pass node data * fix lint * another round * update documents according to comments
* Add Cdk Tree component * Put tree control in control folder. Removed NestedNode FlatNode types and add getLevel getChildren isExpandable in TreeControl. * Use static varaible to pass node data * fix lint * another round * update documents according to comments
* Add Cdk Tree component * Put tree control in control folder. Removed NestedNode FlatNode types and add getLevel getChildren isExpandable in TreeControl. * Use static varaible to pass node data * fix lint * another round * update documents according to comments
* Add Cdk Tree component * Put tree control in control folder. Removed NestedNode FlatNode types and add getLevel getChildren isExpandable in TreeControl. * Use static varaible to pass node data * fix lint * another round * update documents according to comments
* Add Cdk Tree component * Put tree control in control folder. Removed NestedNode FlatNode types and add getLevel getChildren isExpandable in TreeControl. * Use static varaible to pass node data * fix lint * another round * update documents according to comments
* Add Cdk Tree component * Put tree control in control folder. Removed NestedNode FlatNode types and add getLevel getChildren isExpandable in TreeControl. * Use static varaible to pass node data * fix lint * another round * update documents according to comments
* Add Cdk Tree component * Put tree control in control folder. Removed NestedNode FlatNode types and add getLevel getChildren isExpandable in TreeControl. * Use static varaible to pass node data * fix lint * another round * update documents according to comments
* Add Cdk Tree component * Put tree control in control folder. Removed NestedNode FlatNode types and add getLevel getChildren isExpandable in TreeControl. * Use static varaible to pass node data * fix lint * another round * update documents according to comments
* Add Cdk Tree component * Put tree control in control folder. Removed NestedNode FlatNode types and add getLevel getChildren isExpandable in TreeControl. * Use static varaible to pass node data * fix lint * another round * update documents according to comments
* Add Cdk Tree component * Put tree control in control folder. Removed NestedNode FlatNode types and add getLevel getChildren isExpandable in TreeControl. * Use static varaible to pass node data * fix lint * another round * update documents according to comments
* Add Cdk Tree component * Put tree control in control folder. Removed NestedNode FlatNode types and add getLevel getChildren isExpandable in TreeControl. * Use static varaible to pass node data * fix lint * another round * update documents according to comments
* Add Cdk Tree component * Put tree control in control folder. Removed NestedNode FlatNode types and add getLevel getChildren isExpandable in TreeControl. * Use static varaible to pass node data * fix lint * another round * update documents according to comments
* Add Cdk Tree component * Put tree control in control folder. Removed NestedNode FlatNode types and add getLevel getChildren isExpandable in TreeControl. * Use static varaible to pass node data * fix lint * another round * update documents according to comments
* Add Cdk Tree component * Put tree control in control folder. Removed NestedNode FlatNode types and add getLevel getChildren isExpandable in TreeControl. * Use static varaible to pass node data * fix lint * another round * update documents according to comments
* Add Cdk Tree component * Put tree control in control folder. Removed NestedNode FlatNode types and add getLevel getChildren isExpandable in TreeControl. * Use static varaible to pass node data * fix lint * another round * update documents according to comments
* Add Cdk Tree component * Put tree control in control folder. Removed NestedNode FlatNode types and add getLevel getChildren isExpandable in TreeControl. * Use static varaible to pass node data * fix lint * another round * update documents according to comments
* Add Cdk Tree component * Put tree control in control folder. Removed NestedNode FlatNode types and add getLevel getChildren isExpandable in TreeControl. * Use static varaible to pass node data * fix lint * another round * update documents according to comments
expect(treeControl.expansionModel.selected.length) | ||
.toBe(2, 'Expect two dataNodes in expansionModel'); | ||
|
||
treeControl.collapse(seconNode); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typo seconNode -> secondNode
* Add Cdk Tree component * Put tree control in control folder. Removed NestedNode FlatNode types and add getLevel getChildren isExpandable in TreeControl. * Use static varaible to pass node data * fix lint * another round * update documents according to comments
* Add Cdk Tree component * Put tree control in control folder. Removed NestedNode FlatNode types and add getLevel getChildren isExpandable in TreeControl. * Use static varaible to pass node data * fix lint * another round * update documents according to comments
* Add Cdk Tree component * Put tree control in control folder. Removed NestedNode FlatNode types and add getLevel getChildren isExpandable in TreeControl. * Use static varaible to pass node data * fix lint * another round * update documents according to comments
* Add Cdk Tree component * Put tree control in control folder. Removed NestedNode FlatNode types and add getLevel getChildren isExpandable in TreeControl. * Use static varaible to pass node data * fix lint * another round * update documents according to comments
This issue has been automatically locked due to inactivity. Read more about our automatic conversation locking policy. This action has been performed automatically by a bot. |
An initial version of cdk-tree
For #3175