refactor(animations): ensure animation input/outputs are managed within the template parser (#11782)

Closes #11782
Closes #11601
Related #11707
This commit is contained in:
Matias Niemelä
2016-09-23 16:37:04 -04:00
committed by Chuck Jazdzewski
parent cf750e17ed
commit 85489a166e
23 changed files with 325 additions and 399 deletions

View File

@ -1,10 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
export class AnimationOutput {
constructor(public name: string, public phase: string, public fullPropertyName: string) {}
}

View File

@ -9,7 +9,6 @@
import {ANY_STATE as ANY_STATE_, DEFAULT_STATE as DEFAULT_STATE_, EMPTY_STATE as EMPTY_STATE_, FILL_STYLE_FLAG as FILL_STYLE_FLAG_} from './animation/animation_constants';
import {AnimationGroupPlayer as AnimationGroupPlayer_} from './animation/animation_group_player';
import {AnimationKeyframe as AnimationKeyframe_} from './animation/animation_keyframe';
import {AnimationOutput as AnimationOutput_} from './animation/animation_output';
import {AnimationPlayer as AnimationPlayer_, NoOpAnimationPlayer as NoOpAnimationPlayer_} from './animation/animation_player';
import {AnimationSequencePlayer as AnimationSequencePlayer_} from './animation/animation_sequence_player';
import * as animationUtils from './animation/animation_style_util';
@ -115,7 +114,6 @@ export var __core_private__: {
renderStyles: typeof animationUtils.renderStyles,
collectAndResolveStyles: typeof animationUtils.collectAndResolveStyles,
AnimationStyles: typeof AnimationStyles_, _AnimationStyles?: AnimationStyles_,
AnimationOutput: typeof AnimationOutput_, _AnimationOutput?: AnimationOutput_,
ANY_STATE: typeof ANY_STATE_,
DEFAULT_STATE: typeof DEFAULT_STATE_,
EMPTY_STATE: typeof EMPTY_STATE_,
@ -183,7 +181,6 @@ export var __core_private__: {
renderStyles: animationUtils.renderStyles,
collectAndResolveStyles: animationUtils.collectAndResolveStyles,
AnimationStyles: AnimationStyles_,
AnimationOutput: AnimationOutput_,
ANY_STATE: ANY_STATE_,
DEFAULT_STATE: DEFAULT_STATE_,
EMPTY_STATE: EMPTY_STATE_,

View File

@ -7,7 +7,6 @@
*/
import {AnimationGroupPlayer} from '../animation/animation_group_player';
import {AnimationOutput} from '../animation/animation_output';
import {AnimationPlayer, NoOpAnimationPlayer} from '../animation/animation_player';
import {queueAnimation} from '../animation/animation_queue';
import {AnimationTransitionEvent} from '../animation/animation_transition_event';
@ -53,7 +52,7 @@ export abstract class AppView<T> {
public animationPlayers = new ViewAnimationMap();
private _animationListeners = new Map<any, _AnimationOutputWithHandler[]>();
private _animationListeners = new Map<any, _AnimationOutputHandler[]>();
public context: T;
@ -107,7 +106,7 @@ export abstract class AppView<T> {
let listener = listeners[i];
// we check for both the name in addition to the phase in the event
// that there may be more than one @trigger on the same element
if (listener.output.name == animationName && listener.output.phase == phase) {
if (listener.eventName === animationName && listener.eventPhase === phase) {
listener.handler(event);
break;
}
@ -115,14 +114,13 @@ export abstract class AppView<T> {
}
}
registerAnimationOutput(element: any, outputEvent: AnimationOutput, eventHandler: Function):
void {
var entry = new _AnimationOutputWithHandler(outputEvent, eventHandler);
registerAnimationOutput(
element: any, eventName: string, eventPhase: string, eventHandler: Function): void {
var animations = this._animationListeners.get(element);
if (!isPresent(animations)) {
this._animationListeners.set(element, animations = []);
}
animations.push(entry);
animations.push(new _AnimationOutputHandler(eventName, eventPhase, eventHandler));
}
create(context: T, givenProjectableNodes: Array<any|any[]>, rootSelectorOrNode: string|any):
@ -469,6 +467,6 @@ function _findLastRenderNode(node: any): any {
return lastNode;
}
class _AnimationOutputWithHandler {
constructor(public output: AnimationOutput, public handler: Function) {}
class _AnimationOutputHandler {
constructor(public eventName: string, public eventPhase: string, public handler: Function) {}
}

View File

@ -997,7 +997,7 @@ function declareTests({useJit}: {useJit: boolean}) {
<div *ngIf="exp" [@outer]="exp">
outer
<div *ngIf="exp2" [@inner]="exp">
inner
inner
< </div>
< </div>
`,
@ -1234,8 +1234,7 @@ function declareTests({useJit}: {useJit: boolean}) {
message = e.message;
}
expect(message).toMatch(
/- Couldn't find the corresponding animation trigger definition for \(@something\)/);
expect(message).toMatch(/Couldn't find an animation entry for "something"/);
});
it('should throw an error if an animation output is referenced that is not bound to as a property on the same element',
@ -1258,7 +1257,7 @@ function declareTests({useJit}: {useJit: boolean}) {
}
expect(message).toMatch(
/- Unable to listen on \(@trigger.done\) because the animation trigger \[@trigger\] isn't being used on the same element/);
/Unable to listen on \(@trigger.done\) because the animation trigger \[@trigger\] isn't being used on the same element/);
});
it('should throw an error if an unsupported animation output phase name is used', () => {
@ -1287,7 +1286,7 @@ function declareTests({useJit}: {useJit: boolean}) {
TestBed.overrideComponent(DummyIfCmp, {
set: {
template: `
<div (@trigger)="callback($event)"></div>
<div [@trigger]="exp" (@trigger)="callback($event)"></div>
`,
animations: [trigger('trigger', [transition('one => two', [animate(1000)])])]
}
@ -1319,7 +1318,7 @@ function declareTests({useJit}: {useJit: boolean}) {
}
expect(message).toMatch(
/Couldn't find the corresponding host-level animation trigger definition for \(@trigger\)/);
/Unable to listen on \(@trigger.done\) because the animation trigger \[@trigger\] isn't being used on the same element/);
});
it('should allow host and element-level animation bindings to be defined on the same tag/component',
@ -1480,11 +1479,27 @@ function declareTests({useJit}: {useJit: boolean}) {
failureMessage = e.message;
}
expect(failureMessage)
.toMatch(/Animation parsing for DummyIfCmp has failed due to the following errors:/);
expect(failureMessage).toMatch(/- Couldn't find an animation entry for status/);
expect(failureMessage).toMatch(/Template parse errors:/);
expect(failureMessage).toMatch(/Couldn't find an animation entry for "status"/);
});
it('should throw an error if an animation trigger is registered but is already in use', () => {
TestBed.overrideComponent(
DummyIfCmp, {set: {animations: [trigger('matias', []), trigger('matias', [])]}});
var failureMessage = '';
try {
const fixture = TestBed.createComponent(DummyLoadingCmp);
} catch (e) {
failureMessage = e.message;
}
expect(failureMessage).toMatch(/Animation parse errors:/);
expect(failureMessage)
.toMatch(
/The animation trigger "matias" has already been registered for the DummyIfCmp component/);
});
it('should be permitted to be registered on the host element', fakeAsync(() => {
TestBed.overrideComponent(DummyLoadingCmp, {
set: {
@ -1521,7 +1536,7 @@ function declareTests({useJit}: {useJit: boolean}) {
failureMessage = e.message;
}
expect(failureMessage).toMatch(/- Couldn't find an animation entry for loading/);
expect(failureMessage).toMatch(/Couldn't find an animation entry for "loading"/);
});
it('should retain the destination animation state styles once the animation is complete',