Peter Zupanc
Peter Zupanc

Reputation: 1

Angular does not re-render html component after calling API

Code of my product-list component is as follows:

import {ChangeDetectorRef, Component} from '@angular/core';
import {ProductsListService} from './products-list.service';

@Component({
  selector: 'app-products-list',
  templateUrl: './products-list.component.html',
  styleUrls: ['./products-list.component.css']
})
export class ProductsListComponent {
  rerender = false;
  products;
  constructor(private service: ProductsListService, private cdRef: ChangeDetectorRef) {
    this.products = service.getProducts();
  }
  getProductsAgain(query: string): void {
    this.products = this.service.getProducts(query);
    console.log(this.products);
    this.doRerender();
  }
  // tslint:disable-next-line:typedef
  doRerender() {
    this.rerender = true;
    this.cdRef.detectChanges();
    this.rerender = false;
  }
}

and html looks like this:

<ng-container *ngIf="!rerender">
  <ul>
    <li *ngFor="let product of products">
      <img src="{{product.picture}}" height="150px">
      <p>{{product.title}}</p>
      <div>
        <p>{{"ENERC_KCAL: "+product.nutrients._ENERC_KCAL}}</p>
        <p>{{"PROCNT: "+product.nutrients._PROCNT}}</p>
        <p>{{"FAT: "+product.nutrients._FAT}}</p>
        <p>{{"CHOCDF: "+product.nutrients._CHOCDF}}</p>
        <p>{{"FIBTG: "+product.nutrients._FIBTG}}</p>
      </div>
    </li>
  </ul>
</ng-container>

also product-list.service looks like this:

import {Injectable, Optional} from '@angular/core';
import {HttpClient, HttpHeaders} from '@angular/common/http';
import {ResponseDTO} from './ResponseDTO';
import {NutrientsDTO} from './NutrientsDTO';

@Injectable({
  providedIn: 'root'
})
export class ProductsListService {
  constructor(private http: HttpClient) {
  }

  public getProducts(q?: string): Array<ResponseDTO> {
    const query = (q === undefined) || (q == null) || (q === '') || (q === ' ') ? 'orange' : q;
    const url = 'https://edamam-food-and-grocery-database.p.rapidapi.com/parser?ingr=' + query;
    const httpHeaders = new HttpHeaders({
      'content-type': 'application/json',
      'x-rapidapi-key': '9f95da4cd3mshd0fbea030c7c8e3p16d06bjsn503efa8f187f',
      'x-rapidapi-host': 'edamam-food-and-grocery-database.p.rapidapi.com'
    });
    const arr: Array<any> = [];
    const res: Array<ResponseDTO> = [];
    this.http.get(url, {headers: httpHeaders, observe: 'body', responseType: 'json'}).subscribe(data => {
        const obj = JSON.parse(JSON.stringify(data));
        arr.push(obj.hints);
        for (const ar of arr) {
          const nutrients: NutrientsDTO = new NutrientsDTO();
          for (const a of ar) {
            // tslint:disable-next-line:forin
            for (const nutrient in a.food.nutrients) {
              if (a.food.nutrients.hasOwnProperty(nutrient)) {
                if (nutrient === 'ENERC_KCAL') {
                  nutrients.ENERC_KCAL = Math.round((a.food.nutrients[nutrient] + Number.EPSILON) * 100) / 100;
                } else if (nutrient === 'PROCNT') {
                  nutrients.PROCNT = Math.round((a.food.nutrients[nutrient] + Number.EPSILON) * 100) / 100;
                } else if (nutrient === 'FAT') {
                  nutrients.FAT = Math.round((a.food.nutrients[nutrient] + Number.EPSILON) * 100) / 100;
                } else if (nutrient === 'CHOCDF') {
                  nutrients.CHOCDF = Math.round((a.food.nutrients[nutrient] + Number.EPSILON) * 100) / 100;
                } else if (nutrient === 'FIBTG') {
                  nutrients.FIBTG = Math.round((a.food.nutrients[nutrient] + Number.EPSILON) * 100) / 100;
                } else {
                  console.log('not found!');
                }
              }
            }
            const noImgUrl = 'https://st4.depositphotos.com/14953852/24787/v/600/depositphotos_247872612-stock-illustration-no-image-available-icon-vector.jpg';
            const imgSrc = a.food.image == null ? noImgUrl : a.food.image;
            const response: ResponseDTO = new ResponseDTO(imgSrc, a.food.category, a.food.label, nutrients);
            res.push(response);
          }
        }
      }
    );
    return res;
  }
}

I am calling product-list method getProductsAgain by different component search-bar, which simply goes back to the service and calls API again with given parameter. The call is successful and it also assigns value to product variable of ProductsListComponent, but page does not refresh or re-render html. What should I do? or what am I doing wrong?

UPDATE #1:

Still have same problem, I tried all methods like trackBy, [...arrayOfResults], returned Subscription or Observable in component and got result in there, inspected result with setTimeout(()=>{console.log(products)},5000); to see if API request really assigns new values to products variable (and it does!). I even tried to force re-render with ChangeDetectorRef.detectChanges() but no success. So with API request is nothing wrong, but for some reason angular does not see that I update with API request products variable and its stuck on initial results (results in service.getProducts()). What am I doing so wrong?

Upvotes: 0

Views: 1400

Answers (2)

tiakham
tiakham

Reputation: 77

you can try like this as follow ?; i didnt try it but it is might be all right.

getProductsAgain(query: string): void {
    this.service.getProductsAgain(query).subscribe(response => {
        this.products = response.data;
        console.log(this.products);
        this.doRerender();
      }, error => {
        console.log(error);
      });
}

Upvotes: 1

AliF50
AliF50

Reputation: 18859

I am thinking it has to do with the fact that arrays and objects in JavaScript are reference types and you have to change the location of RAM for these type of variables for change detection to take place.

Try this:

getProductsAgain(query: string): void {
    // assign this.products to a new location in RAM/memory for change detection to take place
    // by using the spread operator and assigning to a new array
    this.products = [...this.service.getProducts(query)];
    // console.log(this.products); I don't think you need these two lines anymore
    // this.doRerender();
  }

Upvotes: 0

Related Questions