Hi,
I've a TreeView and it seem that I'm too stupid to updated data or trigger a reload...
my template:
<
kendo-treeview
kendoTreeViewExpandable
[nodes]="categories"
textField
=
"name"
[children]="fetchChildren"
[hasChildren]="hasChildren">
my component (relevant parts only):
{
public categories: IProductCategory[];
@ViewChild(TreeViewComponent)
tree: TreeViewComponent;
fetchChildren = (item: IProductCategory): Observable<IProductCategory[]> => {
return
Observable.create(observer => {
const categoryListPromise =
this
._categoriesStore.query({
filter: { field:
'parentId'
, operator:
'eq'
, value: item.id }
}).then(result => {
observer.next(result.records);
});
});
};
}
so..
* the data input is bound to this.categories, which is a flat list of category objects
* the fetchChildren method loads and returns child categories
Now I'll get updates from our (custom) data layer and have to update the tree:
this._categoriesStore.updates().subscribe(e => {
const event: { record: ICategory, type: 'update'|'delete'|'create' } = e;
// update tree view ..
// ???
});
I can update the root categories by simply updating the cmp.categories array, but this array does not contain any child nodes. I was looking for something like "tree.getNode().reload()" but,.. nope,.. and I'm confused :-)
So my questions are...
* How can I update child nodes that are loaded using function defined in "children" input?
* How can I tell the tree view to reload children of a single node (execute fetchChildren() again)?
Thanks and best regards,
Chris
5 Answers, 1 is accepted
The TreeView component is designed to fetch data only for a specific expanded node (the clicked one). There is no specific API, because the component supports data binding to any data structure until it contains the required `textField` property. We treat the data as immutable unknown structure, which makes adding a specific node logic (like reload()) to it pretty hard and even unnecessary.
The solution in this case is to re-expand the node you want to reload. Thus the component will call the fetchChildren method with the correct parent node and the code will be able to feed the component with the updated data. Basically, you will need to update the expandedKeys collection in order to collapse and expand the node.
https://plnkr.co/edit/0RSzqHsYxOuXWC36hGED?p=preview
We will consider a possible improvement in the component API, but we will need to gather more feedback from our users before we do that.
Regards,
Georgi Krustev
Progress Telerik
Hi Shashi,
I've managed to update the example with the latest versions of our component:
https://stackblitz.com/edit/angular-czbgac-kkfcdq?file=src%2Fapp%2Fapp.component.ts
I hope this helps.
Regards,Yanmario
Progress Telerik
Hello Georgi,
just to give you some feedback...
Using the close / reopen approach works fine, two things to note for my personal case:
Update w/o remote call:
Calling fetch children leads to a remote service call. In case of an update, I have all modifications, so I could update the node w/o this call. Missing tree feature: I cannot access and modify tree data (except of the root categories that are available via data binding)
Delete record
Our update events are "the result after a database operation". In case of delete events, I've only the id of the deleted record, but not the data of the deleted record. So what I want to to is:
* If tree contains the deleted record...
* Find node in tree
* Delete node or reload parent node
Problem: The complete record exists only inside of the tree and there's no official api to query tree data => I don't know the parentId and so I don't know the node that I have to reload.
My current workaround is this shiny piece of "don't do this code":
const lookupService = (<
any
>this.tree).treeViewLookupService;
if (!lookupService || !lookupService.map) {
throw new Error('Could not access internal tree view lookup service.');
}
const map: Map<
string
, ItemLookup> = lookupService.map;
const mapValues = Array.from(map.values()); // ts workaround
for (const i of mapValues) {
if (i.item && i.item.dataItem && i.item.dataItem.id === categoryId) {
return i.item.dataItem;
}
}
As I said, everything works for me and this is just some customer feedback, hoping it may help you for further tree view version.
Best regards,
Chris
As a result of the TreeView compositional nature, the node children can be easily controlled through the `fetchChildren` function. Basically, the function that feeds the component can start returning a Subject instead of an Observable. Thus you can push new data for particular node level with a complete ease. Check the modified plunkr where the suggested approach is implemented:
https://plnkr.co/edit/KGiWpUUzlZjjxR2bRNND?p=preview
Note that the `fetchChildren` method now returns a BehaviorSubject, which helps to track the requests and cache the result. Now you can access a particular Subject instance and push new data whenever you want, like it is done with the reload method:
public reload(categoryID: number): void {
const items$ =
this
.fetchMap.get(categoryID);
if
(!items$) {
return
; }
this
.categoryService
.fetchProducts(categoryID)
.pipe(take(1))
.subscribe(data => items$.next(data.slice(5)));
}
From implementation stand point of view, it will be best if such functionality is moved to a service that performs the data fetch (CategoriesService in our case). Thus the logic will be encapsulated and a clearer API will be available for use. Of course this part is entirely up to the developer's personal preferences.
I hope that the given approach is more versatile and improves the component behavior. Let me know if I am missing something.
Regards,
Georgi Krustev
Progress Telerik
Hello Georgi,
thanks again. This example allows me to keep a copy of the tree state so I can update and modify everything. In general, that's fine, but - let's say this way,.. not as easy as it could be.
I think the main problem here is, that the component is made to be used with data binding, which works perfectly - als long as there is a "source" that knows what data to show. That's easy as long as we're talking about things like mobile apps etc,... (reactive stores, ..) but quite difficult when dealing with 100.000s of records and a large amount of views.
Let me try to give you an overview of how we solved it (categories as example model type):
* CategoriesProxy (service): Simply maps remote functions to our js client. There's an abstract class that provides standard crud functions, so we don't have to write this multiple times
* CategoriesStore (service): That's what we'll access inside of our app, we can get, query, save, delete here. It's responsible for caching, notifying all consumers, notifying other browser windows, applying web sockets update, .etc...it's based on an abstract class to.
* ProductListPage (component): get categories and products stores via di and contains tree with categories and list of products.
Now, where's the problem...
* When a record was modified (store.save()) we need to update this record everywhere in UI. To be able to do this in lists, our store would have return all query(somefilter) calls with a Subject and keep this subject, so we can check if it contains the modified record and if so, emit new value (querySubject.next(modifiedResultList)). hm,.. nope, not the way we decided to go. We wanted something more fine grained...
* What we do: Our store(s) provide a stream of update events. One event describes an update for a single record. If a component that shows a list of records, it has to subscribe to this update stream and update it's view.
... and that's what I'm working on and now we see, that our "per record event concept" doesn't work well with kendo "complete lists as one observable value" concept ;-)
The current tree "data binding only concept" means in fact:
* keep a copy of everything (we need a "source" to bind to)
* update the source (what will update the tree too)
After doing this for our categories tree, I know, that's not as easy as it sounds and has a lot of edge cases. So my next task will be to remove the use of the internal tree lookup service and keep a copy of everything outside and then, let's move it to a directive that add a "non data binding API" to the tree - let's see what happens ;-)
Thanks for the plunker, it was very helpful!
Cheers,
Chris