I have an Angular 2 app which calls a JSON API to load data into a nested HTML list (<ol>
). Essentially a dynamically generated tree-view. Since the data-set from the API will eventually become very large the tree is populated progressively when the user opens a branch (user opens a branch, the API is called with the id of the node which has been opened, the API returns a JSON feed of the direct children of than node, Angular binds the returned JSON to some new HTML <li>
elements and the tree expands to show the new branch).
You can see it in action in this Plunkr. It uses a recursive directive and works nicely. Currently, because I can't open the actual API up to public requests, it's just calling a static JSON feed, hence the returned data for each of the nodes is just repeated, but hopefully you get the idea.
The issue I'm now seeking to overcome is to prevent extraneous HTTP calls when a branch is closed and then re-opened. Having read the HTTP client docs I was hoping that this would be as simple as modifying the method which subscribes to the data service to chain the .distinctUntilChanged()
method to the app/content-list.component.ts
file, as follows:
getContentNodes() {
this._contentService.getContentNodes(this._startNodeId)
.distinctUntilChanged()
.subscribe(
contentNodes => this.contentNodes = contentNodes,
error => this.errorMessage = <any>error
);
}
However, when I open up the browser network inspector, it still makes a call to the API each time the same tree branch is re-opened.
Could anyone advise how to resolve this?
Many thanks.
EDIT:
I'm trying to implement @Thierry Templier's answer below; caching the data returned by the API. So the content service is now:
import {Injectable} from 'angular2/core';
import {Http, Response} from 'angular2/http';
import {Headers, RequestOptions} from 'angular2/http';
import {ContentNode} from './content-node';
import {Observable} from 'rxjs/Observable';
@Injectable()
export class ContentService {
constructor (private http: Http) {}
private _contentNodesUrl = 'app/content.json';
_cachedData: ContentNode[];
getContentNodes (parentId:number) {
if (this._cachedData) {
return Observable.of(this._cachedData);
} else {
return this.http.get(this._contentNodesUrl + parentId)
.map(res => <ContentNode[]> res.json())
.do(
(data) => {
this._cachedData = data;
})
.catch(this.handleError);
}
}
private handleError (error: Response) {
console.error(error);
return Observable.throw(error.json().error || 'Server error');
}
}
However, what's happening here is that when the page loads, this._cachedData
is returning false and the API call is firing and populating this._cachedData
with the returned value (data
), which is correct. However, when the tree node is opened in the web page UI, the following line is run repeatedly thousands of times until eventually the browser crashes:
return Observable.of(this._cachedData);
I'm still very much at the beginning when it comes to Angular 2, so would really appreciate any pointers as to why this isn't working to help my learning.
I would cache the data per node using the following approach:
getData() {
if (this.cachedData) {
return Observable.of(this.cachedData);
} else {
return this.http.get(...)
.map(res => res.json())
.do((data) => {
this.cachedData = data;
});
}
}
The "problem" with the distinctUntilChanged
is that you need to use the same data flow. But it's not the case in your case since you execute several requests on different data flow...
See this question for more details: