'Angular: cdkVirtualFor not rendering new items
I'm building a vertically scrolling calendar. I'm getting the initial days to load, but when new days are added to the list, they aren't being rendered.
<cdk-virtual-scroll-viewport
class="demo-viewport"
[itemSize]="100"
(onContentScrolled)="handleScrollChange($event)"
>
<calendar-day
*cdkVirtualFor="let day of days; trackBy: trackByFn"
[day]="day"
></calendar-day>
</cdk-virtual-scroll-viewport>
<button (click)="goToToday()">go</button>
I have a service with a BehaviorSubject
updating the days. I know the list of days is being updated, but the change doesn't seem to be detected.
ngOnInit() {
this._daysService.days$.subscribe(days => {
this.days = days;
})
this.watchScroll();
this.handleScrollingUp();
this.handleScrollingDown();
}
For more info, the StackBlitz repo is public https://stackblitz.com/edit/material-infinite-calendar
Solution 1:[1]
I figured this out.
Originally, I was adding new days by grabbing the current value like this
let items = this.items$.value;
items.push(newItem);
this.items$.next(items)
Apparently, this is actually a mutation of the value of the BehaviorSubject
's value, therefore not creating a new array to return and not triggering change detection.
I changed it to
let items = [...this.items$.value];
items.push(newItem);
this.items$.next(items)
and all is good.
So, although the answers here are correct in that I was mutating the original array, the information I needed was calling next()
with a mutated version BehaviorSubject
's current value does not emit a new array. An emit
event does not guarantee immutability.
Solution 2:[2]
The *cdkVirtualFor
would only be updated if you update it immutably i.e. you cannot update the array after it is initialized. We use the spread operator to get what you are looking for.
Check this very simple stackblitz... here i have used 2 methods which you can try and see:
addCountryOld
method mutates the array by pushing an object to our array and hence the rendered view is not updated.addCountryNew
method uses immutability through the spread operator which results in the rendered view getting updated.
This is the code for addCountryNew:
addCountryNew(){
let newObj = {'name':'stack overflow country', "code":'SO'};
this.myList = [...this.myList, newObj];
}
Solution 3:[3]
Instead of
this.days = days;
do
this.days = [...days];
Solution 4:[4]
It can be done like this:
You can initialise another variable as observale of your behaviorsubject "days$".
in calendar-days.service.ts
public days$: BehaviorSubject<Date[]>;
public dayObs$: Observable<Date[]>
constructor() {
this.days$ = new BehaviorSubject<Date[]>(this._initialDays);
this.dayObs$ = this.days$.asObservable();
}
And then subscribe that observable in calender.component.ts inside ngOnInit like this:
this._daysService.dayObs$.subscribe(days => {
this.days = days;
})
Solution 5:[5]
I had a strange behavior with *cdkVirtualFor
where it would not update even if I set the variable immutably as described in most of the answers above.
In my scenario if would not render the items when updated from an empty array to a non-empty array.
After spending a lot of time my last restore was to wrap cdk-virtual-scroll-viewport
with an ng-container
based on the existence of items.
<ng-container *ngIf="items.length">
<cdk-virtual-scroll-viewport>
<div *cdkVirtualFor="let item of items">
...
</div>
</cdk-virtual-scroll-viewport>
</ng-container>
This way the entire container was re-initialized when items became available. This solved my issue.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Will Luce |
Solution 2 | Akber Iqbal |
Solution 3 | |
Solution 4 | |
Solution 5 |