docs: Refresh content on routable animations for router guide (#20023)

PR Close #20023
This commit is contained in:
Brandon Roberts 2017-10-09 22:21:04 -05:00 committed by Kara Erickson
parent 15a2b8f622
commit 3c8aa0b301
14 changed files with 133 additions and 105 deletions

View File

@ -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) {

View File

@ -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()),
])
]);

View File

@ -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: `
<h1>Angular Router</h1>
<nav>
<a routerLink="/crisis-center" routerLinkActive="active">Crisis Center</a>
<a routerLink="/heroes" routerLinkActive="active">Heroes</a>
</nav>
<router-outlet></router-outlet>
`
<div [@routeAnimation]="getAnimationData(routerOutlet)">
<router-outlet #routerOutlet="outlet"></router-outlet>
</div>
`,
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

View File

@ -14,7 +14,9 @@ import { Component } from '@angular/core';
// #enddocregion contact-link
</nav>
// #docregion outlets
<router-outlet></router-outlet>
<div [@routeAnimation]="getAnimationData(routerOutlet)">
<router-outlet #routerOutlet="outlet"></router-outlet>
</div>
<router-outlet name="popup"></router-outlet>
// #enddocregion outlets
`

View File

@ -12,7 +12,9 @@ import { Component } from '@angular/core';
<a routerLink="/admin" routerLinkActive="active">Admin</a>
<a [routerLink]="[{ outlets: { popup: ['compose'] } }]">Contact</a>
</nav>
<router-outlet></router-outlet>
<div [@routeAnimation]="getAnimationData(routerOutlet)">
<router-outlet #routerOutlet="outlet"></router-outlet>
</div>
<router-outlet name="popup"></router-outlet>
`
// #enddocregion template

View File

@ -14,7 +14,9 @@ import { Component } from '@angular/core';
<a routerLink="/login" routerLinkActive="active">Login</a>
<a [routerLink]="[{ outlets: { popup: ['compose'] } }]">Contact</a>
</nav>
<router-outlet></router-outlet>
<div [@routeAnimation]="getAnimationData(routerOutlet)">
<router-outlet #routerOutlet="outlet"></router-outlet>
</div>
<router-outlet name="popup"></router-outlet>
`
// #enddocregion template

View File

@ -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';
<a routerLink="/login" routerLinkActive="active">Login</a>
<a [routerLink]="[{ outlets: { popup: ['compose'] } }]">Contact</a>
</nav>
<router-outlet></router-outlet>
<div [@routeAnimation]="getAnimationData(routerOutlet)">
<router-outlet #routerOutlet="outlet"></router-outlet>
</div>
<router-outlet name="popup"></router-outlet>
`
`,
animations: [ slideInAnimation ]
// #enddocregion template
})
export class AppComponent {
getAnimationData(outlet: RouterOutlet) {
return outlet && outlet.activatedRouteData && outlet.activatedRouteData['animation'];
}
}

View File

@ -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;

View File

@ -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';
</p>
</div>
`,
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;

View File

@ -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';
</p>
</div>
`,
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;

View File

@ -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';
<button (click)="gotoHeroes(hero)">Back</button>
</p>
</div>
`,
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<Hero>;
// #docregion ctor

View File

@ -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

View File

@ -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({

View File

@ -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`:
</code-example>
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.
<code-example path="router/src/app/heroes/heroes-routing.module.2.ts" title="src/app/heroes/heroes-routing.module.ts (animation data)">
</code-example>
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
</code-example>
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.
<code-example path="router/src/app/heroes/hero-detail.component.ts" linenums="false" title="src/app/heroes/hero-detail.component.ts (host bindings)" region="host-bindings">
<code-example path="router/src/app/app.component.2.ts" linenums="false" title="src/app/app.component.ts (animation imports)" region="animation-imports">
</code-example>
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.
<code-example path="router/src/app/app.component.2.ts" linenums="false" title="src/app/app.component.ts (router outlet)" region="template">
The `HeroDetailComponent` will ease in from the left when routed to and will slide down when navigating away.
</code-example>
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`.
<div class="alert is-helpful">
<code-example path="router/src/app/app.component.2.ts" linenums="false" title="src/app/app.component.ts (router outlet)" region="function-binding">
</code-example>
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_.
</div>
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.
<code-tabs>
<code-pane title="app.component.ts" path="router/src/app/app.component.1.ts">
<code-pane title="app.component.ts" path="router/src/app/app.component.2.ts">
</code-pane>
@ -2383,7 +2374,7 @@ Here are the relevant files for this version of the sample application.
</code-pane>
<code-pane title="heroes-routing.module.ts" path="router/src/app/heroes/heroes-routing.module.1.ts">
<code-pane title="heroes-routing.module.ts" path="router/src/app/heroes/heroes-routing.module.2.ts">
</code-pane>