From 4bae4b3bb5d0cc61918a412c5ae596a38fab9854 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matias=20Niemel=C3=A4?= Date: Tue, 20 Dec 2016 15:57:02 -0800 Subject: [PATCH] feat(animations): expose the `element` value within transition events --- .../src/animation/animation_compiler.ts | 4 +- .../src/animation/animation_transition.ts | 9 +- .../animation/animation_transition_event.ts | 12 ++- .../animation/animation_integration_spec.ts | 31 +++++++ .../renderer_animation_integration_spec.ts | 90 +++++++++++++------ tools/public_api_guard/core/index.d.ts | 4 +- 6 files changed, 113 insertions(+), 37 deletions(-) diff --git a/modules/@angular/compiler/src/animation/animation_compiler.ts b/modules/@angular/compiler/src/animation/animation_compiler.ts index 66682d7170..6f85494842 100644 --- a/modules/@angular/compiler/src/animation/animation_compiler.ts +++ b/modules/@angular/compiler/src/animation/animation_compiler.ts @@ -294,8 +294,8 @@ class _AnimationBuilder implements AnimationAstVisitor { statements.push(new o.ReturnStatement( o.importExpr(createIdentifier(Identifiers.AnimationTransition)).instantiate([ - _ANIMATION_PLAYER_VAR, _ANIMATION_CURRENT_STATE_VAR, _ANIMATION_NEXT_STATE_VAR, - _ANIMATION_TIME_VAR + _ANIMATION_PLAYER_VAR, _ANIMATION_FACTORY_ELEMENT_VAR, _ANIMATION_CURRENT_STATE_VAR, + _ANIMATION_NEXT_STATE_VAR, _ANIMATION_TIME_VAR ]))); return o.fn( diff --git a/modules/@angular/core/src/animation/animation_transition.ts b/modules/@angular/core/src/animation/animation_transition.ts index eaea2efb2c..772f7789f1 100644 --- a/modules/@angular/core/src/animation/animation_transition.ts +++ b/modules/@angular/core/src/animation/animation_transition.ts @@ -5,20 +5,23 @@ * 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 {ElementRef} from '../linker/element_ref'; + 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 _player: AnimationPlayer, private _element: ElementRef, 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 + phaseName: phaseName, + element: this._element }); } diff --git a/modules/@angular/core/src/animation/animation_transition_event.ts b/modules/@angular/core/src/animation/animation_transition_event.ts index dfc6d6dc48..ccea8dbc5f 100644 --- a/modules/@angular/core/src/animation/animation_transition_event.ts +++ b/modules/@angular/core/src/animation/animation_transition_event.ts @@ -5,6 +5,7 @@ * 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 {ElementRef} from '../linker/element_ref'; /** * An instance of this class is returned as an event parameter when an animation @@ -42,12 +43,19 @@ export class AnimationTransitionEvent { public toState: string; public totalTime: number; public phaseName: string; + public element: ElementRef; - constructor({fromState, toState, totalTime, phaseName}: - {fromState: string, toState: string, totalTime: number, phaseName: string}) { + constructor({fromState, toState, totalTime, phaseName, element}: { + fromState: string, + toState: string, + totalTime: number, + phaseName: string, + element: any + }) { this.fromState = fromState; this.toState = toState; this.totalTime = totalTime; this.phaseName = phaseName; + this.element = new ElementRef(element); } } diff --git a/modules/@angular/core/test/animation/animation_integration_spec.ts b/modules/@angular/core/test/animation/animation_integration_spec.ts index 955ba9258e..710ae00b54 100644 --- a/modules/@angular/core/test/animation/animation_integration_spec.ts +++ b/modules/@angular/core/test/animation/animation_integration_spec.ts @@ -27,6 +27,7 @@ import {AnimationTransitionEvent} from '../../src/animation/animation_transition import {AUTO_STYLE, animate, group, keyframes, sequence, state, style, transition, trigger} from '../../src/animation/metadata'; import {Input} from '../../src/core'; import {isPresent} from '../../src/facade/lang'; +import {ElementRef} from '../../src/linker/element_ref'; import {TestBed, fakeAsync, flushMicrotasks} from '../../testing'; import {MockAnimationPlayer} from '../../testing/mock_animation_player'; @@ -1763,6 +1764,36 @@ function declareTests({useJit}: {useJit: boolean}) { expect(doneCalls[3]).toEqual(1); expect(doneCalls[4]).toEqual(1); })); + + it('should expose the element associated with the animation within the callback event', + fakeAsync(() => { + TestBed.overrideComponent(DummyIfCmp, { + set: { + template: ` +
{{ item }}
+ `, + animations: [trigger('trigger', [transition('* => *', [animate(1000)])])] + } + }); + + const fixture = TestBed.createComponent(DummyIfCmp); + const cmp = fixture.componentInstance; + + const elements: ElementRef[] = []; + cmp.callback = (e: AnimationTransitionEvent) => elements.push(e.element); + + cmp.items = [0, 1, 2, 3, 4]; + fixture.detectChanges(); + flushMicrotasks(); + + const targetElements = + getDOM().querySelectorAll(fixture.nativeElement, '.target'); + for (let i = 0; i < elements.length; i++) { + expect(elements[i].nativeElement).toBe(targetElements[i]); + } + })); }); describe('ng directives', () => { diff --git a/modules/@angular/platform-webworker/test/web_workers/worker/renderer_animation_integration_spec.ts b/modules/@angular/platform-webworker/test/web_workers/worker/renderer_animation_integration_spec.ts index f0507b15e1..bea50582d8 100644 --- a/modules/@angular/platform-webworker/test/web_workers/worker/renderer_animation_integration_spec.ts +++ b/modules/@angular/platform-webworker/test/web_workers/worker/renderer_animation_integration_spec.ts @@ -8,6 +8,8 @@ import {AUTO_STYLE, AnimationTransitionEvent, Component, Injector, ViewChild, animate, state, style, transition, trigger} from '@angular/core'; import {DebugDomRootRenderer} from '@angular/core/src/debug/debug_renderer'; +import {ElementRef} from '@angular/core/src/linker/element_ref'; +import {ViewChild} from '@angular/core/src/metadata/di'; import {RootRenderer} from '@angular/core/src/render/api'; import {TestBed, fakeAsync, flushMicrotasks} from '@angular/core/testing'; import {MockAnimationPlayer} from '@angular/core/testing/testing_internal'; @@ -203,24 +205,34 @@ export function main() { flushMicrotasks(); uiDriver.log.shift()['player'].finish(); - const [triggerOneStart, triggerOneDone] = log['one']; - expect(triggerOneStart) - .toEqual(new AnimationTransitionEvent( - {fromState: 'a', toState: 'b', totalTime: 500, phaseName: 'start'})); - expect(triggerOneDone) - .toEqual(new AnimationTransitionEvent( - {fromState: 'a', toState: 'b', totalTime: 500, phaseName: 'done'})); + const [triggerOneStart, triggerOneDone] = log['one']; + expect(triggerOneStart.fromState).toEqual('a'); + expect(triggerOneStart.toState).toEqual('b'); + expect(triggerOneStart.totalTime).toEqual(500); + expect(triggerOneStart.phaseName).toEqual('start'); + expect(triggerOneStart.element instanceof ElementRef).toEqual(true); + + expect(triggerOneDone.fromState).toEqual('a'); + expect(triggerOneDone.toState).toEqual('b'); + expect(triggerOneDone.totalTime).toEqual(500); + expect(triggerOneDone.phaseName).toEqual('done'); + expect(triggerOneDone.element instanceof ElementRef).toEqual(true); uiDriver.log.shift()['player'].finish(); - const [triggerTwoStart, triggerTwoDone] = log['two']; - expect(triggerTwoStart) - .toEqual(new AnimationTransitionEvent( - {fromState: 'c', toState: 'd', totalTime: 1000, phaseName: 'start'})); - expect(triggerTwoDone) - .toEqual(new AnimationTransitionEvent( - {fromState: 'c', toState: 'd', totalTime: 1000, phaseName: 'done'})); + const [triggerTwoStart, triggerTwoDone] = log['two']; + expect(triggerTwoStart.fromState).toEqual('c'); + expect(triggerTwoStart.toState).toEqual('d'); + expect(triggerTwoStart.totalTime).toEqual(1000); + expect(triggerTwoStart.phaseName).toEqual('start'); + expect(triggerTwoStart.element instanceof ElementRef).toEqual(true); + + expect(triggerTwoDone.fromState).toEqual('c'); + expect(triggerTwoDone.toState).toEqual('d'); + expect(triggerTwoDone.totalTime).toEqual(1000); + expect(triggerTwoDone.phaseName).toEqual('done'); + expect(triggerTwoDone.element instanceof ElementRef).toEqual(true); })); it('should handle .start and .done callbacks for mutliple elements that contain animations that are fired at the same time', @@ -228,7 +240,7 @@ export function main() { function logFactory( log: {[phaseName: string]: AnimationTransitionEvent}, phaseName: string): (event: AnimationTransitionEvent) => any { - return (event: AnimationTransitionEvent) => { log[phaseName] = event; }; + return (event: AnimationTransitionEvent) => log[phaseName] = event; } const fixture = TestBed.createComponent(ContainerAnimationCmp); @@ -250,25 +262,37 @@ export function main() { uiDriver.log.shift()['player'].finish(); - expect(cmp1Log['start']) - .toEqual(new AnimationTransitionEvent( - {fromState: 'void', toState: 'off', totalTime: 500, phaseName: 'start'})); + const start1 = cmp1Log['start']; + expect(start1.fromState).toEqual('void'); + expect(start1.toState).toEqual('off'); + expect(start1.totalTime).toEqual(500); + expect(start1.phaseName).toEqual('start'); + expect(start1.element instanceof ElementRef).toBe(true); - expect(cmp1Log['done']) - .toEqual(new AnimationTransitionEvent( - {fromState: 'void', toState: 'off', totalTime: 500, phaseName: 'done'})); + const done1 = cmp1Log['done']; + expect(done1.fromState).toEqual('void'); + expect(done1.toState).toEqual('off'); + expect(done1.totalTime).toEqual(500); + expect(done1.phaseName).toEqual('done'); + expect(done1.element instanceof ElementRef).toBe(true); // the * => on transition has two steps uiDriver.log.shift()['player'].finish(); uiDriver.log.shift()['player'].finish(); - expect(cmp2Log['start']) - .toEqual(new AnimationTransitionEvent( - {fromState: 'void', toState: 'on', totalTime: 1000, phaseName: 'start'})); + const start2 = cmp2Log['start']; + expect(start2.fromState).toEqual('void'); + expect(start2.toState).toEqual('on'); + expect(start2.totalTime).toEqual(1000); + expect(start2.phaseName).toEqual('start'); + expect(start2.element instanceof ElementRef).toBe(true); - expect(cmp2Log['done']) - .toEqual(new AnimationTransitionEvent( - {fromState: 'void', toState: 'on', totalTime: 1000, phaseName: 'done'})); + const done2 = cmp2Log['done']; + expect(done2.fromState).toEqual('void'); + expect(done2.toState).toEqual('on'); + expect(done2.totalTime).toEqual(1000); + expect(done2.phaseName).toEqual('done'); + expect(done2.element instanceof ElementRef).toBe(true); })); it('should destroy the player when the animation is complete', fakeAsync(() => { @@ -331,6 +355,7 @@ class ContainerAnimationCmp { selector: 'my-comp', template: `
...
@@ -348,6 +373,8 @@ class AnimationCmp { state = 'off'; stateStartFn = (event: AnimationTransitionEvent): any => {}; stateDoneFn = (event: AnimationTransitionEvent): any => {}; + + @ViewChild('ref') public elmRef: ElementRef; } @Component({ @@ -355,10 +382,10 @@ class AnimationCmp { template: `
...
+ (@one.done)="callback('one', $event)" #one>...
...
+ (@two.done)="callback('two', $event)" #two>... `, animations: [ trigger( @@ -378,5 +405,10 @@ class AnimationCmp { class MultiAnimationCmp { oneTriggerState: string; twoTriggerState: string; + + @ViewChild('one') public elmRef1: ElementRef; + + @ViewChild('two') public elmRef2: ElementRef; + callback = (triggerName: string, event: AnimationTransitionEvent): any => {}; } diff --git a/tools/public_api_guard/core/index.d.ts b/tools/public_api_guard/core/index.d.ts index b1d0a9da31..56ed6e3dbd 100644 --- a/tools/public_api_guard/core/index.d.ts +++ b/tools/public_api_guard/core/index.d.ts @@ -108,15 +108,17 @@ export declare class AnimationStyleMetadata extends AnimationMetadata { /** @experimental */ export declare class AnimationTransitionEvent { + element: ElementRef; fromState: string; phaseName: string; toState: string; totalTime: number; - constructor({fromState, toState, totalTime, phaseName}: { + constructor({fromState, toState, totalTime, phaseName, element}: { fromState: string; toState: string; totalTime: number; phaseName: string; + element: any; }); }