Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to listen for the end of a css animaton with Angular2 and Reactive

I want to manually perform page transitions in my Angular2 app. So what I have done so far is produced a service that I call from a Component that deals with navigation. When you click on some link, or icon or whatever I call the AnimationService goTo method. This receives a URL and pushes to an Observable stream / subject. Here is the service so far:

@Injectable()
export class AnimationService {
    // should you be a directive?
    animationStream: Subject<string> = new Subject<string>();
    animationStreamEnd:Subject<string> = new Subject<string>()

    constructor() {

        // this is to listen for when it is time to navigate to whereever? 
        this.animationStreamEnd.subscribe(resp => {
            // redirect the url / app here
            this.router.go(resp);
        });

    }

    goTo(url:string): void {
        // so, broadcast down to trigger the css animation...
        this.animationStream.next(url);
    }

}

the content that I wish to animate has a ngClass condition in it's HTML template like so [ngClass]="{'animate-left': applyTransitionClass}", it looks something like this

<div class="gridClass" [ngClass]="{'animate-left': applyTransitionClass}">
    <div class="gridRow">
        <route-view></route-view>
        </div>
    </div>
</div>

and in it's component I have the following code

export class AnotherComponent {

    constructor(private animationService: AnimationService) {
            public applyTransitionClass: boolean = false;

        this.animationService.animationStream.subscribe(resp => {
            // resp is a url...
            // apply a css class... when that is done we need to broadcast the thing is done...
            this.applyTransitionClass = true;
            // here I want to listen for when the animation end (it is only 1 second) and tell the animationService to now navigate to wherever
            this.animationService.animationStreamEnd.next(resp);
        });
    }

You can see where I subscribe to the Observable and how I change the value that triggers the css animation. Now within the Component I wish to listen for the end of the css class I have just triggered then let the AnimationService know this has happened by pushing to the animationStreamEnd. However I don't know how to get hold of the animation end event (like "transitionend","oTransitionEnd","transitionend","webkitTransitionEnd"). I know I could just set a 1 second delay but I want this to use Observables.

I know I haven't explained myself well, I will amend the question should I need to clarify the requirement. Thanks in advance.

like image 697
Mark Sandman Avatar asked Jul 14 '16 14:07

Mark Sandman


4 Answers

component-view:

<div (transitionend)="transitionEnd($event)">
  ...
</div>

component-code:

  transitionEnd(e: Event){
    alert('no way, that easy?');
  }
like image 188
Martin Cremer Avatar answered Oct 23 '22 17:10

Martin Cremer


This is how I am doing it:

        //this.isIn = false;
    this.renderer.invokeElementMethod(this.elementRef.nativeElement, 'addEventListener', ['animationend', (e) => {
        console.log(e);
    }]);
    this.renderer.invokeElementMethod(this.elementRef.nativeElement, 'addEventListener', ['transitionend', (e) => {
        console.log(e);
    }]);
like image 34
VRPF Avatar answered Oct 23 '22 16:10

VRPF


The Angular2 animation module got callbacks implemented just before 2.0.0 final.

https://angular.io/docs/ts/latest/guide/animations.html#!#animation-callbacks

template: `
  <ul>
    <li *ngFor="let hero of heroes"
        (@flyInOut.start)="animationStarted($event)"
        (@flyInOut.done)="animationDone($event)"
        [@flyInOut]="'in'">
      {{hero.name}}
    </li>
  </ul>
`,
like image 2
Günter Zöchbauer Avatar answered Oct 23 '22 16:10

Günter Zöchbauer


You can now use the @HostListener decorator to listen to events on the host element.

For your case we can listen to the transitionstart and transitionend events:

@Component({
  selector: 'app-hello-world',
  template: 'Hello world',
})
export class HelloWorldComponent {

  @HostListener('transitionstart', ['$event'])
  onTransitionStart(event) {
      console.log('Transition started:', event);
  }

  @HostListener('transitionend', ['$event'])
  onTransitionEnd(event) {
      console.log('Transition ended:', event);
  }

}

Note that injecting the event (second parameter) is optional.

Using RXJS

The same can be achieved using RXJS by listenening to events on a target element, like so:

const element = this.elementRef.nativeElement;

fromEvent(element, 'transitionstart').subscribe((event) => {
    console.log('Started transition:', event);
});

Here is a cool little abstraction to merge multiple events:

merge(
  fromEvent(element, 'transitionstart').pipe(mapTo('start')),
  fromEvent(element, 'transitionend').pipe(mapTo('end'))
).subscribe(phase => {
  console.log('Transition phase:', phase);
});

Don't forget to unsubscribe when you're done, we don't want memory leaks.

like image 1
JasonK Avatar answered Oct 23 '22 15:10

JasonK