docs(animations): improve aio animations guides info around :enter/:leave (#44550)

improve the aio animations guide information around elements entering and
leaving, this includes, querying them, their transitions and also improvements
to the animations guide live examples

PR Close #44550
This commit is contained in:
Dario Piotrowicz 2021-12-22 19:39:20 +01:00 committed by Alex Rickabaugh
parent e872e9d3a2
commit 569a262dfb
10 changed files with 283 additions and 28 deletions

View file

@ -8,6 +8,7 @@ import * as auto from './auto.po';
import * as filterStagger from './filter-stagger.po';
import * as heroGroups from './hero-groups';
import { getLinkById, sleepFor } from './util';
import { getComponentSection, getToggleButton } from './querying.po';
describe('Animation Tests', () => {
const openCloseHref = getLinkById('open-close');
@ -17,6 +18,7 @@ describe('Animation Tests', () => {
const autoHref = getLinkById('auto');
const filterHref = getLinkById('heroes');
const heroGroupsHref = getLinkById('hero-groups');
const queryingHref = getLinkById('querying');
beforeAll(() => browser.get(''));
@ -218,7 +220,7 @@ describe('Animation Tests', () => {
describe('Hero Groups Component', () => {
beforeAll(async () => {
await heroGroupsHref.click();
await sleepFor(300);
await sleepFor(400);
});
it('should attach a flyInOut trigger to the list of items', async () => {
@ -242,4 +244,49 @@ describe('Animation Tests', () => {
await browser.wait(async () => await heroesList.count() < total, 2000);
});
});
describe('Querying Component', () => {
const queryingAnimationDuration = 2500;
beforeAll(async () => {
await queryingHref.click();
await sleepFor(queryingAnimationDuration);
});
it('should toggle the section', async () => {
const toggleButton = getToggleButton();
const section = getComponentSection();
expect(await section.isPresent()).toBe(true);
// toggling off
await toggleButton.click();
await sleepFor(queryingAnimationDuration);
expect(await section.isPresent()).toBe(false);
// toggling on
await toggleButton.click();
await sleepFor(queryingAnimationDuration);
expect(await section.isPresent()).toBe(true);
await sleepFor(queryingAnimationDuration);
});
it(`should disable the button for the animation's duration`, async () => {
const toggleButton = getToggleButton();
expect(await toggleButton.isEnabled()).toBe(true);
// toggling off
await toggleButton.click();
expect(await toggleButton.isEnabled()).toBe(false);
await sleepFor(queryingAnimationDuration);
expect(await toggleButton.isEnabled()).toBe(true);
// toggling on
await toggleButton.click();
expect(await toggleButton.isEnabled()).toBe(false);
await sleepFor(queryingAnimationDuration);
expect(await toggleButton.isEnabled()).toBe(true);
});
});
});

View file

@ -0,0 +1,16 @@
import { by } from 'protractor';
import { locate } from './util';
export function getComponent() {
return by.css('app-querying');
}
export function getToggleButton() {
const toggleButton = () => by.className('toggle');
return locate(getComponent(), toggleButton());
}
export function getComponentSection() {
const findSection = () => by.css('section');
return locate(getComponent(), findSection());
}

View file

@ -39,11 +39,11 @@ export const slideInAnimation =
]),
query(':enter', [
animate('300ms ease-out', style({ left: '0%' }))
])
]),
query('@*', animateChild())
]),
query(':enter', animateChild()),
]),
transition('* <=> FilterPage', [
transition('* <=> *', [
style({ position: 'relative' }),
query(':enter, :leave', [
style({
@ -59,13 +59,13 @@ export const slideInAnimation =
query(':leave', animateChild()),
group([
query(':leave', [
animate('200ms ease-out', style({ left: '100%' }))
animate('200ms ease-out', style({ left: '100%', opacity: 0 }))
]),
query(':enter', [
animate('300ms ease-out', style({ left: '0%' }))
])
]),
query('@*', animateChild())
]),
query(':enter', animateChild()),
])
// #enddocregion query
]);

View file

@ -1,3 +1,15 @@
nav a {
padding: .7rem;
}
h1 {
margin-bottom: .3rem;
}
form {
margin-bottom: 2rem;
}
nav {
padding-bottom: 3rem;
}

View file

@ -17,7 +17,7 @@
<a id="heroes" routerLink="/heroes" routerLinkActive="active">Filter/Stagger</a>
<a id="hero-groups" routerLink="/hero-groups" routerLinkActive="active">Hero Groups</a>
<a id="insert-remove" routerLink="/insert-remove" routerLinkActive="active">Insert/Remove</a>
<a id="querying" routerLink="/querying" routerLinkActive="active">Querying</a>
</nav>
<!-- #docregion route-animations-outlet -->

View file

@ -20,6 +20,7 @@ import { HeroListAutoComponent } from './hero-list-auto.component';
import { HomeComponent } from './home.component';
import { AboutComponent } from './about.component';
import { InsertRemoveComponent } from './insert-remove.component';
import { QueryingComponent } from './querying.component';
@NgModule({
@ -28,17 +29,61 @@ import { InsertRemoveComponent } from './insert-remove.component';
BrowserAnimationsModule,
RouterModule.forRoot([
{ path: '', pathMatch: 'full', redirectTo: '/enter-leave' },
{ path: 'open-close', component: OpenClosePageComponent },
{ path: 'status', component: StatusSliderPageComponent },
{ path: 'toggle', component: ToggleAnimationsPageComponent },
{ path: 'heroes', component: HeroListPageComponent,
data: { animation: 'FilterPage' } },
{ path: 'hero-groups', component: HeroListGroupPageComponent },
{ path: 'enter-leave', component: HeroListEnterLeavePageComponent },
{ path: 'auto', component: HeroListAutoCalcPageComponent },
{ path: 'insert-remove', component: InsertRemoveComponent},
{ path: 'home', component: HomeComponent, data: { animation: 'HomePage' } },
{ path: 'about', component: AboutComponent, data: { animation: 'AboutPage' } },
{
path: 'open-close',
component: OpenClosePageComponent,
data: { animation: 'openClosePage' }
},
{
path: 'status',
component: StatusSliderPageComponent,
data: { animation: 'statusPage' }
},
{
path: 'toggle',
component: ToggleAnimationsPageComponent,
data: { animation: 'togglePage' }
},
{
path: 'heroes',
component: HeroListPageComponent,
data: { animation: 'filterPage' }
},
{
path: 'hero-groups',
component: HeroListGroupPageComponent,
data: { animation: 'heroGroupPage' }
},
{
path: 'enter-leave',
component: HeroListEnterLeavePageComponent,
data: { animation: 'enterLeavePage' }
},
{
path: 'auto',
component: HeroListAutoCalcPageComponent,
data: { animation: 'autoPage' }
},
{
path: 'insert-remove',
component: InsertRemoveComponent,
data: { animation: 'insertRemovePage' }
},
{
path: 'querying',
component: QueryingComponent,
data: { animation: 'queryingPage' }
},
{
path: 'home',
component: HomeComponent,
data: { animation: 'HomePage' }
},
{
path: 'about',
component: AboutComponent,
data: { animation: 'AboutPage' }
},
])
],
// #enddocregion route-animation-data
@ -59,6 +104,7 @@ import { InsertRemoveComponent } from './insert-remove.component';
HeroListAutoComponent,
HomeComponent,
InsertRemoveComponent,
QueryingComponent,
AboutComponent
],
bootstrap: [AppComponent]

View file

@ -0,0 +1,31 @@
section {
border: 1px solid black;
overflow: hidden;
}
section > * {
margin: 1rem;
}
.hero {
display: flex;
align-items: center;
border-radius: 4px;
color: black;
background-color: #DDD;
}
.hero .badge {
display: inline-block;
font-size: small;
color: white;
padding: 0.5rem;
background-color: #3d5157;
margin-right: .8em;
border-radius: 4px 0 0 4px;
align-self: stretch;
}
.hero .name {
height: min-content;
}

View file

@ -0,0 +1,80 @@
import {
Component,
} from '@angular/core';
import {
trigger,
style,
animate,
transition,
group,
query,
animateChild,
keyframes
} from '@angular/animations';
import { HEROES } from './mock-heroes';
@Component({
selector: 'app-querying',
template: `
<nav>
<button class="toggle" (click)="show = !show" [disabled]="toggleDisabled">Toggle View</button>
</nav>
<section *ngIf="show" @query (@query.start)="toggleDisabled = true" (@query.done)="toggleDisabled = false">
<p>I am a simple child element</p>
<p *ngIf="show">I am a child element that enters and leaves with its parent</p>
<p @animateMe>I am a child element with an animation trigger</p>
<div class="hero">
<span class="badge">{{ hero.id }}</span>
<span class="name">{{ hero.name }} <small>(heroes are always animated!)</small></span>
</div>
</section>
`,
styleUrls: ['./querying.component.css'],
animations: [
trigger('query', [
transition(':enter', [
style({ height: 0 }),
group([
animate(500, style({ height: '*' })),
query(':enter', [
style({ opacity: 0, transform: 'scale(0)'}),
animate(2000, style({ opacity: 1, transform: 'scale(1)' }))
]),
query('.hero', [
style({ transform: 'translateX(-100%)'}),
animate('.7s 500ms ease-in', style({ transform: 'translateX(0)' }))
]),
]),
query('@animateMe', animateChild()),
]),
transition(':leave', [
style({ height: '*' }),
query('@animateMe', animateChild()),
group([
animate('500ms 500ms', style({ height: '0', padding: '0' })),
query(':leave', [
style({ opacity: 1, transform: 'scale(1)'}),
animate('1s', style({ opacity: 0, transform: 'scale(0)' }))
]),
query('.hero', [
style({ transform: 'translateX(0)'}),
animate('.7s ease-out', style({ transform: 'translateX(-100%)' }))
]),
]),
]),
]),
trigger('animateMe', [
transition('* <=> *', animate('500ms cubic-bezier(.68,-0.73,.26,1.65)', keyframes([
style({ backgroundColor: "transparent", color: '*', offset: 0 }),
style({ backgroundColor: "blue", color: 'white', offset: 0.2 }),
style({ backgroundColor: "transparent", color: '*', offset: 1 })
])))
]),
]
})
export class QueryingComponent {
toggleDisabled = false;
show = true;
hero = HEROES[0];
}

View file

@ -20,11 +20,34 @@ The functions that control complex animation sequences are:
{@a complex-sequence}
## The query() function
Most complex animations rely on the `query()` function to find child elements and apply animations to them, basic examples of such are:
- `query()` followed by `animate()`
used to query simple HTML elements and directly apply animations to them.
- `query()` followed by `animateChild()`
used to query child elements which themselves have animations metadata applied to them and trigger such animation (which would be otherwise be blocked by the current/parent element's animation)
The first argument of `query()` is a [css selector](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors) string which can also contain the following angular-specific tokens:
- `:enter`/`:leave` for entering/leaving elements
- `:animating` for elements currently animating
- `@*`/`@triggerName` for elements with any or a specific trigger
- `:self` the animating element itself
<div class="callout is-helpful">
<header>Entering and Leaving Elements</header>
Not all child elements are actually considered as entering/leaving, this can at times be counterintuitive and confusing, please see the [query api docs](/api/animations/query#entering-and-leaving-elements) for more information.
You can also see an illustration of this in the animations live example (introduced in the animations [introduction section](/guide/animations#about-this-guide)) under the Querying tab.
</div>
## Animate multiple elements using query() and stagger() functions
The `query()` function lets you find inner elements within the element that is being animated. This function targets specific HTML elements within a parent component and applies animations to each element individually. Angular intelligently handles setup, teardown, and cleanup as it coordinates the elements across the page.
The `stagger()` function lets you define a timing gap between each queried item that is animated and thus animates elements with a delay between them.
After having queried child elements via `query()`, the `stagger()` function lets you define a timing gap between each queried item that is animated and thus animates elements with a delay between them.
The following example demonstrates how to use the `query()` and `stagger()` functions to animate a list (of heroes) adding each in sequence, with a slight delay, from top to bottom.

View file

@ -75,12 +75,6 @@ Combine wildcard and void states in a transition to trigger animations that ente
This section shows how to animate elements entering or leaving a page.
<div class="alert is-helpful">
**Note:** For this example, an element entering or leaving a view is equivalent to being inserted or removed from the DOM.
</div>
Add a new behavior:
* When you add a hero to the list of heroes, it appears to fly onto the page from the left.
@ -109,6 +103,12 @@ So, use the aliases `:enter` and `:leave` to target HTML elements that are inser
The `:enter` transition runs when any `*ngIf` or `*ngFor` views are placed on the page, and `:leave` runs when those views are removed from the page.
<div class="alert is-important">
**Note:** Entering/leaving behaviors can sometime be confusing. As a rule of thumb consider that any element being added to the DOM by Angular passes via the `:enter` transition, but only elements being directly removed from the DOM by Angular pass via the `:leave` transition (e.g. an element's view is removed from the DOM because its parent is being removed from the DOM or the app's route has changed, then the element will not pass via the `:leave` transition).
</div>
This example has a special trigger for the enter and leave animation called `myInsertRemoveTrigger`. The HTML template contains the following code.
<code-example path="animations/src/app/insert-remove.component.html" header="src/app/insert-remove.component.html" region="insert-remove" language="typescript">