skip to Main Content

I have a simple component like this:

<div id="countdown">
  <div id="countdown-number">{{ countDown }}</div>
  <svg>
    <circle #circle r="18" cx="20" cy="20"></circle>
  </svg>
</div>

This is a circle countdown with a number in it. I have also some css, where I animate the countdown:

svg circle {
  ... 
  animation: countdown 5s linear forwards;
}

It works. Now, I want to make the 5s variable in that way, that it is coming from the component.

For this I tried different approaches:

  1. Define a property in css and modify it, f. e.:

    this.renderer2.setProperty(
    this.circle.nativeElement,
    ‘–time’,
    ${this.time}s
    );

The initial time is showed in the component and it gets decremented, but the animation doesn’t start.

  1. I set the style on the element, f. e.:

    this.renderer2.setStyle(
    this.circle.nativeElement,
    ‘animation’,
    countdown ${this.time}s linear forwards
    );

No luck.

The 3. option would be, that I define a simple class, which contains only the animation style, f. e.:

.start-animation {
  animation: countdown 5s linear forwards;
}

And I add this to the element, f. e.:

this.renderer2.addClass(this.circle.nativeElement, 'start-animation');

This starts the animation, but the 5s are hardcoded.

So, the question is, how to solve this issue? How can I define the animation duration dinamically?

2

Answers


  1. Chosen as BEST ANSWER

    I found a simpler solution. I add a variable (as above stated):

    animation: countdown var(--time) linear forwards;
    

    Then in the template:

    <svg>
        <circle
          #circle
          r="18"
          cx="20"
          cy="20"
          style="--time: {{ time + 's' }}"
        ></circle>
      </svg>
    

  2. Different solution using state, seems like a better approach!

    import { NgStyle } from '@angular/common';
    import { Component } from '@angular/core';
    import { FormsModule } from '@angular/forms';
    import { bootstrapApplication } from '@angular/platform-browser';
    import 'zone.js';
    import { provideAnimations } from '@angular/platform-browser/animations';
    import {
      animate,
      keyframes,
      state,
      style,
      transition,
      trigger,
    } from '@angular/animations';
    
    const stylesArr = [
      style({ transform: 'scale(1)', offset: 0 }),
      animate(
        '{{countDown}}s',
        keyframes([
          style({ transform: 'scale(1)', offset: 0 }),
          style({ transform: 'scale(0.5)', offset: 1 }),
        ])
      ),
    ];
    
    @Component({
      selector: 'app-root',
      standalone: true,
      imports: [NgStyle, FormsModule],
      animations: [
        trigger('animate', [
          state('neutral', style({ transform: 'scale(1)' })),
          state('executed', style({ transform: 'scale(0.5)' })),
          transition(
            'executed => neutral',
            animate(
              '{{countDown}}s',
              keyframes([
                style({ transform: 'scale(1)', offset: 0 }),
                style({ transform: 'scale(0.5)', offset: 1 }),
              ])
            )
          ),
        ]),
      ],
      template: `
        <div id="countdown">
          <div id="countdown-number">{{ countDown }}</div>
          <svg>
            <circle #circle r="18" cx="20" cy="20" class="needed-styles" [@animate]="{
        value:state, params:{
          countDown:countDown,      
        }
      }"></circle>
          </svg>
        </div>
        <input [(ngModel)]="countDown" (ngModelChange)="change()"/>
      `,
      styles: [
        `
          .needed-styles {
              animation-timing-function: linear;
              animation-delay: 0s;
              animation-iteration-count: 1;
              animation-direction: normal;
              animation-fill-mode: forwards;
              animation-play-state: running;
              animation-timeline: auto;
              animation-range-start: normal;
              animation-range-end: normal;
          }
      `,
      ],
    })
    export class App {
      state = 'executed';
      countDown = 3;
    
      ngOnInit() {
        setTimeout(() => {
          this.state = 'neutral';
        });
      }
    
      change() {
        this.state = 'executed';
        setTimeout(() => {
          this.state = 'neutral';
        });
      }
    }
    
    bootstrapApplication(App, {
      providers: [provideAnimations()],
    });
    

    Stackblitz Demo


    This is my solution using angular animations, we can pass the input to angular animation the variable that holds the countdown duration inside the params property, Then I apply all the other static styles using a class and the dynamic property can be assigned using {{countDown}}s, by setting the transition for :increment and :decrement we can restart the animation!

    import { NgStyle } from '@angular/common';
    import { Component } from '@angular/core';
    import { FormsModule } from '@angular/forms';
    import { bootstrapApplication } from '@angular/platform-browser';
    import 'zone.js';
    import { provideAnimations } from '@angular/platform-browser/animations';
    import {
      animate,
      keyframes,
      style,
      transition,
      trigger,
    } from '@angular/animations';
    
    const stylesArr = [
      style({ transform: 'scale(1)', offset: 0 }),
      animate(
        '{{countDown}}s',
        keyframes([
          style({ transform: 'scale(1)', offset: 0 }),
          style({ transform: 'scale(0.5)', offset: 1 }),
        ])
      ),
    ];
    
    @Component({
      selector: 'app-root',
      standalone: true,
      imports: [NgStyle, FormsModule],
      animations: [
        trigger('animate', [
          transition('* => *', stylesArr),
          transition(':increment', stylesArr),
          transition(':decrement', stylesArr),
        ]),
      ],
      template: `
        <div id="countdown">
          <div id="countdown-number">{{ countDown }}</div>
          <svg>
            <circle #circle r="18" cx="20" cy="20" class="needed-styles" [@animate]="{
        value:countDown, params:{
          countDown:countDown,      
        }
      }"></circle>
          </svg>
        </div>
        <input [(ngModel)]="countDown"/>
      `,
      styles: [
        `
          .needed-styles {
              animation-timing-function: linear;
              animation-delay: 0s;
              animation-iteration-count: 1;
              animation-direction: normal;
              animation-fill-mode: forwards;
              animation-play-state: running;
              animation-timeline: auto;
              animation-range-start: normal;
              animation-range-end: normal;
          }
      `,
      ],
    })
    export class App {
      animationToggle = false;
      countDown = 3;
    }
    
    bootstrapApplication(App, {
      providers: [provideAnimations()],
    });
    

    Stackblitz Demo

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search