From 6e5f8b59b369f7ca4719b8709871a2869b9c02d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matias=20Niemel=C3=A4?= Date: Tue, 18 Oct 2016 17:16:51 -0700 Subject: [PATCH] fix(animations): generate aot code for animation trigger output events (#12291) Closes #11707 Closes #12291 --- .../src/animation/animation_compiler.ts | 18 +++-- modules/@angular/compiler/src/identifiers.ts | 12 ++-- .../compiler/src/private_import_core.ts | 1 + .../src/view_compiler/event_binder.ts | 29 +++----- .../src/view_compiler/property_binder.ts | 72 ++++++++++--------- .../compiler/src/view_compiler/view_binder.ts | 4 +- .../src/view_compiler/view_compiler.ts | 3 +- .../src/animation/animation_transition.ts | 34 +++++++++ .../animation/animation_transition_event.ts | 6 +- .../@angular/core/src/core_private_export.ts | 7 +- .../core/src/linker/animation_view_context.ts | 42 +---------- tools/public_api_guard/core/index.d.ts | 4 +- 12 files changed, 116 insertions(+), 116 deletions(-) create mode 100644 modules/@angular/core/src/animation/animation_transition.ts diff --git a/modules/@angular/compiler/src/animation/animation_compiler.ts b/modules/@angular/compiler/src/animation/animation_compiler.ts index d607f19a13..0ad4501dd1 100644 --- a/modules/@angular/compiler/src/animation/animation_compiler.ts +++ b/modules/@angular/compiler/src/animation/animation_compiler.ts @@ -264,23 +264,21 @@ class _AnimationBuilder implements AnimationAstVisitor { .toStmt()])]) .toStmt()); - var transitionParams = o.literalMap([ - ['toState', _ANIMATION_NEXT_STATE_VAR], ['fromState', _ANIMATION_CURRENT_STATE_VAR], - ['totalTime', _ANIMATION_TIME_VAR] - ]); - - var transitionEvent = o.importExpr(resolveIdentifier(Identifiers.AnimationTransitionEvent)) - .instantiate([transitionParams]); - statements.push(_ANIMATION_FACTORY_VIEW_CONTEXT .callMethod( 'queueAnimation', [ _ANIMATION_FACTORY_ELEMENT_VAR, o.literal(this.animationName), - _ANIMATION_PLAYER_VAR, transitionEvent + _ANIMATION_PLAYER_VAR ]) .toStmt()); + statements.push(new o.ReturnStatement( + o.importExpr(resolveIdentifier(Identifiers.AnimationTransition)).instantiate([ + _ANIMATION_PLAYER_VAR, _ANIMATION_CURRENT_STATE_VAR, _ANIMATION_NEXT_STATE_VAR, + _ANIMATION_TIME_VAR + ]))); + return o.fn( [ new o.FnParam( @@ -290,7 +288,7 @@ class _AnimationBuilder implements AnimationAstVisitor { new o.FnParam(_ANIMATION_CURRENT_STATE_VAR.name, o.DYNAMIC_TYPE), new o.FnParam(_ANIMATION_NEXT_STATE_VAR.name, o.DYNAMIC_TYPE) ], - statements); + statements, o.importType(resolveIdentifier(Identifiers.AnimationTransition))); } build(ast: AnimationAst): AnimationEntryCompileResult { diff --git a/modules/@angular/compiler/src/identifiers.ts b/modules/@angular/compiler/src/identifiers.ts index 4965bb9cc2..5df3eecb1e 100644 --- a/modules/@angular/compiler/src/identifiers.ts +++ b/modules/@angular/compiler/src/identifiers.ts @@ -6,10 +6,10 @@ * found in the LICENSE file at https://angular.io/license */ -import {ANALYZE_FOR_ENTRY_COMPONENTS, AnimationTransitionEvent, ChangeDetectionStrategy, ChangeDetectorRef, ComponentFactory, ComponentFactoryResolver, ElementRef, Injector, LOCALE_ID as LOCALE_ID_, NgModuleFactory, QueryList, RenderComponentType, Renderer, SecurityContext, SimpleChange, TRANSLATIONS_FORMAT as TRANSLATIONS_FORMAT_, TemplateRef, ViewContainerRef, ViewEncapsulation} from '@angular/core'; +import {ANALYZE_FOR_ENTRY_COMPONENTS, ChangeDetectionStrategy, ChangeDetectorRef, ComponentFactory, ComponentFactoryResolver, ElementRef, Injector, LOCALE_ID as LOCALE_ID_, NgModuleFactory, QueryList, RenderComponentType, Renderer, SecurityContext, SimpleChange, TRANSLATIONS_FORMAT as TRANSLATIONS_FORMAT_, TemplateRef, ViewContainerRef, ViewEncapsulation} from '@angular/core'; import {CompileIdentifierMetadata, CompileTokenMetadata} from './compile_metadata'; -import {AnimationGroupPlayer, AnimationKeyframe, AnimationSequencePlayer, AnimationStyles, AppElement, AppView, ChangeDetectorStatus, CodegenComponentFactoryResolver, DebugAppView, DebugContext, EMPTY_ARRAY, EMPTY_MAP, NgModuleInjector, NoOpAnimationPlayer, StaticNodeDebugInfo, TemplateRef_, UNINITIALIZED, ValueUnwrapper, ViewType, ViewUtils, balanceAnimationKeyframes, castByValue, checkBinding, clearStyles, collectAndResolveStyles, devModeEqual, flattenNestedViewRenderNodes, interpolate, prepareFinalAnimationStyles, pureProxy1, pureProxy10, pureProxy2, pureProxy3, pureProxy4, pureProxy5, pureProxy6, pureProxy7, pureProxy8, pureProxy9, reflector, registerModuleFactory, renderStyles} from './private_import_core'; +import {AnimationGroupPlayer, AnimationKeyframe, AnimationSequencePlayer, AnimationStyles, AnimationTransition, AppElement, AppView, ChangeDetectorStatus, CodegenComponentFactoryResolver, DebugAppView, DebugContext, EMPTY_ARRAY, EMPTY_MAP, NgModuleInjector, NoOpAnimationPlayer, StaticNodeDebugInfo, TemplateRef_, UNINITIALIZED, ValueUnwrapper, ViewType, ViewUtils, balanceAnimationKeyframes, castByValue, checkBinding, clearStyles, collectAndResolveStyles, devModeEqual, flattenNestedViewRenderNodes, interpolate, prepareFinalAnimationStyles, pureProxy1, pureProxy10, pureProxy2, pureProxy3, pureProxy4, pureProxy5, pureProxy6, pureProxy7, pureProxy8, pureProxy9, reflector, registerModuleFactory, renderStyles} from './private_import_core'; import {assetUrl} from './util'; var APP_VIEW_MODULE_URL = assetUrl('core', 'linker/view'); @@ -266,10 +266,10 @@ export class Identifiers { moduleUrl: assetUrl('core', 'i18n/tokens'), runtime: TRANSLATIONS_FORMAT_ }; - static AnimationTransitionEvent: IdentifierSpec = { - name: 'AnimationTransitionEvent', - moduleUrl: assetUrl('core', 'animation/animation_transition_event'), - runtime: AnimationTransitionEvent + static AnimationTransition: IdentifierSpec = { + name: 'AnimationTransition', + moduleUrl: assetUrl('core', 'animation/animation_transition'), + runtime: AnimationTransition }; } diff --git a/modules/@angular/compiler/src/private_import_core.ts b/modules/@angular/compiler/src/private_import_core.ts index 8936e825f4..42d1486aa8 100644 --- a/modules/@angular/compiler/src/private_import_core.ts +++ b/modules/@angular/compiler/src/private_import_core.ts @@ -90,3 +90,4 @@ export const ViewMetadata: typeof r.ViewMetadata = r.ViewMetadata; export type ComponentStillLoadingError = typeof r._ComponentStillLoadingError; export const ComponentStillLoadingError: typeof r.ComponentStillLoadingError = r.ComponentStillLoadingError; +export const AnimationTransition: typeof r.AnimationTransition = r.AnimationTransition; diff --git a/modules/@angular/compiler/src/view_compiler/event_binder.ts b/modules/@angular/compiler/src/view_compiler/event_binder.ts index f045cb449e..c063057b55 100644 --- a/modules/@angular/compiler/src/view_compiler/event_binder.ts +++ b/modules/@angular/compiler/src/view_compiler/event_binder.ts @@ -40,6 +40,7 @@ export class CompileEventListener { } get methodName() { return this._methodName; } + get isAnimation() { return !!this.eventPhase; } constructor( public compileElement: CompileElement, public eventTarget: string, public eventName: string, @@ -113,21 +114,13 @@ export class CompileEventListener { disposable.set(listenExpr).toDeclStmt(o.FUNCTION_TYPE, [o.StmtModifier.Private])); } - listenToAnimation() { - var outputListener = o.THIS_EXPR.callMethod( - 'eventHandler', - [o.THIS_EXPR.prop(this._methodName).callMethod(o.BuiltinMethod.Bind, [o.THIS_EXPR])]); - - // tie the property callback method to the view animations map - var stmt = o.THIS_EXPR.prop('animationContext') - .callMethod( - 'registerOutputHandler', - [ - this.compileElement.renderNode, o.literal(this.eventName), - o.literal(this.eventPhase), outputListener - ]) - .toStmt(); - this.compileElement.view.createMethod.addStmt(stmt); + listenToAnimation(animationTransitionVar: o.ReadVarExpr): o.Statement { + const callbackMethod = this.eventPhase == 'start' ? 'onStart' : 'onDone'; + return animationTransitionVar + .callMethod( + callbackMethod, + [o.THIS_EXPR.prop(this.methodName).callMethod(o.BuiltinMethod.Bind, [o.THIS_EXPR])]) + .toStmt(); } listenToDirective(directiveInstance: o.Expression, observablePropName: string) { @@ -185,9 +178,9 @@ export function bindDirectiveOutputs( export function bindRenderOutputs(eventListeners: CompileEventListener[]) { eventListeners.forEach(listener => { - if (listener.eventPhase) { - listener.listenToAnimation(); - } else { + // the animation listeners are handled within property_binder.ts to + // allow them to be placed next to the animation factory statements + if (!listener.isAnimation) { listener.listenToRenderer(); } }); diff --git a/modules/@angular/compiler/src/view_compiler/property_binder.ts b/modules/@angular/compiler/src/view_compiler/property_binder.ts index ef34c5fdbf..8533baf79f 100644 --- a/modules/@angular/compiler/src/view_compiler/property_binder.ts +++ b/modules/@angular/compiler/src/view_compiler/property_binder.ts @@ -21,6 +21,7 @@ import {CompileElement, CompileNode} from './compile_element'; import {CompileMethod} from './compile_method'; import {CompileView} from './compile_view'; import {DetectChangesVars, ViewProperties} from './constants'; +import {CompileEventListener} from './event_binder'; import {convertCdExpressionToIr, temporaryDeclaration} from './expression_converter'; function createBindFieldExpr(exprIndex: number): o.ReadPropExpr { @@ -90,7 +91,7 @@ export function bindRenderText( function bindAndWriteToRenderer( boundProps: BoundElementPropertyAst[], context: o.Expression, compileElement: CompileElement, - isHostProp: boolean) { + isHostProp: boolean, eventListeners: CompileEventListener[]) { var view = compileElement.view; var renderNode = compileElement.renderNode; boundProps.forEach((boundProp) => { @@ -142,41 +143,45 @@ function bindAndWriteToRenderer( .toStmt()); break; case PropertyBindingType.Animation: - var animationName = boundProp.name; - var targetViewExpr: o.Expression = o.THIS_EXPR; - if (isHostProp) { - targetViewExpr = compileElement.appElement.prop('componentView'); - } - compileMethod = view.animationBindingsMethod; + const detachStmts: o.Statement[] = []; - var animationFnExpr = + const animationName = boundProp.name; + const targetViewExpr: o.Expression = + isHostProp ? compileElement.appElement.prop('componentView') : o.THIS_EXPR; + + const animationFnExpr = targetViewExpr.prop('componentType').prop('animations').key(o.literal(animationName)); // it's important to normalize the void value as `void` explicitly // so that the styles data can be obtained from the stringmap - var emptyStateValue = o.literal(EMPTY_ANIMATION_STATE); - - // void => ... - var oldRenderVar = o.variable('oldRenderVar'); - updateStmts.push(oldRenderVar.set(oldRenderValue).toDeclStmt()); - updateStmts.push(new o.IfStmt( - oldRenderVar.equals(o.importExpr(resolveIdentifier(Identifiers.UNINITIALIZED))), - [oldRenderVar.set(emptyStateValue).toStmt()])); - - // ... => void - var newRenderVar = o.variable('newRenderVar'); - updateStmts.push(newRenderVar.set(renderValue).toDeclStmt()); - updateStmts.push(new o.IfStmt( - newRenderVar.equals(o.importExpr(resolveIdentifier(Identifiers.UNINITIALIZED))), - [newRenderVar.set(emptyStateValue).toStmt()])); + const emptyStateValue = o.literal(EMPTY_ANIMATION_STATE); + const unitializedValue = o.importExpr(resolveIdentifier(Identifiers.UNINITIALIZED)); + const animationTransitionVar = o.variable('animationTransition_' + animationName); updateStmts.push( - animationFnExpr.callFn([o.THIS_EXPR, renderNode, oldRenderVar, newRenderVar]).toStmt()); + animationTransitionVar + .set(animationFnExpr.callFn([ + o.THIS_EXPR, renderNode, oldRenderValue.equals(unitializedValue) + .conditional(emptyStateValue, oldRenderValue), + renderValue.equals(unitializedValue).conditional(emptyStateValue, renderValue) + ])) + .toDeclStmt()); - view.detachMethod.addStmt( - animationFnExpr.callFn([o.THIS_EXPR, renderNode, oldRenderValue, emptyStateValue]) - .toStmt()); + detachStmts.push(animationTransitionVar + .set(animationFnExpr.callFn( + [o.THIS_EXPR, renderNode, oldRenderValue, emptyStateValue])) + .toDeclStmt()); + + eventListeners.forEach(listener => { + if (listener.isAnimation && listener.eventName === animationName) { + let animationStmt = listener.listenToAnimation(animationTransitionVar); + updateStmts.push(animationStmt); + detachStmts.push(animationStmt); + } + }); + + view.detachMethod.addStmts(detachStmts); break; } @@ -218,14 +223,17 @@ function sanitizedValue( } export function bindRenderInputs( - boundProps: BoundElementPropertyAst[], compileElement: CompileElement): void { - bindAndWriteToRenderer(boundProps, compileElement.view.componentContext, compileElement, false); + boundProps: BoundElementPropertyAst[], compileElement: CompileElement, + eventListeners: CompileEventListener[]): void { + bindAndWriteToRenderer( + boundProps, compileElement.view.componentContext, compileElement, false, eventListeners); } export function bindDirectiveHostProps( - directiveAst: DirectiveAst, directiveInstance: o.Expression, - compileElement: CompileElement): void { - bindAndWriteToRenderer(directiveAst.hostProperties, directiveInstance, compileElement, true); + directiveAst: DirectiveAst, directiveInstance: o.Expression, compileElement: CompileElement, + eventListeners: CompileEventListener[]): void { + bindAndWriteToRenderer( + directiveAst.hostProperties, directiveInstance, compileElement, true, eventListeners); } export function bindDirectiveInputs( diff --git a/modules/@angular/compiler/src/view_compiler/view_binder.ts b/modules/@angular/compiler/src/view_compiler/view_binder.ts index 5c81bbef54..3d262f6b4f 100644 --- a/modules/@angular/compiler/src/view_compiler/view_binder.ts +++ b/modules/@angular/compiler/src/view_compiler/view_binder.ts @@ -44,14 +44,14 @@ class ViewBinderVisitor implements TemplateAstVisitor { collectEventListeners(ast.outputs, ast.directives, compileElement).forEach(entry => { eventListeners.push(entry); }); - bindRenderInputs(ast.inputs, compileElement); + bindRenderInputs(ast.inputs, compileElement, eventListeners); bindRenderOutputs(eventListeners); ast.directives.forEach((directiveAst) => { var directiveInstance = compileElement.instances.get(directiveAst.directive.type.reference); bindDirectiveInputs(directiveAst, directiveInstance, compileElement); bindDirectiveDetectChangesLifecycleCallbacks(directiveAst, directiveInstance, compileElement); - bindDirectiveHostProps(directiveAst, directiveInstance, compileElement); + bindDirectiveHostProps(directiveAst, directiveInstance, compileElement, eventListeners); bindDirectiveOutputs(directiveAst, directiveInstance, eventListeners); }); templateVisitAll(this, ast.children, compileElement); diff --git a/modules/@angular/compiler/src/view_compiler/view_compiler.ts b/modules/@angular/compiler/src/view_compiler/view_compiler.ts index 2a67f7bdbd..7db0f3d5f7 100644 --- a/modules/@angular/compiler/src/view_compiler/view_compiler.ts +++ b/modules/@angular/compiler/src/view_compiler/view_compiler.ts @@ -8,7 +8,7 @@ import {Injectable} from '@angular/core'; -import {AnimationCompiler, AnimationEntryCompileResult} from '../animation/animation_compiler'; +import {AnimationEntryCompileResult} from '../animation/animation_compiler'; import {CompileDirectiveMetadata, CompilePipeMetadata} from '../compile_metadata'; import {CompilerConfig} from '../config'; import * as o from '../output/output_ast'; @@ -29,7 +29,6 @@ export class ViewCompileResult { @Injectable() export class ViewCompiler { - private _animationCompiler = new AnimationCompiler(); constructor(private _genConfig: CompilerConfig) {} compileComponent( diff --git a/modules/@angular/core/src/animation/animation_transition.ts b/modules/@angular/core/src/animation/animation_transition.ts new file mode 100644 index 0000000000..103427dd98 --- /dev/null +++ b/modules/@angular/core/src/animation/animation_transition.ts @@ -0,0 +1,34 @@ +/** + * @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 + */ +import {AnimationPlayer} from './animation_player'; +import {AnimationTransitionEvent} from './animation_transition_event'; + +export class AnimationTransition { + constructor( + private _player: AnimationPlayer, private _fromState: string, private _toState: string, + private _totalTime: number) {} + + private _createEvent(phaseName: string): AnimationTransitionEvent { + return new AnimationTransitionEvent({ + fromState: this._fromState, + toState: this._toState, + totalTime: this._totalTime, + phaseName: phaseName + }); + } + + onStart(callback: (event: AnimationTransitionEvent) => any): void { + const event = this._createEvent('start'); + this._player.onStart(() => callback(event)); + } + + onDone(callback: (event: AnimationTransitionEvent) => any): void { + const event = this._createEvent('done'); + this._player.onDone(() => callback(event)); + } +} diff --git a/modules/@angular/core/src/animation/animation_transition_event.ts b/modules/@angular/core/src/animation/animation_transition_event.ts index cb861394e1..dfc6d6dc48 100644 --- a/modules/@angular/core/src/animation/animation_transition_event.ts +++ b/modules/@angular/core/src/animation/animation_transition_event.ts @@ -41,11 +41,13 @@ export class AnimationTransitionEvent { public fromState: string; public toState: string; public totalTime: number; + public phaseName: string; - constructor({fromState, toState, - totalTime}: {fromState: string, toState: string, totalTime: number}) { + constructor({fromState, toState, totalTime, phaseName}: + {fromState: string, toState: string, totalTime: number, phaseName: string}) { this.fromState = fromState; this.toState = toState; this.totalTime = totalTime; + this.phaseName = phaseName; } } diff --git a/modules/@angular/core/src/core_private_export.ts b/modules/@angular/core/src/core_private_export.ts index 6367f7bb32..3d799708bd 100644 --- a/modules/@angular/core/src/core_private_export.ts +++ b/modules/@angular/core/src/core_private_export.ts @@ -13,6 +13,7 @@ import {AnimationPlayer as AnimationPlayer_, NoOpAnimationPlayer as NoOpAnimatio import {AnimationSequencePlayer as AnimationSequencePlayer_} from './animation/animation_sequence_player'; import * as animationUtils from './animation/animation_style_util'; import {AnimationStyles as AnimationStyles_} from './animation/animation_styles'; +import {AnimationTransition} from './animation/animation_transition'; import * as change_detection_util from './change_detection/change_detection_util'; import * as constants from './change_detection/constants'; import * as console from './console'; @@ -118,7 +119,8 @@ export var __core_private__: { FILL_STYLE_FLAG: typeof FILL_STYLE_FLAG_, _ComponentStillLoadingError?: ComponentStillLoadingError, ComponentStillLoadingError: typeof ComponentStillLoadingError, - isPromise: typeof isPromise + isPromise: typeof isPromise, + AnimationTransition: typeof AnimationTransition } = { isDefaultChangeDetectionStrategy: constants.isDefaultChangeDetectionStrategy, ChangeDetectorStatus: constants.ChangeDetectorStatus, @@ -182,5 +184,6 @@ export var __core_private__: { EMPTY_STATE: EMPTY_STATE_, FILL_STYLE_FLAG: FILL_STYLE_FLAG_, ComponentStillLoadingError: ComponentStillLoadingError, - isPromise: isPromise + isPromise: isPromise, + AnimationTransition: AnimationTransition }; diff --git a/modules/@angular/core/src/linker/animation_view_context.ts b/modules/@angular/core/src/linker/animation_view_context.ts index 56db8d5cfd..67677454e7 100644 --- a/modules/@angular/core/src/linker/animation_view_context.ts +++ b/modules/@angular/core/src/linker/animation_view_context.ts @@ -13,7 +13,6 @@ import {ViewAnimationMap} from '../animation/view_animation_map'; export class AnimationViewContext { private _players = new ViewAnimationMap(); - private _listeners = new Map(); onAllActiveAnimationsDone(callback: () => any): void { var activeAnimationPlayers = this._players.getAllPlayers(); @@ -26,19 +25,9 @@ export class AnimationViewContext { } } - queueAnimation( - element: any, animationName: string, player: AnimationPlayer, - event: AnimationTransitionEvent): void { + queueAnimation(element: any, animationName: string, player: AnimationPlayer): void { queueAnimationGlobally(player); - this._players.set(element, animationName, player); - player.onDone(() => { - // TODO: add codegen to remove the need to store these values - this._triggerOutputHandler(element, animationName, 'done', event); - this._players.remove(element, animationName); - }); - - player.onStart(() => this._triggerOutputHandler(element, animationName, 'start', event)); } cancelActiveAnimation(element: any, animationName: string, removeAllAnimations: boolean = false): @@ -52,33 +41,4 @@ export class AnimationViewContext { } } } - - registerOutputHandler( - element: any, eventName: string, eventPhase: string, eventHandler: Function): void { - var animations = this._listeners.get(element); - if (!animations) { - this._listeners.set(element, animations = []); - } - animations.push(new _AnimationOutputHandler(eventName, eventPhase, eventHandler)); - } - - private _triggerOutputHandler( - element: any, animationName: string, phase: string, event: AnimationTransitionEvent): void { - const listeners = this._listeners.get(element); - if (listeners && listeners.length) { - for (let i = 0; i < listeners.length; i++) { - 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.eventName === animationName && listener.eventPhase === phase) { - listener.handler(event); - break; - } - } - } - } -} - -class _AnimationOutputHandler { - constructor(public eventName: string, public eventPhase: string, public handler: Function) {} } diff --git a/tools/public_api_guard/core/index.d.ts b/tools/public_api_guard/core/index.d.ts index 49f6ed19fc..c06424959d 100644 --- a/tools/public_api_guard/core/index.d.ts +++ b/tools/public_api_guard/core/index.d.ts @@ -109,12 +109,14 @@ export declare class AnimationStyleMetadata extends AnimationMetadata { /** @experimental */ export declare class AnimationTransitionEvent { fromState: string; + phaseName: string; toState: string; totalTime: number; - constructor({fromState, toState, totalTime}: { + constructor({fromState, toState, totalTime, phaseName}: { fromState: string; toState: string; totalTime: number; + phaseName: string; }); }