From af0e84ff17ae3e231d0f702d41151c6060d51ac2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matias=20Niemel=C3=A4?= Date: Thu, 13 Oct 2016 19:58:22 -0700 Subject: [PATCH] fix(animations): generate aot code for animation trigger output events Closes #11707 Closes #12291 --- .../src/animation/animation_compiler.ts | 22 +++++---- modules/@angular/compiler/src/identifiers.ts | 12 ++--- .../compiler/src/private_import_core.ts | 1 + .../src/view_compiler/event_binder.ts | 25 ++-------- .../src/view_compiler/property_binder.ts | 47 +++++++++++++++---- .../compiler/src/view_compiler/view_binder.ts | 4 +- .../src/view_compiler/view_compiler.ts | 3 +- .../src/animation/animation_transition.ts | 43 +++++++++++++++++ .../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, 120 insertions(+), 96 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 d607f19a13c4d0..649d71510976e9 100644 --- a/modules/@angular/compiler/src/animation/animation_compiler.ts +++ b/modules/@angular/compiler/src/animation/animation_compiler.ts @@ -264,23 +264,25 @@ 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()); + + var transitionParams = o.literalMap([ + ['toState', _ANIMATION_NEXT_STATE_VAR], ['fromState', _ANIMATION_CURRENT_STATE_VAR], + ['totalTime', _ANIMATION_TIME_VAR] + ]); + + statements.push( + new o.ReturnStatement(o.importExpr(resolveIdentifier(Identifiers.AnimationTransition)) + .instantiate([_ANIMATION_PLAYER_VAR, transitionParams]))); + return o.fn( [ new o.FnParam( @@ -290,7 +292,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.DYNAMIC_TYPE); } build(ast: AnimationAst): AnimationEntryCompileResult { diff --git a/modules/@angular/compiler/src/identifiers.ts b/modules/@angular/compiler/src/identifiers.ts index 4965bb9cc2488d..5df3eecb1e03b9 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 8936e825f448a3..42d1486aa841f4 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 f045cb449e1ab9..d12a716f9b702a 100644 --- a/modules/@angular/compiler/src/view_compiler/event_binder.ts +++ b/modules/@angular/compiler/src/view_compiler/event_binder.ts @@ -10,6 +10,7 @@ import {CompileDirectiveMetadata} from '../compile_metadata'; import {isPresent} from '../facade/lang'; import {identifierToken} from '../identifiers'; import * as o from '../output/output_ast'; +import {ViewType} from '../private_import_core'; import {BoundEventAst, DirectiveAst} from '../template_parser/template_ast'; import {CompileBinding} from './compile_binding'; @@ -40,6 +41,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,23 +115,6 @@ 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); - } - listenToDirective(directiveInstance: o.Expression, observablePropName: string) { var subscription = o.variable(`subscription_${this.compileElement.view.subscriptions.length}`); this.compileElement.view.subscriptions.push(subscription); @@ -185,9 +170,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 then 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 ef34c5fdbf9b6b..9928bff59451e5 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) => { @@ -148,8 +149,11 @@ function bindAndWriteToRenderer( targetViewExpr = compileElement.appElement.prop('componentView'); } + var detachStmts: o.Statement[] = []; compileMethod = view.animationBindingsMethod; + const _ANIMATION_TRANSITION_VAR = o.variable('animationTransition_' + animationName); + var animationFnExpr = targetViewExpr.prop('componentType').prop('animations').key(o.literal(animationName)); @@ -172,12 +176,32 @@ function bindAndWriteToRenderer( [newRenderVar.set(emptyStateValue).toStmt()])); updateStmts.push( - animationFnExpr.callFn([o.THIS_EXPR, renderNode, oldRenderVar, newRenderVar]).toStmt()); + _ANIMATION_TRANSITION_VAR + .set(animationFnExpr.callFn([o.THIS_EXPR, renderNode, oldRenderVar, newRenderVar])) + .toDeclStmt()); - view.detachMethod.addStmt( - animationFnExpr.callFn([o.THIS_EXPR, renderNode, oldRenderValue, emptyStateValue]) - .toStmt()); + detachStmts.push(_ANIMATION_TRANSITION_VAR + .set(animationFnExpr.callFn( + [o.THIS_EXPR, renderNode, oldRenderValue, emptyStateValue])) + .toDeclStmt()); + + eventListeners.forEach(listener => { + if (listener.isAnimation && listener.eventName === animationName) { + let callbackMethod = listener.eventPhase == 'start' ? 'onStart' : 'onDone'; + let transitionEvent = o.variable('e'); + let outputStmt = + _ANIMATION_TRANSITION_VAR + .callMethod( + callbackMethod, + [o.THIS_EXPR.prop(listener.methodName).callMethod('bind', [o.THIS_EXPR])]) + .toStmt(); + + detachStmts.push(outputStmt); + updateStmts.push(outputStmt); + } + }); + view.detachMethod.addStmts(detachStmts); break; } @@ -218,14 +242,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 5c81bbef54f1ba..3d262f6b4fa58b 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 2a67f7bdbd1e2e..7db0f3d5f7ad5b 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 00000000000000..b3a660fbd00930 --- /dev/null +++ b/modules/@angular/core/src/animation/animation_transition.ts @@ -0,0 +1,43 @@ +/** + * @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 { + private _fromState: string; + private _toState: string; + private _totalTime: number; + + constructor( + public player: AnimationPlayer, + {fromState, toState, totalTime}: {fromState: string, toState: string, totalTime: number}) { + this._fromState = fromState; + this._toState = toState; + this._totalTime = totalTime; + } + + /** @internal */ + _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 = new AnimationTransitionEvent(this._createEvent('start')); + this.player.onStart(() => callback(event)); + } + + onDone(callback: (event: AnimationTransitionEvent) => any): void { + const event = new AnimationTransitionEvent(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 cb861394e131ac..dfc6d6dc484b17 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 6367f7bb3206f5..3d799708bd5297 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 56db8d5cfd4abf..67677454e7c6e0 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 49f6ed19fcad51..c06424959d2acc 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; }); }