diff --git a/packages/core/test/animation/animation_integration_spec.ts b/packages/core/test/animation/animation_integration_spec.ts index 551f65315e..fc56c91531 100644 --- a/packages/core/test/animation/animation_integration_spec.ts +++ b/packages/core/test/animation/animation_integration_spec.ts @@ -328,6 +328,36 @@ const DEFAULT_COMPONENT_ID = '1'; ]); }); + // https://github.com/angular/angular/issues/32794 + it('should support nested animation triggers', () => { + const REUSABLE_ANIMATION = [trigger( + 'myAnimation', + [transition( + 'void => *', [style({'opacity': '0'}), animate(500, style({'opacity': '1'}))])])]; + + @Component({ + selector: 'if-cmp', + template: ` +
+ `, + animations: [REUSABLE_ANIMATION], + }) + class Cmp { + } + + TestBed.configureTestingModule({declarations: [Cmp]}); + + const engine = TestBed.inject(ɵAnimationEngine); + const fixture = TestBed.createComponent(Cmp); + fixture.detectChanges(); + engine.flush(); + + expect(getLog().length).toEqual(1); + expect(getLog().pop() !.keyframes).toEqual([ + {offset: 0, opacity: '0'}, {offset: 1, opacity: '1'} + ]); + }); + it('should allow a transition to use a function to determine what method to run', () => { let valueToMatch = ''; let capturedElement: any; diff --git a/packages/platform-browser/animations/src/animation_renderer.ts b/packages/platform-browser/animations/src/animation_renderer.ts index 59b4f48268..02ae8cf58d 100644 --- a/packages/platform-browser/animations/src/animation_renderer.ts +++ b/packages/platform-browser/animations/src/animation_renderer.ts @@ -12,6 +12,12 @@ import {Injectable, NgZone, Renderer2, RendererFactory2, RendererStyleFlags2, Re const ANIMATION_PREFIX = '@'; const DISABLE_ANIMATIONS_FLAG = '@.disabled'; +// Define a recursive type to allow for nested arrays of `AnimationTriggerMetadata`. Note that an +// interface declaration is used as TypeScript prior to 3.7 does not support recursive type +// references, see https://github.com/microsoft/TypeScript/pull/33050 for details. +type NestedAnimationTriggerMetadata = AnimationTriggerMetadata | RecursiveAnimationTriggerMetadata; +interface RecursiveAnimationTriggerMetadata extends Array {} + @Injectable() export class AnimationRendererFactory implements RendererFactory2 { private _currentId: number = 0; @@ -55,10 +61,17 @@ export class AnimationRendererFactory implements RendererFactory2 { this._currentId++; this.engine.register(namespaceId, hostElement); - const animationTriggers = type.data['animation'] as AnimationTriggerMetadata[]; - animationTriggers.forEach( - trigger => this.engine.registerTrigger( - componentId, namespaceId, hostElement, trigger.name, trigger)); + + const registerTrigger = (trigger: NestedAnimationTriggerMetadata) => { + if (Array.isArray(trigger)) { + trigger.forEach(registerTrigger); + } else { + this.engine.registerTrigger(componentId, namespaceId, hostElement, trigger.name, trigger); + } + }; + const animationTriggers = type.data['animation'] as NestedAnimationTriggerMetadata[]; + animationTriggers.forEach(registerTrigger); + return new AnimationRenderer(this, namespaceId, delegate, this.engine); } diff --git a/packages/platform-browser/animations/test/animation_renderer_spec.ts b/packages/platform-browser/animations/test/animation_renderer_spec.ts index 7455b4e00f..8d3e87c82b 100644 --- a/packages/platform-browser/animations/test/animation_renderer_spec.ts +++ b/packages/platform-browser/animations/test/animation_renderer_spec.ts @@ -79,6 +79,16 @@ import {el} from '../../testing/src/browser_util'; expect(engine.captures['setProperty'].pop()).toEqual([element, 'prop', 'value']); }); + // https://github.com/angular/angular/issues/32794 + it('should support nested animation triggers', () => { + makeRenderer([[trigger('myAnimation', [])]]); + + const {triggers} = TestBed.inject(AnimationEngine) as MockAnimationEngine; + + expect(triggers.length).toEqual(1); + expect(triggers[0].name).toEqual('myAnimation'); + }); + describe('listen', () => { it('should hook into the engine\'s listen call if the property begins with `@`', () => { const renderer = makeRenderer(); @@ -320,8 +330,10 @@ class MockAnimationEngine extends InjectableAnimationEngine { data.push(args); } - registerTrigger(componentId: string, namespaceId: string, trigger: AnimationTriggerMetadata) { - this.triggers.push(trigger); + registerTrigger( + componentId: string, namespaceId: string, hostElement: any, name: string, + metadata: AnimationTriggerMetadata): void { + this.triggers.push(metadata); } onInsert(namespaceId: string, element: any): void { this._capture('onInsert', [element]); }