0

I'm a little bit confused while trying to make a call to an API. I first created a service that is calling the API:

public getCategories(): Observable<any> {
  let getCategoriesUrl = 'http://localhost:4300/WS/GetCategories';
  return this.http.get<any>(getCategoriesUrl, {
    headers: this.httpOptions, responseType: 'text' as 'json'
  });
}

In my component I have a method that retrieves the data. My data is not clean that's why do clean special caracters that are returned. That's also why in my service i do not retrieve an observable of a specific model but of 'any'. Here is the implementation.

private getCategories() {
  this.soapService.getCategories().subscribe(
    (res) => {
      console.log(typeof res);
      res = res.replace(/\n/g, '');
      this.replacement.forEach(
        item => {
          res = res.replace(item, '');
        }
      );
      // res is a string with all data from API call => parsing string to object
      // console.log(res);
      this.categoriesResponseModel = JSON.parse(res);
      console.log('iterate after string parsing');
      // @ts-ignore
      for (const category of this.categoriesResponseModel.CATEGORIES) {
        // console.log(category);
        this.categories.push(category);
      }
      console.log("In get categories");
      console.log("Output  ", this.categories);
    },
    (err) => {
      console.log(err.message);
    },
    () => {
      console.log('Completed - Categories pushed');
    }
  );
}

In the method output is printed with the expected values on the line containing the following console.log: console.log("Output ", this.categories);

enter image description here

ngOnInit() {
  console.log('# ngOnInit() called');
  this.getCategories();
  console.log(this.categories)
  console.log("output  " + this.categories)
}

In the ngOnInit i do have an empty array as output. I test to display the result in the HTML page:

{{ categories.length }}
<div *ngFor="let category of categories">
  {{ category.CategoryID }} - {{ category.CategoryName }}: {{ category.DocDescription }}
</div>

I obtain length equals to 0 unfortunatelly.

davidvera
  • 1,292
  • 2
  • 24
  • 55
  • console.log in ngOnInit does not work and should not work since this is an async call. However, {{ categories.length }} should be non-zero. How many times is this line being called ... this.categories.push(category); ... ? this is the most important code ... Also where do you define this.categories as an empty array? – danday74 Sep 16 '21 at 10:05
  • hi, thanks for your answer. In this.categorie.push(category) I have 3 entries in my getCategories method. That's confusing me. I don't think it's related but the component is lazy loaded... – davidvera Sep 16 '21 at 10:07
  • lazy loading wont effect anything - is push called 3 times? - where do you define this.categories? – danday74 Sep 16 '21 at 10:09
  • I define the this member in the component. Simply declaring it this way: categories: CategoryModel[] = []; – davidvera Sep 16 '21 at 10:14
  • ok fine, as expected, so how many times is push called? push adds an item to an array so if you call it 3 times there should be 3 times in the array - hence how many times is it called? – danday74 Sep 16 '21 at 10:16
  • Indeed ... it is called 3 times as I have 3 entries. I update the question with a capture... – davidvera Sep 16 '21 at 10:18
  • console.log("Output ", this.categories, this.categories.length); ..... length here is 3? ..................................................... {{ categories.length }} ... length here is 0? – danday74 Sep 16 '21 at 10:23
  • In HTML : {{ categories.length }} i obtain 0 ... In console.log I have 3 entries. – davidvera Sep 16 '21 at 10:25
  • this is weird! is your code on github or somewhere I can see it? – danday74 Sep 16 '21 at 10:30
  • 1
    That's because change detection needs the array to be recreated, you should not push into it and expect the items to get rendered (unless you inject `ChangeDetectorRef` and call `cd.detectChanges()` after pushing all the categories in). – Octavian Mărculescu Sep 16 '21 at 10:30
  • @OctavianMărculescu !! Thanks... I injected the ChangeDetectorRef and I called instead the markForCheck once the values are pushed onto the array :). I'll post the updated code !! – davidvera Sep 16 '21 at 10:54

3 Answers3

0

Two things

1.

ngOnInit() {
  console.log('# ngOnInit() called');
  this.getCategories();
  console.log(this.categories)
  console.log("output  " + this.categories)
}

Here console.log(this.categories) might be undefined due to async assignment inside the subscription. See this canonical post on async data.

  1. Pushing to an array doesn't trigger Angular change detection. In this case, you could directly assign to the variable.
this.categories = this.categoriesResponseModel.CATEGORIES;

instead of

  // console.log(category);
  this.categories.push(category);
}
ruth
  • 29,535
  • 4
  • 30
  • 57
0

Instead of this:

// @ts-ignore
for (const category of this.categoriesResponseModel.CATEGORIES) {
  // console.log(category);
  this.categories.push(category);
}

you should try this:

this.categories = [...this.categoriesResponseModel.CATEGORIES];
Octavian Mărculescu
  • 4,312
  • 1
  • 16
  • 29
0

I've been inspired by Octavian Mărculescu proposal to use ChangeDetectorRef. Here is my updated code for getCategories in the component.

I also changed the place i call the getCategories and placed it in the constructor

constructor(@Inject(TRANSLATION) public readonly lang: Translation,
            private cd: ChangeDetectorRef,
            private soapService: SoapApiService,
            private route: ActivatedRoute) {
  this.getCategories();
}

Here is the implementation:

getCategories(): void {
  const values: CategoryModel[] = [];
  const response = this.soapService.getCategories().subscribe(
    (res) => {
      console.log(typeof res);
      res = res.replace(/\n/g, '');
      this.replacement.forEach(
        item => {
          res = res.replace(item, '');
        }
      );
      // res is a string with all data from API call => parsing string to object
      // console.log(res);
      this.categoriesResponseModel = JSON.parse(res);
      console.log('iterate after string parsing');
      // @ts-ignore
      for (const category of this.categoriesResponseModel.CATEGORIES) {
        // console.log(category);
        values.push(category);
      }
      this.cd.markForCheck();
    },
    (err) => {
      console.log(err.message);
    },
    () => {
      console.log('Completed - Categories pushed');
      this.categories = values;
      console.log(this.categories);
    }
  );
}
davidvera
  • 1,292
  • 2
  • 24
  • 55