1

My app needs to make a number of api requests (approximately 500) and display the data. It needs to display the results as the requests are completed. So far I have set it to use an async pipe with an observable.

I have tried putting ChangeDetectorRef.detectChanges() in the complete() function call. But the view wasn't affected in any way.

news.component.ts

export class NewsComponent implements OnInit {

  news: Observable<News[]>;
  temp: News[] = new Array();

  constructor(private dataService: DataService, private ref: ChangeDetectorRef) {
  }

  ngOnInit() {
    this.news = this.getData();
  }

  getData(): Observable<News[]> {
    let newsArr = new Subject<News[]>();
    this.dataService.getTopNews().subscribe(val => {
      this.temp.push(val);
      newsArr.next(this.temp);
    }, null, () => {
      newsArr.complete();
      this.ref.detectChanges();
      console.log(this.news); //print all fetched data
    });
    return newsArr.asObservable();
  }
}

news.component.html

<app-news-item *ngFor="let newsItem of news | async" [news]="newsItem">
</app-news-item>

news-item.component.ts

@Component({
  selector: 'app-news-item',
  templateUrl: './news-item.component.html',
  styleUrls: ['./news-item.component.css']
})
export class NewsItemComponent implements OnInit {
  @Input() news: Subject<News>;
  constructor() { }

  ngOnInit() {
  }

}

The view (html) is only updating once, at the beginning with just some chunks of data. The data is loading properly and complete() fires after all the data is fetched as well.

2
  • what does the data service getTopNews function do? Commented Jul 8, 2019 at 20:37
  • getTopNews function fetches all the data from remote and returns them one by one through an observable. ``` getTopNews(): Observable<News> { // news = new Subject<News>(); // forEach id fetch data from remote // news.next(Json data) // return news.asObservable(); } ```` Commented Jul 8, 2019 at 20:41

3 Answers 3

2

The reason this doesn't work is it looks like you are pushing an array into your temp array, so it's actually an array of arrays... you could fix it as simply as

this.temp.push(...val)

though, I may be misunderstanding what getTopNews actually emits (an array or a single news item)

but, I recommend trying to use some operators to do this correctly, in particular, the scan operator which accumulates data, as I'm assuming getTopNews emits multiple "chunks"

import {scan} from 'rxjs/operators';

getData(): Observable<News[]> {
    return this.dataService.getTopNews().pipe(
      scan((acc, curr) => acc.concat(curr), []) // or acc.concat([curr]) if curr is actually a single news item and not an array
      // if you're not sure if its a single or array:
      // acc.concat(Array.isArray(curr) ? curr : [curr])
    );
  }
}

this is a far simpler structure and eliminates the need for the temp variable or the inner subject. If you only want it to render once it's ALL done, then just replace scan with reduce.

There also seems to be an error in your news-item component as it seems to expect a news item subject instead of an actual news item, which is what it's getting based on the rest of the code.

Sign up to request clarification or add additional context in comments.

1 Comment

Thank you. Apologies about the botched up logic.
0

since your app makes almost 500 requests, and you render data after all requests complete i suggest following a more explicit approach such that don't use async pipe.

export class NewsComponent implements OnInit, OnDestroy {
  private unsub: Subscription;
  news:News[] = [];

  constructor(private dataService: DataService) {}

  ngOnInit() {
    this.getData();
  }

  getData(): Observable<News[]> {
    const tmp: News[] = [];
    this.unsub = this.dataService.getTopNews().subscribe(val => {
      tmp.push(...val);
    }, null, () => {
      this.news = tmp;
      console.log(this.news); //print all fetched data
    });
  }

  ngOnDestroy() {
    this.unsub.unsubscribe();
  }
}

and in your template

<app-news-item *ngFor="let newsItem of news" [news]="newsItem"></app-news-item>

1 Comment

if it's rendering data from 500 requests, you almost definitely want the async pipe for performance reasons as you definitely want on push change detection.
0

Why over complicate things? Just put the observable from the service on the component and use the async pipe to subscribe to that, it will take care of subscribing and unsubscribing..

export class NewsComponent implements OnInit {

  news = this.dataService.getTopNews();

  constructor(private dataService: DataService) {
  }
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.