Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Prevent duplicate HTTP requests in Angular 2 API-driven tree-view

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.

like image 386
Dan Avatar asked Apr 10 '16 16:04

Dan


People also ask

Is it possible to avoid duplicate HTTP calls with Angular Universal?

You have avoided duplicate HTTP calls with Angular Universal. I hope that the NG Universal team continues to do awesome work and that some of this manual work can be relieved from the programmer. Thanks for reading. Does TransferHttpCacheModule only avoid duplicate GET requests?

How to handle multiple HTTP requests in angular?

There are multiple ways to handle multiple requests; they can be sequential or in parallel. In this post, we will cover both. Let's start with a simple HTTP request using the Angular Http service. In our app, we have just a single component that pulls in Angular's Http service via Dependency Injection.

What are duplicate API requests and how to avoid them?

How to Avoid Duplicate API Requests? Duplicate API requests are common and if dealt wisely, can help developers in creating a seamless user experience. In a scalable application, duplicate API requests can be problematic to the resources on a server, can affect the cost, and can interrupt performance.

Are your HTTP calls duplicated on the server?

Among these is the fact that HTTP calls are duplicated on both the server and client applications. This issue can be resolved differently depending on your specific scenario. If your application is making HTTP calls with Angular's HttpClient, then the solution is rather straightforward.


1 Answers

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:

  • Angular 2, best practice to load data from a server one time and share results to components
like image 105
Thierry Templier Avatar answered Oct 09 '22 14:10

Thierry Templier