Igor Simic
1 year ago

Angular animation - how to animate single item from ngFor loop


How to animate single item from ngFor loop of object items? Let's say we are looping list of users, and then we will perform some action in UI on specific user item. We want to start animation triggered by action, to display the user that something happened with that user item.
And by the way we will include also list animation, every item from user object will be animated on page load (thank me)




Animate single item from ngFor loop



In this example we will be using material design, but i will not explain how to include it, more info about it you can find here 

So let's start, first we will create user list, open app.components.ts and add object:
import { Component, OnInit } from '@angular/core';
import {listAnimation, actionAnimation} from './animations';

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.css' ],
 
})

export class AppComponent  {
  name = 'Angular: Animate single element from loop';

  users = [
    { 
     
      name: 'Vincent Vega',
      occupation: 'Sceptic',
      membership: 'Admin',
      status: 'Active',
      created_at: '1994-11-03',
      url:'https://www.coditty.com/code/angular-animation-how-to-animate-single-item-from-ngfor-loop?utm_source=Stackblitz&utm_medium=Code&utm_campaign=Animation_List_and_Items'

    },
    { 
      
      name: 'Jules Winnfield',
      occupation: 'Earth walker',
      membership: 'User',
      status: 'Blocked',
      created_at: '1994-11-03',
      url:'https://www.coditty.com/code/angular-animation-how-to-animate-single-item-from-ngfor-loop?utm_source=Stackblitz&utm_medium=Code&utm_campaign=Animation_List_and_Items'

    },
    {
  
      name: 'Mia Wallace',
      occupation: 'Housewife',
      membership: 'User',
      status: 'Active',
      created_at: '1994-11-03',
      url:'https://www.coditty.com/code/angular-animation-how-to-animate-single-item-from-ngfor-loop?utm_source=Stackblitz&utm_medium=Code&utm_campaign=Animation_List_and_Items'

    },
    { 
       
      name: 'Butch Coolidge',
      occupation: 'Boxer',
      membership: 'Admin',
      status: 'Active',
      created_at: '1994-11-03',
      url:'https://www.coditty.com/code/angular-animation-how-to-animate-single-item-from-ngfor-loop?utm_source=Stackblitz&utm_medium=Code&utm_campaign=Animation_List_and_Items'

    },
    { 
   
      name: 'Winston Wolf',
      occupation: 'Problem solver',
      membership: 'Admin',
      status: 'Active',
      created_at: '1994-11-03',
      url:'https://www.coditty.com/code/angular-animation-how-to-animate-single-item-from-ngfor-loop?utm_source=Stackblitz&utm_medium=Code&utm_campaign=Animation_List_and_Items'

    }

  ];

Next, create HTML by using material card and loop over the user object. Open app.component.html and paste this code:
<div fxLayout="row>" >
  <div
      *ngFor="let user of users"
      fxFlex="300px"
      class="padding-10">
        <mat-card style="margin:10px;">
            <div fxLayout="row" fxLayoutAlign="space-between center">
                <mat-icon class="text-muted" style="transform:scale(2); margin-right:15px;margin-top:-10px">face</mat-icon>
                <div fxFlex="80">
                  <p class="mat-title">{{user.name}}</p>
                </div>
                <div fxFlex>
                  <button mat-icon-button [matMenuTriggerFor]="userMenu" class="" style="transform:scale(1);margin-top: -10px !important;">
                        <mat-icon class="">more_vert</mat-icon>
                    </button>
                    <mat-menu #userMenu="matMenu">
                     
                        <button mat-menu-item *ngIf="user.membership == 'User'" (click)="setUserRole('Admin',user)">Set as Admin</button>
                        <button mat-menu-item *ngIf="user.membership == 'Admin'" (click)="setUserRole('User',user)">Set as User</button>
                        <button mat-menu-item *ngIf="user.status == 'Active'" (click)="setUserStatus('Blocked',user)">Block</button>
                        <button mat-menu-item *ngIf="user.status == 'Blocked'" (click)="setUserStatus('Active',user)">Activate</button>

                    </mat-menu>
                </div>
                
            </div>
            <mat-divider></mat-divider>
            <br>
            <mat-card-content>
              <div fxLayout="row" fxLayoutAlign="start center">
                <mat-icon class="text-muted">supervisor_account</mat-icon>
                <p style="margin-left:5px">Role: {{user.membership}}</p>
              </div>

