angular2 ngFor not working while fetching data from api on ngOnInit()
Asked Answered
F

7

6

comment.component.ts:

import { Component, OnInit } from '@angular/core';
import { Router} from '@angular/router'
import { Comment } from 'comment entity path'
import {CommentService} from 'comment service path'
import { Observable } from 'rxjs/Observable';
@Component({
    template: ` <ul><li *ngFor="let comment of comments|async"> {{comment.Name}}</li></ul>`
})
export class CommentComponent implements OnInit {
    comments: Observable<comment[]>;  

    constructor(private router: Router, private commentService: CommentService) {
    }

    ngOnInit() {
        this.comments = this.getComments();
    }

    getComments() {
        return this.commentService.getComments();
    }

}

comment.service.ts

import { Injectable } from '@angular/core';
import { Headers, Http, Response } from '@angular/http';
import { Comment } from 'comment path here';
import { Observable } from 'rxjs/Rx';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';

@Injectable()
export class CommentService {
    private commentUrl = 'api path';  // URL to web api

    constructor(private http: Http) {
    }

    getComments(): Observable<Comment[]> {
        return this.http.get(this.commentUrl).map(
            (response) => {
                let data = response.text() ? response.json():[{}];
                if (data) {
                    console.log(data);
                    return data;
                }
                return data;
            });
        }
    }

Within ngOnInit method I am able to get list of comments but the problem is the list is not binding using ngFor on the HTML. This is because HTML is rendering before the response. But On refreshing page data binds automatically. Am I missing something?

Foretoken answered 16/3, 2017 at 3:23 Comment(2)
try to format your code block, so others will be easy to understand your question.Calypso
Thanks for suggestion, I have updated code block.Foretoken
E
0

Try this
template: <ul><li *ngFor="let comment of comments|async"> {{comment.Name}}</li></ul>

comments: Observable<comment[]>;  
    ngOnInit() {      
       this.comments = this.getComments();
    }

    getComments() {      
     return this.commentService.getComments();   
   }

I see 2 problems in your code 1. You call map without returning any value. 2. You try to set values inside map instead of subscribe but the values was undefined once it reach subscribe in your ngOnInit

Evyn answered 16/3, 2017 at 4:2 Comment(2)
I have updated my code as you suggest. but still stuck in the same issue.Foretoken
Why is this the accepted this answer if it did not resolve the issue?Testate
E
4

You can use ChangeDetectorRef class to forcefully detect changes for a component and it's child components. You will need to create class property of type ChangeDetectorRef like following:

private cdr: ChangeDetectorRef

After you load data in OnInit(), just call detectChanges() method to manually run change detection:

this.cdr.detectChanges();

I am having same issue with angular2, calling API from OnInit/AfterViewInit and bindings are not updated in view (select dropdown array not populated inside view). Above approach worked for me, but root cause is still unknown to me.

Please direct me to the root cause for this issue as I am unable to find one.

Euphemia answered 10/5, 2017 at 12:46 Comment(2)
It's 2020 now. It's still a good work arround but it would be great to to get a real fix.Dippy
This is the only answer that worked for me. It is really weird that it is now 2021 and this is still broken in Angular. It should just work.Testate
E
2

One solution would be to only display your ul once your comments have been loaded, which would then force a refresh. So something like:

<ul *ngIf="comments"><li *ngFor="let comment of comments">{{comment.Name}}</li></ul>

So once comments has been loaded, Angular will force a refresh, and the ul will be added to the DOM, at which point it will have all the data it needs to bind an li per comment.

Erasmoerasmus answered 16/3, 2017 at 3:37 Comment(1)
@Foretoken this will only work if comments is undefined until your observable has completed. If you initialise it as an empty array then you may want to try *ngIf="comments.length > 0"Erasmoerasmus
C
1

use async pipe for loading data by observable.

<li *ngFor="let comment of comments | async">
Calypso answered 16/3, 2017 at 3:39 Comment(0)
A
0

Try adding the ngFor in the ul tag

<ul  *ngFor="let comment of comments"><li > {{comment.Name}}</li></ul>`

or

<ul  *ngFor="let comment of (comments|async)"> <li> {{comment.Name}}</li></ul>`

also maybe you have not pasted your entire code, i don't see a injection of the service component.

you will need to import and inject the service in the component that you want to use it in. In the component constructor

    import {CommentService} from '../path to service'

    constructor(private commentService: CommentService) 
    {

    //you can call the getcomments service here itself or in ngOnInit
    this.commentService.getComments().subscribe(data =>    
                 {    
                   console.log(data)
                  }
    } //end constructor

Hopefully you have tested that your commentService is returning data via console.log

Aylmar answered 16/3, 2017 at 3:44 Comment(3)
I didn't post all the code. I have already imported CommentService on the component. and use @Injectable() decorator on the service all working fine. even i don't have any console error.Foretoken
did you try adding the code to call the comments service in the constructorAylmar
yes I tried but comments service in the constructor didn't worked @vinayForetoken
E
0

Try this
template: <ul><li *ngFor="let comment of comments|async"> {{comment.Name}}</li></ul>

comments: Observable<comment[]>;  
    ngOnInit() {      
       this.comments = this.getComments();
    }

    getComments() {      
     return this.commentService.getComments();   
   }

I see 2 problems in your code 1. You call map without returning any value. 2. You try to set values inside map instead of subscribe but the values was undefined once it reach subscribe in your ngOnInit

Evyn answered 16/3, 2017 at 4:2 Comment(2)
I have updated my code as you suggest. but still stuck in the same issue.Foretoken
Why is this the accepted this answer if it did not resolve the issue?Testate
F
0
 I have found a solution of my issue using Zone and promise.
 below is the update code of the "comments.component.ts".
 by using zone.run(), I am able to bind data with HTML when
 "comments.component" is loaded.    

Is this a correct way to bind data with HTML if data is coming using api?

    import { Component, OnInit, NgZone } from '@angular/core';  //NgZone *
    import { Router} from '@angular/router'
    import { Comment } from 'comment entity path'
    import {CommentService} from 'comment service path'
    import { Observable } from 'rxjs/Observable';
    import 'rxjs/add/operator/toPromise'   // *
    @Component({
        template: `<ul  *ngIf="comments && comments.length > 0">
                   <li *ngFor="let item of comments"> {{item.Name}}</li>
                   </ul>`
    })
    export class CommentComponent implements OnInit {
        comments: comment[]=[];   // *

        constructor(private router: Router, private commentService: CommentService, private zone: NgZone) {
        }

        ngOnInit() {

          this.zone.run(() => {
            this.getComments().toPromise().then((data) => {
                this.comments= data || [];
            });
            })
         }

        getComments() {
            return this.commentService.getComments();
        }

    }
Foretoken answered 16/3, 2017 at 15:10 Comment(0)
C
0

Tt's 2022 now, and the issue is still in angular 6. There is another work around is use setTimeout function which will get a dirty check.

ngOnInit() {
   setTimeout(() => {
        this.comments = this.getComments();
   }, 1000);       
}
Conquer answered 17/1, 2022 at 16:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.