1

I'm using Angular2 RC5 with an ASP.NET Core server that makes the API calls to get my data. I'm actually wondering if there is a way to reduce the number of http calls you make with Angular2, because I fear there will be a lot if I keep using components the way I do. Here is a concrete example.

I want to get a text value from the database, which is defined by an ID and a Language. I then made the following component :

dico.component.ts

@Component({
    selector: 'dico',
    template: `{{text}}`,
    providers: [EntitiesService]
})

class Dico implements AfterViewInit {
    @Input() private id: string;   
    @Input() private lang: string;
    private text: string = null;

    // DI for my service
    constructor(private entitiesService: EntitiesService) {
    }

    ngAfterViewInit() {
        this.getDico();
    }

    // Call the service that makes the http call to my ASP Controller
    getDico() {
        this.entitiesService.getDico(this.id, this.lang)
            .subscribe(
            DicoText => this.text = DicoText
            );
    }
}

@Component({
    template: `<dico [id] [lang]></dico>`,
    directives: [Dico]
})

export class DicoComponent {
}

Here is the code from my service :

entities.service.ts

getDico(aDicoID: string, aLangue: string) {
        // Parameters to use in my controller
        let params = new URLSearchParams();
        params.set("aDicoID", aDicoID);
        params.set("aLangue", aLangue);
        // Setting up the Http request
        let lHttpRequestBody = params.toString();
        let lControllerAction: string = "/libelle";
        let lControllerFullURL: string = this.controllerURL + lControllerAction;
        let headers = new Headers({ 'Content-Type': 'application/x-www-form-urlencoded' });
        let options = new RequestOptions({ headers: headers });

        return this.http.post(lControllerFullURL, lHttpRequestBody, options)
            .map((res: any) => {
                // Parsing the data from the response
                let data = res.json();

                // Managing the error cases
                switch (data.status) {
                    case "success":
                        let l_cRet: string = data.results;
                        if (l_cRet != null && !l_cRet.includes("UNDEFINED")) {
                            return data.results;
                        } else {
                            throw new Error("Erreur récupération Dico : " + l_cRet);
                        }
                    case "error":
                        throw new Error("Erreur récupération Dico : " + data.message);
                }
            }
            ).catch(this.handleError);
}

Then I can use my newly made component in my app :

randomFile.html

<dico id="201124" lang="it"></dico>
<dico id="201125" lang="en"></dico>
<dico id="201126" lang="fr"></dico>

But this application will eventually use hundreds of these "dico" and I was wondering how I could manage some pre-fetch or something like that before the app fully loads. Does it even matter ? Will that affect performance in the long term ?

Any advice would be greatly appreciated.

