3

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 2

1

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>
Sign up to request clarification or add additional context in comments.

Comments

0

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

1 Comment

Thanks for your prompt answer. I will give it a try, but I wanted to avoid to use the build-in angular animaton feature for this simple task.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.