From c9fece997ca88579569d5e39e2f09382bfecd6b9 Mon Sep 17 00:00:00 2001 From: Brandon Roberts Date: Mon, 9 Oct 2017 22:21:04 -0500 Subject: [PATCH] docs: Refresh content on routable animations for router guide (#20023) PR Close #20023 --- .../examples/router/e2e/src/app.e2e-spec.ts | 23 +++++--- .../examples/router/src/app/animations.ts | 51 ++++++++++------- .../router/src/app/app.component.2.ts | 21 ++++++- .../router/src/app/app.component.4.ts | 4 +- .../router/src/app/app.component.5.ts | 4 +- .../router/src/app/app.component.6.ts | 4 +- .../examples/router/src/app/app.component.ts | 12 +++- .../src/app/compose-message.component.ts | 9 +-- .../crisis-detail.component.1.ts | 8 +-- .../crisis-center/crisis-detail.component.ts | 8 +-- .../src/app/heroes/hero-detail.component.ts | 11 +--- .../src/app/heroes/heroes-routing.module.2.ts | 22 +++++++ .../src/app/heroes/heroes-routing.module.ts | 4 +- aio/content/guide/router.md | 57 ++++++++----------- 14 files changed, 133 insertions(+), 105 deletions(-) create mode 100644 aio/content/examples/router/src/app/heroes/heroes-routing.module.2.ts diff --git a/aio/content/examples/router/e2e/src/app.e2e-spec.ts b/aio/content/examples/router/e2e/src/app.e2e-spec.ts index 4343806859..89adb2efab 100644 --- a/aio/content/examples/router/e2e/src/app.e2e-spec.ts +++ b/aio/content/examples/router/e2e/src/app.e2e-spec.ts @@ -13,33 +13,34 @@ describe('Router', () => { function getPageStruct() { const hrefEles = element.all(by.css('app-root > nav a')); - const crisisDetail = element.all(by.css('app-root > ng-component > ng-component > ng-component > div')).first(); - const heroDetail = element(by.css('app-root > ng-component > div')); + const crisisDetail = element.all(by.css('app-root > div > ng-component > ng-component > ng-component > div')).first(); + const heroDetail = element(by.css('app-root > div > ng-component')); return { hrefs: hrefEles, activeHref: element(by.css('app-root > nav a.active')), crisisHref: hrefEles.get(0), - crisisList: element.all(by.css('app-root > ng-component > ng-component li')), + crisisList: element.all(by.css('app-root > div > ng-component > ng-component li')), crisisDetail: crisisDetail, crisisDetailTitle: crisisDetail.element(by.xpath('*[1]')), heroesHref: hrefEles.get(1), - heroesList: element.all(by.css('app-root > ng-component li')), + heroesList: element.all(by.css('app-root > div > ng-component li')), heroDetail: heroDetail, - heroDetailTitle: heroDetail.element(by.xpath('*[1]')), + heroDetailTitle: heroDetail.element(by.xpath('*[2]')), adminHref: hrefEles.get(2), - adminPreloadList: element.all(by.css('app-root > ng-component > ng-component > ul > li')), + adminPreloadList: element.all(by.css('app-root > div > ng-component > ng-component > ul > li')), loginHref: hrefEles.get(3), - loginButton: element.all(by.css('app-root > ng-component > p > button')), + loginButton: element.all(by.css('app-root > div > ng-component > p > button')), contactHref: hrefEles.get(4), contactCancelButton: element.all(by.buttonText('Cancel')), - outletComponents: element.all(by.css('app-root > ng-component')) + primaryOutlet: element.all(by.css('app-root > div > ng-component')), + secondaryOutlet: element.all(by.css('app-root > ng-component')) }; } @@ -98,6 +99,7 @@ describe('Router', () => { it('saves changed hero details', async () => { const page = getPageStruct(); await page.heroesHref.click(); + await browser.sleep(600); const heroEle = page.heroesList.get(4); let text = await heroEle.getText(); expect(text.length).toBeGreaterThan(0, 'hero item text length'); @@ -105,6 +107,7 @@ describe('Router', () => { const heroText = text.substr(text.indexOf(' ')).trim(); await heroEle.click(); + await browser.sleep(600); expect(page.heroesList.count()).toBe(0, 'hero list count'); expect(page.heroDetail.isPresent()).toBe(true, 'hero detail'); expect(page.heroDetailTitle.getText()).toContain(heroText); @@ -114,6 +117,7 @@ describe('Router', () => { let buttonEle = page.heroDetail.element(by.css('button')); await buttonEle.click(); + await browser.sleep(600); expect(heroEle.getText()).toContain(heroText + '-foo'); }); @@ -130,7 +134,8 @@ describe('Router', () => { const page = getPageStruct(); await page.heroesHref.click(); await page.contactHref.click(); - expect(page.outletComponents.count()).toBe(2, 'route count'); + expect(page.primaryOutlet.count()).toBe(1, 'primary outlet'); + expect(page.secondaryOutlet.count()).toBe(1, 'secondary outlet'); }); async function crisisCenterEdit(index: number, save: boolean) { diff --git a/aio/content/examples/router/src/app/animations.ts b/aio/content/examples/router/src/app/animations.ts index 39a0b1840a..c1cf63f75d 100644 --- a/aio/content/examples/router/src/app/animations.ts +++ b/aio/content/examples/router/src/app/animations.ts @@ -1,26 +1,35 @@ // #docregion -import { animate, state, style, transition, trigger } from '@angular/animations'; +import { + trigger, animateChild, group, + transition, animate, style, query +} from '@angular/animations'; -// Component transition animations -export const slideInDownAnimation = + +// Routable animations +export const slideInAnimation = trigger('routeAnimation', [ - state('*', - style({ - opacity: 1, - transform: 'translateX(0)' - }) - ), - transition(':enter', [ - style({ - opacity: 0, - transform: 'translateX(-100%)' - }), - animate('0.2s ease-in') - ]), - transition(':leave', [ - animate('0.5s ease-out', style({ - opacity: 0, - transform: 'translateY(100%)' - })) + transition('heroes <=> hero', [ + style({ position: 'relative' }), + query(':enter, :leave', [ + style({ + position: 'absolute', + top: 0, + left: 0, + width: '100%' + }) + ]), + query(':enter', [ + style({ left: '-100%'}) + ]), + query(':leave', animateChild()), + group([ + query(':leave', [ + animate('300ms ease-out', style({ left: '100%'})) + ]), + query(':enter', [ + animate('300ms ease-out', style({ left: '0%'})) + ]) + ]), + query(':enter', animateChild()), ]) ]); diff --git a/aio/content/examples/router/src/app/app.component.2.ts b/aio/content/examples/router/src/app/app.component.2.ts index e705183911..2e56506a58 100644 --- a/aio/content/examples/router/src/app/app.component.2.ts +++ b/aio/content/examples/router/src/app/app.component.2.ts @@ -1,16 +1,31 @@ /* Second Heroes version */ // #docregion import { Component } from '@angular/core'; +// #docregion animation-imports +import { RouterOutlet } from '@angular/router'; +import { slideInAnimation } from './animations'; +// #enddocregion animation-imports @Component({ selector: 'app-root', + // #docregion template template: `

Angular Router

- - ` +
+ +
+ `, + animations: [ slideInAnimation ] + // #enddocregion template }) -export class AppComponent { } +// #docregion function-binding +export class AppComponent { + getAnimationData(outlet: RouterOutlet) { + return outlet && outlet.activatedRouteData && outlet.activatedRouteData['animation']; + } +} +// #enddocregion function-binding diff --git a/aio/content/examples/router/src/app/app.component.4.ts b/aio/content/examples/router/src/app/app.component.4.ts index a51b792a78..03a987ab37 100644 --- a/aio/content/examples/router/src/app/app.component.4.ts +++ b/aio/content/examples/router/src/app/app.component.4.ts @@ -14,7 +14,9 @@ import { Component } from '@angular/core'; // #enddocregion contact-link // #docregion outlets - +
+ +
// #enddocregion outlets ` diff --git a/aio/content/examples/router/src/app/app.component.5.ts b/aio/content/examples/router/src/app/app.component.5.ts index dc7ebcf58b..61b711834e 100644 --- a/aio/content/examples/router/src/app/app.component.5.ts +++ b/aio/content/examples/router/src/app/app.component.5.ts @@ -12,7 +12,9 @@ import { Component } from '@angular/core'; Admin Contact - +
+ +
` // #enddocregion template diff --git a/aio/content/examples/router/src/app/app.component.6.ts b/aio/content/examples/router/src/app/app.component.6.ts index d67ddfb728..5543507d77 100644 --- a/aio/content/examples/router/src/app/app.component.6.ts +++ b/aio/content/examples/router/src/app/app.component.6.ts @@ -14,7 +14,9 @@ import { Component } from '@angular/core'; Login Contact - +
+ +
` // #enddocregion template diff --git a/aio/content/examples/router/src/app/app.component.ts b/aio/content/examples/router/src/app/app.component.ts index 70cdf8cba0..55b4cf9ec1 100644 --- a/aio/content/examples/router/src/app/app.component.ts +++ b/aio/content/examples/router/src/app/app.component.ts @@ -1,6 +1,8 @@ // #docplaster // #docregion import { Component } from '@angular/core'; +import { RouterOutlet } from '@angular/router'; +import { slideInAnimation } from './animations'; @Component({ selector: 'app-root', @@ -14,10 +16,16 @@ import { Component } from '@angular/core'; Login Contact - +
+ +
- ` + `, + animations: [ slideInAnimation ] // #enddocregion template }) export class AppComponent { + getAnimationData(outlet: RouterOutlet) { + return outlet && outlet.activatedRouteData && outlet.activatedRouteData['animation']; + } } diff --git a/aio/content/examples/router/src/app/compose-message.component.ts b/aio/content/examples/router/src/app/compose-message.component.ts index f2d95de745..e243d21c75 100644 --- a/aio/content/examples/router/src/app/compose-message.component.ts +++ b/aio/content/examples/router/src/app/compose-message.component.ts @@ -2,18 +2,11 @@ import { Component, HostBinding } from '@angular/core'; import { Router } from '@angular/router'; -import { slideInDownAnimation } from './animations'; - @Component({ templateUrl: './compose-message.component.html', - styles: [ ':host { position: relative; bottom: 10%; }' ], - animations: [ slideInDownAnimation ] + styles: [ ':host { position: relative; bottom: 10%; }' ] }) export class ComposeMessageComponent { - @HostBinding('@routeAnimation') routeAnimation = true; - @HostBinding('style.display') display = 'block'; - @HostBinding('style.position') position = 'absolute'; - details: string; message: string; sending = false; diff --git a/aio/content/examples/router/src/app/crisis-center/crisis-detail.component.1.ts b/aio/content/examples/router/src/app/crisis-center/crisis-detail.component.1.ts index 45e8d9c95b..4ed913a18c 100644 --- a/aio/content/examples/router/src/app/crisis-center/crisis-detail.component.1.ts +++ b/aio/content/examples/router/src/app/crisis-center/crisis-detail.component.1.ts @@ -5,7 +5,6 @@ import { ActivatedRoute, Router, ParamMap } from '@angular/router'; import { Observable } from 'rxjs'; import { switchMap } from 'rxjs/operators'; -import { slideInDownAnimation } from '../animations'; import { Crisis, CrisisService } from './crisis.service'; import { DialogService } from '../dialog.service'; @@ -25,14 +24,9 @@ import { DialogService } from '../dialog.service';

`, - styles: ['input {width: 20em}'], - animations: [ slideInDownAnimation ] + styles: ['input {width: 20em}'] }) export class CrisisDetailComponent implements OnInit { - @HostBinding('@routeAnimation') routeAnimation = true; - @HostBinding('style.display') display = 'block'; - @HostBinding('style.position') position = 'absolute'; - crisis: Crisis; editName: string; diff --git a/aio/content/examples/router/src/app/crisis-center/crisis-detail.component.ts b/aio/content/examples/router/src/app/crisis-center/crisis-detail.component.ts index 2b5150686c..c3510a8df9 100644 --- a/aio/content/examples/router/src/app/crisis-center/crisis-detail.component.ts +++ b/aio/content/examples/router/src/app/crisis-center/crisis-detail.component.ts @@ -4,7 +4,6 @@ import { Component, OnInit, HostBinding } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; import { Observable } from 'rxjs'; -import { slideInDownAnimation } from '../animations'; import { Crisis } from './crisis.service'; import { DialogService } from '../dialog.service'; @@ -24,14 +23,9 @@ import { DialogService } from '../dialog.service';

`, - styles: ['input {width: 20em}'], - animations: [ slideInDownAnimation ] + styles: ['input {width: 20em}'] }) export class CrisisDetailComponent implements OnInit { - @HostBinding('@routeAnimation') routeAnimation = true; - @HostBinding('style.display') display = 'block'; - @HostBinding('style.position') position = 'absolute'; - crisis: Crisis; editName: string; diff --git a/aio/content/examples/router/src/app/heroes/hero-detail.component.ts b/aio/content/examples/router/src/app/heroes/hero-detail.component.ts index 2170686864..2ef9347dcf 100644 --- a/aio/content/examples/router/src/app/heroes/hero-detail.component.ts +++ b/aio/content/examples/router/src/app/heroes/hero-detail.component.ts @@ -7,8 +7,6 @@ import { Component, OnInit, HostBinding } from '@angular/core'; import { Router, ActivatedRoute, ParamMap } from '@angular/router'; import { Observable } from 'rxjs'; -import { slideInDownAnimation } from '../animations'; - import { Hero, HeroService } from './hero.service'; @Component({ @@ -26,16 +24,9 @@ import { Hero, HeroService } from './hero.service';

- `, - animations: [ slideInDownAnimation ] + ` }) export class HeroDetailComponent implements OnInit { -// #docregion host-bindings - @HostBinding('@routeAnimation') routeAnimation = true; - @HostBinding('style.display') display = 'block'; - @HostBinding('style.position') position = 'absolute'; -// #enddocregion host-bindings - hero$: Observable; // #docregion ctor diff --git a/aio/content/examples/router/src/app/heroes/heroes-routing.module.2.ts b/aio/content/examples/router/src/app/heroes/heroes-routing.module.2.ts new file mode 100644 index 0000000000..6f8d5fda4b --- /dev/null +++ b/aio/content/examples/router/src/app/heroes/heroes-routing.module.2.ts @@ -0,0 +1,22 @@ +// #docregion +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; + +import { HeroListComponent } from './hero-list.component'; +import { HeroDetailComponent } from './hero-detail.component'; + +const heroesRoutes: Routes = [ + { path: 'heroes', component: HeroListComponent, data: { animation: 'heroes' } }, + { path: 'hero/:id', component: HeroDetailComponent, data: { animation: 'hero' } } +]; + +@NgModule({ + imports: [ + RouterModule.forChild(heroesRoutes) + ], + exports: [ + RouterModule + ] +}) +export class HeroRoutingModule { } +// #enddocregion diff --git a/aio/content/examples/router/src/app/heroes/heroes-routing.module.ts b/aio/content/examples/router/src/app/heroes/heroes-routing.module.ts index 43558907b0..11aab5f48c 100644 --- a/aio/content/examples/router/src/app/heroes/heroes-routing.module.ts +++ b/aio/content/examples/router/src/app/heroes/heroes-routing.module.ts @@ -8,8 +8,8 @@ import { HeroDetailComponent } from './hero-detail.component'; const heroesRoutes: Routes = [ { path: 'heroes', redirectTo: '/superheroes' }, { path: 'hero/:id', redirectTo: '/superhero/:id' }, - { path: 'superheroes', component: HeroListComponent }, - { path: 'superhero/:id', component: HeroDetailComponent } + { path: 'superheroes', component: HeroListComponent, data: { animation: 'heroes' } }, + { path: 'superhero/:id', component: HeroDetailComponent, data: { animation: 'hero' } } ]; @NgModule({ diff --git a/aio/content/guide/router.md b/aio/content/guide/router.md index 0ecd1c8151..3802e1eb9f 100644 --- a/aio/content/guide/router.md +++ b/aio/content/guide/router.md @@ -2171,8 +2171,7 @@ The optional `foo` route parameter is harmless and continues to be ignored. ### Adding animations to the routed component The heroes feature module is almost complete, but what is a feature without some smooth transitions? -This section shows you how to add some [animations](guide/animations) -to the `HeroDetailComponent`. +This section shows you how to add some [animations](guide/animations) to the `HeroDetailComponent`. First import `BrowserAnimationsModule`: @@ -2180,6 +2179,11 @@ First import `BrowserAnimationsModule`: +Next, add a `data` object to the routes for `HeroListComponent` and `HeroDetailComponent`. Transitions are based on `states` and you'll use the `animation` data from the route to provide a named animation `state` for the transitions. + + + + Create an `animations.ts` file in the root `src/app/` folder. The contents look like this: @@ -2189,53 +2193,40 @@ Create an `animations.ts` file in the root `src/app/` folder. The contents look - This file does the following: * Imports the animation symbols that build the animation triggers, control state, and manage transitions between states. -* Exports a constant named `slideInDownAnimation` set to an animation trigger named *`routeAnimation`*; -animated components will refer to this name. +* Exports a constant named `slideInAnimation` set to an animation trigger named *`routeAnimation`*; -* Specifies the _wildcard state_ , `*`, that matches any animation state that the route component is in. +* Defines one *transition* when switching back and forth from the `heroes` and `hero` routes to ease the component in from the left of the screen as it enters the application view (`:enter`), the other to animate the component to the right as it leaves the application view (`:leave`). -* Defines two *transitions*, one to ease the component in from the left of the screen as it enters the application view (`:enter`), -the other to animate the component down as it leaves the application view (`:leave`). +You could also create more transitions for other routes. This trigger is sufficient for the current milestone. -You could create more triggers with different transitions for other route components. This trigger is sufficient for the current milestone. +Back in the `AppComponent`, import the `RouterOutlet` token from the `@angular/router` package and the `slideInDownAnimation` from `'./animations.ts`. - -Back in the `HeroDetailComponent`, import the `slideInDownAnimation` from `'./animations.ts`. -Add the `HostBinding` decorator to the imports from `@angular/core`; you'll need it in a moment. - -Add an `animations` array to the `@Component` metadata's that contains the `slideInDownAnimation`. - -Then add three `@HostBinding` properties to the class to set the animation and styles for the route component's element. - - + +In order to use the routable animations, you'll need to wrap the `RouterOutlet` inside an element. You'll +use the `@routeAnimation` trigger and bind it to the element. +For the `@routeAnimation` transitions to key off states, you'll need to provide it with the `data` from the `ActivatedRoute`. The `RouterOutlet` is exposed as an `outlet` template variable, so you bind a reference to the router outlet. A variable of `routerOutlet` is an ideal choice. -The `'@routeAnimation'` passed to the first `@HostBinding` matches -the name of the `slideInDownAnimation` _trigger_, `routeAnimation`. -Set the `routeAnimation` property to `true` because you only care about the `:enter` and `:leave` states. +Add an `animations` array to the `@Component` metadata's that contains the `slideInDownAnimation`. -The other two `@HostBinding` properties style the display and position of the component. + -The `HeroDetailComponent` will ease in from the left when routed to and will slide down when navigating away. + +The `@routeAnimation` property is bound to the `getAnimationData` with the provided `routerOutlet` reference, so you'll need to define that function in the `AppComponent`. The `getAnimationData` function returns the animation property from the `data` provided through the `ActivatedRoute`. The `animation` property matches the `transition` names you used in the `slideDownAnimation` defined in `animations.ts`. -
+ + - -Applying route animations to individual components works for a simple demo, but in a real life app, -it is better to animate routes based on _route paths_. - - -
+When switching between the two routes, the `HeroDetailComponent` and `HeroListComponent` will ease in from the left when routed to and will slide to the right when navigating away. @@ -2250,7 +2241,7 @@ You've learned how to do the following: * Navigate imperatively from one component to another. * Pass information along in route parameters and subscribe to them in the component. * Import the feature area NgModule into the `AppModule`. -* Apply animations to the route component. +* Applying routable animations based on the page. After these changes, the folder structure looks like this: @@ -2355,7 +2346,7 @@ Here are the relevant files for this version of the sample application. - + @@ -2383,7 +2374,7 @@ Here are the relevant files for this version of the sample application. - +