EDIT : These dico allow me to fetch from the database a text translated into the langage I want. Here, in the example above, I have 3 "dico" that will output some text in italian, french, and english. My application will use a lot of them, as every text in every menu will be a "dico", and the problem is that there will be a lot of them, and right now for every "dico" I make, my service is called and makes one http call to get the data. What I want is to somehow define all my dicos, call the service which will give me the text of all my dicos in an array to avoid making several calls (but I don't really know how to do that).

Alex Beugnet
  • 4,003
  • 4
  • 23
  • 40
  • 2
    Sounds like http://stackoverflow.com/questions/36271899/what-is-the-correct-way-to-share-the-result-of-an-angular-2-http-network-call-in – Günter Zöchbauer Sep 06 '16 at 09:17
  • I'm quite a noob yet with Observables, and I guess this link is pretty interesting, but I need to go more in depth... A little question though, is there a way to like declare a list of "Dico" like I showed in my code and tell my service to do a single call that will return me an array of the results ? I don't really see how I can map my results according to the differents ... And thanks, caching the results will be something I really need to do. – Alex Beugnet Sep 06 '16 at 10:10
  • I think that is what the answers (at least mine) in the linked answer do. No matter how many calls are made to the service, all callers will be kept "on hold" until the response arrives and then every caller will receive the full result at once. It also returns the same result on subsequent calls when you might get updated results not the cached ones. You need to tell the service to not use the cache. That is not implemented in my answer there, but should be quite simple - just add a method to clear the cache so the service creates a new call. – Günter Zöchbauer Sep 06 '16 at 10:16
  • If I understood correctly, this link is useful in the case you have to get the same data from several components, so that you only need to make the call once and you can even cache it to avoid more calls. Here I know I will have a lot of to call, and thus a lot of calls to do. The problem is that even if I cache them, the first load will make hundreds of http calls... What I really want to do is like declare my Dicos like I did in my code sample, and tell my service to do one call that will return me an array with all the results, so that I only need to do one call for each component. – Alex Beugnet Sep 06 '16 at 10:37
  • But then I don't know how I can map my results from the array to the different Dicos... Maybe I misunderstood from your answer in the link, but I think it is a bit different. – Alex Beugnet Sep 06 '16 at 10:38
  • No idea what " I will have a lot of to call, and thus a lot of calls to do." means. "the first load will make hundreds of http calls" why? I don't understand your code. I guess I need more explanation what problem you actually try to solve. – Günter Zöchbauer Sep 06 '16 at 11:11
  • I edited the post... Hope it will be clearer this time ! – Alex Beugnet Sep 06 '16 at 11:40
  • 1
    You could just collect the requests from the ``s and delay the call to the server until you have all collected, then make a request for all required texts at once and then respond to the ``s. – Günter Zöchbauer Sep 06 '16 at 11:42
  • Alright, I'll look into, this seems to be what I need, even though I don't really know how... If you have any example of something like that, I'd be really grateful. Thanks again – Alex Beugnet Sep 06 '16 at 11:44
  • One question I can't answer from your information is, when is the right time for you to send the request? Perhaps `ngAfterContentInit()` in `AppComponent` – Günter Zöchbauer Sep 06 '16 at 11:50
  • Yes, that would be the best option I think, though I don't really know the difference between ngAfterViewInit() and ngAfterContentInit() – Alex Beugnet Sep 06 '16 at 12:04
  • Right, there shouldn't be a difference in the root component (`AppComponent`) because Angular2 doesn't support transclusion to the root component. Use `ngAfterViewInit()` instead. – Günter Zöchbauer Sep 06 '16 at 12:08

1 Answers1

1

A basic untested approach (I don't know observables too well myself)

class DicoService {
  private subjects = {}
  private ids = [];

  getDico(String id):Observable<Dico> {
    var s = this.subjects[id];

    if(!s) {
      this.ids.push(id);
      s = new Subject(); 
      this.subjects[id]=s;
    }
    return s.asObservable().share().first();
  }

  sendRequest() {
    http.get(....) /* pass this.ids */
    map(response => response.json())
    .subscribe(data => {
      for(item in data) { // don't know how to iterate exactly because I don't know how the response will look like
        this.subject[item.id].next(item.langText);
      }
      // you might cache them if other components added by the router also request them
      // this.subjects = {};
      // this.ids = []
    });
  }  
}
<dico [text]="dicoService.getDico('someId') | async"></dico>
ngAfterViewInit() {
  this.dicoService.sendRequest();
}
Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • Nice, I'll try to do something like that asap. One question though, what does `.take(1);` do ? Thanks – Alex Beugnet Sep 06 '16 at 12:36
  • 1
    That makes the observable close after one received value for the subscriber. If you want to keep sending updates to the subscriber omit `.take(1)`. – Günter Zöchbauer Sep 06 '16 at 12:41
  • Another thing I don't understand : `this.subjects[id]=o;` : What is o there ? Sorry if this seems obvious... – Alex Beugnet Sep 06 '16 at 12:56
  • Sorry, should be `s` (was a remainder of code I changed later). – Günter Zöchbauer Sep 06 '16 at 12:58
  • Hey, I'm coming back to you for this issue... So far everything is working great, and I managed to send my data, and get the text from my Web API. Now, I'm using `this.subjects[l_dicoID].next(l_dicoText);` like you showed in your example, but I am missing two things here : 1 : My text value isn't updated in my dico : I probably need to do something more and 2 : I probably need to tell the subscribers to complete once the data has been retrieved, and I have no idea how to do that... Should I do another post with some code ? – Alex Beugnet Sep 08 '16 at 15:16
  • Can you please create a new question with your current code? – Günter Zöchbauer Sep 08 '16 at 15:19
  • 1
    Sure, I'll make a new question. Thanks – Alex Beugnet Sep 08 '16 at 15:22
  • Done ! http://stackoverflow.com/questions/39395290/update-several-component-properties-with-a-single-rxjs-subscription – Alex Beugnet Sep 08 '16 at 15:47