Rasmus Hansen
Rasmus Hansen

Reputation: 1573

How to make Angular animations wait for rendering to finish

I'm trying to animate some route changes in Angular. The animation should height animate between nothing and the height of the content. This works very well when the content is known ahead of time, see this plunkr: http://plnkr.co/edit/FOZSZOJaSpLbrs2vl6Ka

It does however not work very well, the moment an http request has to happen. See this plunkr: http://plnkr.co/edit/vyobBtb5KDO7nkYLLsto (Here the http request has been simulated by using a delayed rx operation)

I would be perfectly fine with the animation not starting until content has been loaded, but i doubt that is possible.

What i assume is going wrong, is that Angular is measuring the height of the "twoComponent" right when it's inserted, but before it has finished loading, which then causes this.

Overall not correctly working code:

//our root app component
import {Component, NgModule, VERSION} from '@angular/core'
import {BrowserModule} from '@angular/platform-browser'
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import {RouterModule, Routes} from '@angular/router';
import {HashLocationStrategy} from '@angular/common';
import {animate, animateChild, query, style, transition, trigger} from '@angular/animations';
import {Observable} from 'rxjs/Observable';
import {of} from 'rxjs/observable/of';
import 'rxjs/add/operator/delay';

@Component({
  selector: 'my-app',
  template: `
    <div>
      <h2>Hello {{name}}</h2>
      <a routerLink="">Go to one</a>
      <a routerLink="two">Go to two</a>
      <div [@routeAnimations]="prepareRoute(outlet)" 
            style="background-color:pink"
            id="main-router-outlet">
        <router-outlet #outlet="outlet"></router-outlet>
      </div>
    </div>
  `,
  animations: [
    trigger('routeAnimations', [
      transition('* => *', [
        query(':enter', [
          style({height: 0, opacity: 0, width: 0}),
        ], {optional: true}),
        query(':leave', [
          style({height: '*', width: '*'}),
          animate('200ms', style({opacity: 0})),
          animate('500ms', style({height: 0, width: 0})),
        ], {optional: true}),
        query(':enter', [
          animate('500ms', style({height: '*', width: '*'})),
          animate('500ms', style({opacity: 1})),
          animateChild(),
        ], {optional: true}),
      ]),
    ]),
  ],
  styles: [
    `#main-router-outlet ::ng-deep > * {
        display: block;
      } `
    ]
})
export class App {
  name:string;
  constructor() {
    this.name = `Angular! v${VERSION.full}`
  }

  public prepareRoute(outlet: RouterOutlet) {
    return outlet.activatedRouteData['animation'] || '';
  }
}

@Component({
  template: `one component<br>more`
})
export class oneComponent {

}


@Component({
  template: `two component
  <div *ngFor="let s of dynamicContent|async">
  {{s}}
  </div>
  `
})
export class twoComponent {
  public dynamicContent: Observable<string[]>;

  ngOnInit() {
    this.dynamicContent = of(['foo', 'bar', 'baz'])
      .delay(200);
  }
}

@NgModule({
  imports: [ 
    BrowserModule, 
    BrowserAnimationsModule,
    RouterModule.forRoot([{
      path: '',
      component: oneComponent,
    data: {
      animation: 'one',
    }
    },
    {
      path: 'two',
      component: twoComponent,
    data: {
      animation: 'two',
    }
    }],  { useClass: HashLocationStrategy })
    ],
  declarations: [ App, oneComponent, twoComponent ],
  bootstrap: [ App ]
})
export class AppModule {}

Upvotes: 2

Views: 4622

Answers (1)

Michael Kang
Michael Kang

Reputation: 52867

Use a Resolver: Demo

Resolver

import { Injectable } from '@angular/core';
import { Resolve, ActivatedRouteSnapshot,ActivatedRoute } from '@angular/router';
import { ContactsService } from './contacts.service';

@Injectable()
export class ArrResolve implements Resolve<string[]> {

  constructor() {}

  resolve(route: ActivatedRouteSnapshot) {
    return  of(['foo', 'bar', 'baz'])
      .delay(200);
  }
}

AppModule

@NgModule({
  imports: [ 
    BrowserModule, 
    BrowserAnimationsModule,
    RouterModule.forRoot([{
      path: '',
      component: oneComponent,
    data: {
      animation: 'one',
    }
    },
    {
      path: 'two',
      component: twoComponent,
    data: {
      animation: 'two',
    },
    resolve: {
      arr: ArrResolve  
    }
    }],  { useClass: HashLocationStrategy })
    ],
  declarations: [ App, oneComponent, twoComponent ],
  bootstrap: [ App ],
  providers: [ArrResolve]
})
export class AppModule {}

Two Component

@Component({
  template: `two component
  <div *ngFor="let s of dynamicContent|async">
  {{s}}
  </div>
  `
})
export class twoComponent {
  public dynamicContent: Observable<string[]>;
  constructor(private route:ActivatedRoute) {

  }

  ngOnInit() {
    this.dynamicContent = of(this.route.snapshot.data.arr);
  }
}

Upvotes: 4

Related Questions