Search code examples
angulartypescriptangular-materialangular-animations

How to avoid :enter/:leave animations on value change when using *ngFor?


I'm using angular 9 together with angular material. I've got dynamic list of strings that I wanna be able to edit - each in a separate input. I also wanna be able to add and delete them, and have some animations for that.

Issue: :enter and :leave animations fire on each value change.

Stackblitz: https://stackblitz.com/edit/angular-material-baseline2-7pmidv

So far, best solution I've came up with is to use separate object for storing values.

public list:{value:string}[] = [{value: "1"}, {value: "2"}, {value:"3"}];

instead of

public list: string[] = ["1", "2", "3"];

I was wondering if there is a better way to do it.


Solution

  • That's because how ngFor works internally. As soon as the reference of the tracked element changes ngFor will rerender it.

    As javascript does not save references to primitves on the stack, but the value directly, every value change will change the value on the stack and therefore ngFor will rerender your object.

    So, if you wrap it in an object and only change the objects values, there is no reference change and that's why your solution works.

    However to prevent this, Angular offers the trackByFn. This function allows you to define a unique identifier for your objects and if the identifier stays the same, your object does not get rerendered, even if the reference/value changes. So I think your best bet would be to use the index as unique identifier:

    Typescript:

      trackItem(index:number,item:string){
        return index;
      }
    

    HTML:

    <div  [@inOutAnimation] *ngFor="let val of list; index as i;trackBy:trackItem" class="form-field">
        <mat-form-field >
          <mat-label>At</mat-label>
          <input matInput [(ngModel)] = "list[i]">
        </mat-form-field>
        <button mat-icon-button class="add-shift-btn" (click)="removeShift(i)">
          <mat-icon aria-hidden="false" aria-label="Example home icon">delete</mat-icon>
        </button>
       </div>