Reputation: 99
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.
Upvotes: 1
Views: 228
Reputation: 21638
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) {
}
}
Upvotes: 0
Reputation: 29325
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.
Upvotes: 2
Reputation: 4864
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>
Upvotes: 0