              <div fxLayout="row" fxLayoutAlign="start center">
                <mat-icon class="text-muted">how_to_reg</mat-icon>
                <p style="margin-left:5px">Status: {{user.status}}</p>
              </div>

              <div fxLayout="row" fxLayoutAlign="start center">
                <mat-icon class="text-muted">date_range</mat-icon>
                <p style="margin-left:5px">Since: {{user.created_at | date}}</p>
              </div>

              <div fxLayout="row" fxLayoutAlign="start center">
                <mat-icon class="text-muted">link</mat-icon>
                <p style="margin-left:5px"><a href="{{user.url}}"  target="_blank">https://www.coditty.com</a></p>
              </div>



            </mat-card-content>
            <mat-card-actions align="start">
              
            </mat-card-actions>
            <mat-divider></mat-divider>
            <mat-card-footer style="padding:10px;" class="mat-body">
                {{user.occupation}}
            </mat-card-footer>
        </mat-card>
  </div>
</div>

As you can see we are using functions to change the role and status: 
setUserRole() and setUserStatus()
let's add them to app.component.ts
setUserRole(status,user){

    // here we are making timeout to simulate request to backend
    setTimeout(() => {

        // change value
        user.membership = status;
 
    },300);

  }

  setUserStatus(status,user){
    
    // here we are making timeout to simulate request to backend
    setTimeout(() => {

        // change value
        user.status = status;
 

    },300);

  }
Ok, now let's add some animations. Create animations.ts and place it in root folder. 
import {
  animate,
  animateChild,
  group,
  query,
  stagger,
  style,
  state,
  transition,
  trigger
} from "@angular/animations";

export const listAnimation = trigger("listAnimation", [
  transition("* => *", [
    // each time the binding value changes
    query(":leave", [stagger(500, [animate("0.5s", style({ opacity: 0 }))])], {
      optional: true
    }),
    query(
      ":enter",
      [
        style({ opacity: 0 }),
        stagger(500, [animate("0.5s", style({ opacity: 1 }))])
      ],
      { optional: true }
    )
  ])
]);

export const actionAnimation = trigger("actionAnimation", [
  state(
    "orig",
    style({
      transform: "scale(1)",
      opacity: 1
    })
  ),
  state(
    "small",
    style({
      transform: "scale(0.75)",
      opacity: 0.3
    })
  ),
  transition("* => *", animate("500ms ease-in-out"))
]);


Now, include animations.ts it in app.components.ts:
import { Component, OnInit } from '@angular/core';
import {listAnimation, actionAnimation} from './animations';

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.css' ],
  animations: [
        listAnimation,
        actionAnimation
    ],
})
...

Here we have two animations, actionAnimation and listAnimation. ListAnimation will be triggered on page load and actionAnimation will be triggered on action change. So, let's add triggers to HTML. Open app.component.html and add it:
<div fxLayout="row>" [@listAnimation]="users.length">
  <div
      *ngFor="let user of users"
      fxFlex="300px"
      class="padding-10"[@actionAnimation]="user.state" >
...

Action animation have two states "orig" and "small" by default  we will set all user to have state:'orig', we can do this on onInit() method in app.cmponents.ts:
ngOnInit(){
      this.users = this.users.map((user) => {
                  user.state = 'orig';
                  return user;
              });
  } 

Now, we will just change the state on action call, so in methods setUserStatus and setUserRole we will change the user status for that specific item. These two methods now will look like this:
setUserRole(status,user){

    /*
      NOTE: here is important to not update this.users because then we can see flickering, but to update user item passed in function and grabbed from ngFor loop    
    */ 

    // set animation state
    user.state = 'small';

    // here we are making timeout to simulate request to backend
    setTimeout(() => {

        // change value
        user.membership = status;

        // back to original state
        user.state = 'orig';

    },300);

  }

  setUserStatus(status,user){
    // set animation state
    user.state = 'small';

    // here we are making timeout to simulate request to backend
    setTimeout(() => {

        // change value
        user.status = status;

        // back to original state
        user.state = 'orig';

    },300);

  }

And that should be it, you can take a look at finished example.
Please click on menu to change user role or status: