This is a migrated thread and some comments may be shown as answers.

Update / Refresh TreeView data

5 Answers 2275 Views
General Discussions
This is a migrated thread and some comments may be shown as answers.
Christoph
Top achievements
Rank 1
Christoph asked on 13 Mar 2018, 04:00 PM

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

Sort by
0
Accepted
Georgi Krustev
Telerik team
answered on 14 Mar 2018, 08:35 AM
Hello Christoph,

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
Try our brand new, jQuery-free Angular components built from ground-up which deliver the business app essential building blocks - a grid component, data visualization (charts) and form elements.
0
Christoph
Top achievements
Rank 1
answered on 14 Mar 2018, 12:24 PM
Thanks Georgi! My stupid brain didn't fully realize that "expanded state" thing and that it can be used to trigger a reload. Your solution works perfectly. Thanks for providing a full example!
Shashi Kant
Top achievements
Rank 1
commented on 23 Apr 2022, 02:32 PM

https://plnkr.co/edit/0RSzqHsYxOuXWC36hGED?p=preview is not working . So can you please share me updated link
Yanmario
Telerik team
commented on 26 Apr 2022, 12:26 PM

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
0
Christoph
Top achievements
Rank 1
answered on 15 Mar 2018, 10:02 AM

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


0
Georgi Krustev
Telerik team
answered on 19 Mar 2018, 10:06 AM
Hello Christoph,

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
Try our brand new, jQuery-free Angular components built from ground-up which deliver the business app essential building blocks - a grid component, data visualization (charts) and form elements.
0
Christoph
Top achievements
Rank 1
answered on 19 Mar 2018, 12:52 PM

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

 

Tags
General Discussions
Asked by
Christoph
Top achievements
Rank 1
Answers by
Georgi Krustev
Telerik team
Christoph
Top achievements
Rank 1
Share this question
or