Home > Software engineering >  HTTP calls depending on another request in the same component Angular repeated query
HTTP calls depending on another request in the same component Angular repeated query

Time:11-23

I have an angular application that read from a config file the backend URL in order to process the queries. The advantage of this is that the back and front can be deployed on any servers.

In my angular application I read a JSON containing the following information:

{
  baseUrl: "http://server-url:8080/backend
}

In a service I implemented a method that open the json in the service layer:

getJsonConfig() {
  let jsonUrl = './assets/configuration/config.json';
  return this.http.get<any>(jsonUrl)
    .pipe(
      map((response: any) => {
        this.baseUrl = response.baseUrl;
      })
    );
}

I have to call 2 other HTTP entry points to display data on my component: the call of those 2 URL depend on the baseUrl retrieved. First I have to retrieve a database name with this call:

getDbName(): Observable<getDbName> {
  const url = this.baseUrl   "/dbInfos"
  return this.http.get<getDbName>(url);
}

getData(): Observable<data[]> {
  let url = this.baseUrl   "/actions"
  return this.http.get<data[]>(url);
}

Here it comes more complex: I have 2 components: a parent component and a child component. the method getData must be updated every 10 seconds because it is used to display a graph...

In the parent component, I used ngOnInit webhook to call the webservice

ngOnInit(): void {
  this.httpService.getJsonConfig().subscribe({
    next: res => {
      console.log(res)
      this.jsonConfigObject = res;
    },
    error: err => {
      this.loading = false;
      Utils.handleErrorMessage(err, this.messageService);
    },
    complete: () => {
      this.getData();
      this.getDbName();
    }
  });
}

At this stage, I wait the configuration to be loaded to call the 2 other webservices. Here is the implementation of those 2 methods:

private getDbName() {
  this.httpService.getDbName().subscribe({
      next: (res) => {
        if(res != undefined) {
          this.isPathLoaded = true
        }
        this.dbName = res.path;
        return res.path;
      },
    error: err => {
      this.loading = false;
      Utils.handleErrorMessage(err, this.messageService);
    }
  });
}

private getData() {
  this.subscribe = timer(0, 10000).pipe(
    tap((res) => {
      console.log(res);
      return res;
    }),
    takeUntil(this.unsub),
    switchMap(() => this.httpService.getData().pipe(
      tap({
        next: (res) => {
          if(res.length >= 1) {
            this.isDataLoaded = true
          }
          this.data = res;
        }
      }),
    ))
  ).subscribe();
}

I have some data to display in the parent component and for this I check i do have values before inserting the data. Here is how i handle this:

<div  *ngIf="data">
  <p >
     Database : {{ dbName }} <br />
     Id : {{ histData.id }} <br />
  </p>
</div>

After in my code I use primeng where I bind the data to the select and labels have the id as value.

<p-dropdown
    [options]="data"
    [(ngModel)]="histData"
    optionLabel ="id">
</p-dropdown>

And finally I send data to the child component:

<app-stacked-charts [data] = "histData" ></app-stacked-charts>

My issues are the following:

  1. I have an error message : ERROR Error: NG0100: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: ''. Current value: 'someCurrentValue'
  2. When i select a value in my dropdown select, In certain cases, after a few seconds, the select is returning to its default value instead of remaining on the selected value.

CodePudding user response:

I have some comments regarding your implementation.

First of all, you need to use APP_INITIALIZER injection token to load configuration data to you application. It will increase initial boot-up time, but it will simplify your life.

Second, switchMap operator shall be used to make subsequent dependant requests, ie in this case

getData(): Data {
    return this.getDbName().pipe(
        switchMap(dbName => this.getData(dbName))
    )
}

As soon as subscribe to getData() method, first getDbName and then data will be retrieved

Thirdly, you use async pipe to subscribe inside the template, then you don't need to handle subscriptions whatsoever

<div  *ngIf="data$ | async as data">
  <p >
     Database : {{ dbName }} <br />
     Id : {{ histData.id }} <br />
  </p>
</div>

CodePudding user response:

Let me add some concerns to Timothys answer. Some may have influence on your issues:

  • You are not unsubscribing from getDbName subscription
  • In getData your takeUntilcomes before the subsequent switchMap(read further)
  • In getJsonConfig your map doesn't return anything. You are mapping to void. Change to tap here. In getDatayou return a value in a tap, which is just useless. tap doesn't alter the streamed value.
  • Related