Search code examples
angulardependency-injectionangular-cdkangular-cdk-virtual-scroll

CDK Virtual Scrolling wrong values seen when scrolling due to components having injected properties


I have an Angular16 Application that is using Angular's CDK virtual scrolling module - https://material.angular.io/cdk/scrolling/overview.

The components being scrolled (lets say ProductComponent) have child components that use Angular token injection to access a property from its parent, one particular instance (lets say ProductNameComponent) is showing the wrong product name after scrolling.

So, I have displayed the name within the ProductComponent (on the right side) and displayed the name as a button within the ProductNameComponent (on the left side), having scrolled up and down a few times you start to notice the names start to differ.

Having dug around for a while I have verified the name displayed by the ProductComponent is correct, its just the ProductNameComponent that is wrong.

I have reproduced my issue in Stackblitz as cant share the real code, and I found it works when using Input bindings to pass the object down to the ProductNameComponent but not when using the injection approach as per the current implementation.

I was hoping I could get this working as is with the injection approach, but I've not been able to resolve this issue... so it is reproduced in Stackblitz here, if anyone can direct me that would be great.

https://angular-cdk-demo-virtual-scroll-nrnsxq.stackblitz.io

enter image description here

I initially tried triggering change detection after scrolling, but now it seems as it caches components the injection is somehow impacting what is being displayed


Solution

  • The problem is caused by changeDetection: ChangeDetectionStrategy.OnPush set on PersonNameComponent, here change detection runs only when the @Input changes or events are fired, since you are using a dependency injection of parent logic, change detection never runs! You can fix it by removing ChangeDetectionStrategy.OnPush

    demo with no change detection on PersonNameComponent

    Stackblitz Demo


    Why not use the standard method of sending props @Input why are we going for component injection, since that works great and is the easiest solution to your question!

    person component html

    <app-person-name [person]="person"></app-person-name>
    <div>{{ person.name }}</div>
    

    person-name component ts

    import { ChangeDetectionStrategy, Inject } from '@angular/core';
    import { Input } from '@angular/core';
    import { Component, OnInit } from '@angular/core';
    
    @Component({
      selector: 'app-person-name',
      templateUrl: './person-name.component.html',
      styleUrls: ['./person-name.component.css'],
      changeDetection: ChangeDetectionStrategy.OnPush,
    })
    export class PersonNameComponent implements OnInit {
      @Input() person!: { name: string };
    
      constructor() {}
    
      public ngOnInit(): void {}
    
      public logPerson(): void {
        console.log(this.person);
      }
    }
    

    Stackblitz Demo