From 58b94e6f5e934bb3589b5ae17cb1dc8953f8d5db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matias=20Niemel=C3=A4?= Date: Mon, 12 Mar 2018 17:21:02 -0700 Subject: [PATCH] feat(animations): expose `element` and `params` within transition matchers (#22693) PR Close #22693 --- .../browser/src/dsl/animation_ast.ts | 3 +- .../src/dsl/animation_transition_expr.ts | 3 +- .../src/dsl/animation_transition_factory.ts | 9 +-- .../browser/src/dsl/animation_trigger.ts | 6 +- .../src/render/transition_animation_engine.ts | 3 +- .../test/dsl/animation_trigger_spec.ts | 5 +- packages/animations/src/animation_metadata.ts | 39 ++++++++++++- .../animation/animation_metadata_wrapped.ts | 3 +- .../animation/animation_integration_spec.ts | 56 ++++++++++++++++++- .../animations/animations.d.ts | 8 ++- tools/public_api_guard/core/core.d.ts | 4 +- 11 files changed, 119 insertions(+), 20 deletions(-) diff --git a/packages/animations/browser/src/dsl/animation_ast.ts b/packages/animations/browser/src/dsl/animation_ast.ts index 7055b59542..dd5a46363b 100644 --- a/packages/animations/browser/src/dsl/animation_ast.ts +++ b/packages/animations/browser/src/dsl/animation_ast.ts @@ -46,7 +46,8 @@ export interface StateAst extends Ast { } export interface TransitionAst extends Ast { - matchers: ((fromState: string, toState: string) => boolean)[]; + matchers: ((fromState: string, toState: string, element: any, params: {[key: string]: + any}) => boolean)[]; animation: Ast; queryCount: number; depCount: number; diff --git a/packages/animations/browser/src/dsl/animation_transition_expr.ts b/packages/animations/browser/src/dsl/animation_transition_expr.ts index a9e3aace93..7285ca056a 100644 --- a/packages/animations/browser/src/dsl/animation_transition_expr.ts +++ b/packages/animations/browser/src/dsl/animation_transition_expr.ts @@ -6,7 +6,8 @@ * found in the LICENSE file at https://angular.io/license */ export const ANY_STATE = '*'; -export declare type TransitionMatcherFn = (fromState: any, toState: any) => boolean; +export declare type TransitionMatcherFn = + (fromState: any, toState: any, element: any, params: {[key: string]: any}) => boolean; export function parseTransitionExpr( transitionValue: string | TransitionMatcherFn, errors: string[]): TransitionMatcherFn[] { diff --git a/packages/animations/browser/src/dsl/animation_transition_factory.ts b/packages/animations/browser/src/dsl/animation_transition_factory.ts index d407da046f..0c4f9785fe 100644 --- a/packages/animations/browser/src/dsl/animation_transition_factory.ts +++ b/packages/animations/browser/src/dsl/animation_transition_factory.ts @@ -24,8 +24,8 @@ export class AnimationTransitionFactory { private _triggerName: string, public ast: TransitionAst, private _stateStyles: {[stateName: string]: AnimationStateStyles}) {} - match(currentState: any, nextState: any): boolean { - return oneOrMoreTransitionsMatch(this.ast.matchers, currentState, nextState); + match(currentState: any, nextState: any, element: any, params: {[key: string]: any}): boolean { + return oneOrMoreTransitionsMatch(this.ast.matchers, currentState, nextState, element, params); } buildStyles(stateName: string, params: {[key: string]: any}, errors: any[]) { @@ -89,8 +89,9 @@ export class AnimationTransitionFactory { } function oneOrMoreTransitionsMatch( - matchFns: TransitionMatcherFn[], currentState: any, nextState: any): boolean { - return matchFns.some(fn => fn(currentState, nextState)); + matchFns: TransitionMatcherFn[], currentState: any, nextState: any, element: any, + params: {[key: string]: any}): boolean { + return matchFns.some(fn => fn(currentState, nextState, element, params)); } export class AnimationStateStyles { diff --git a/packages/animations/browser/src/dsl/animation_trigger.ts b/packages/animations/browser/src/dsl/animation_trigger.ts index b52ff0b5dc..c594241c36 100644 --- a/packages/animations/browser/src/dsl/animation_trigger.ts +++ b/packages/animations/browser/src/dsl/animation_trigger.ts @@ -47,8 +47,10 @@ export class AnimationTrigger { get containsQueries() { return this.ast.queryCount > 0; } - matchTransition(currentState: any, nextState: any): AnimationTransitionFactory|null { - const entry = this.transitionFactories.find(f => f.match(currentState, nextState)); + matchTransition(currentState: any, nextState: any, element: any, params: {[key: string]: any}): + AnimationTransitionFactory|null { + const entry = + this.transitionFactories.find(f => f.match(currentState, nextState, element, params)); return entry || null; } diff --git a/packages/animations/browser/src/render/transition_animation_engine.ts b/packages/animations/browser/src/render/transition_animation_engine.ts index e06fc3221b..d41d4f2cd7 100644 --- a/packages/animations/browser/src/render/transition_animation_engine.ts +++ b/packages/animations/browser/src/render/transition_animation_engine.ts @@ -248,7 +248,8 @@ export class AnimationTransitionNamespace { } }); - let transition = trigger.matchTransition(fromState.value, toState.value); + let transition = + trigger.matchTransition(fromState.value, toState.value, element, toState.params); let isFallbackTransition = false; if (!transition) { if (!defaultToFallback) return; diff --git a/packages/animations/browser/test/dsl/animation_trigger_spec.ts b/packages/animations/browser/test/dsl/animation_trigger_spec.ts index 74a517dec1..c541c09292 100644 --- a/packages/animations/browser/test/dsl/animation_trigger_spec.ts +++ b/packages/animations/browser/test/dsl/animation_trigger_spec.ts @@ -103,7 +103,7 @@ import {makeTrigger} from '../shared'; it('should null when no results are found', () => { const result = makeTrigger('name', [transition('a => b', animate(1111))]); - const trigger = result.matchTransition('b', 'a'); + const trigger = result.matchTransition('b', 'a', {}, {}); expect(trigger).toBeFalsy(); }); @@ -226,7 +226,8 @@ function buildTransition( trigger: AnimationTrigger, element: any, fromState: any, toState: any, fromOptions?: AnimationOptions, toOptions?: AnimationOptions): AnimationTransitionInstruction| null { - const trans = trigger.matchTransition(fromState, toState) !; + const params = toOptions && toOptions.params || {}; + const trans = trigger.matchTransition(fromState, toState, element, params) !; if (trans) { const driver = new MockAnimationDriver(); return trans.build( diff --git a/packages/animations/src/animation_metadata.ts b/packages/animations/src/animation_metadata.ts index 72bfee2d00..55d35a4f27 100755 --- a/packages/animations/src/animation_metadata.ts +++ b/packages/animations/src/animation_metadata.ts @@ -115,7 +115,9 @@ export interface AnimationStateMetadata extends AnimationMetadata { * @experimental Animation support is experimental. */ export interface AnimationTransitionMetadata extends AnimationMetadata { - expr: string|((fromState: string, toState: string) => boolean); + expr: string| + ((fromState: string, toState: string, element?: any, + params?: {[key: string]: any}) => boolean); animation: AnimationMetadata|AnimationMetadata[]; options: AnimationOptions|null; } @@ -294,6 +296,38 @@ export interface AnimationStaggerMetadata extends AnimationMetadata { *
...
* ``` * + * ### Using an inline function + * The `transition` animation method also supports reading an inline function which can decide + * if its associated animation should be run. + * + * ``` + * // this method will be run each time the `myAnimationTrigger` + * // trigger value changes... + * function myInlineMatcherFn(fromState: string, toState: string, element: any, params: {[key: + string]: any}): boolean { + * // notice that `element` and `params` are also available here + * return toState == 'yes-please-animate'; + * } + * + * @Component({ + * selector: 'my-component', + * templateUrl: 'my-component-tpl.html', + * animations: [ + * trigger('myAnimationTrigger', [ + * transition(myInlineMatcherFn, [ + * // the animation sequence code + * ]), + * ]) + * ] + * }) + * class MyComponent { + * myStatusExp = "yes-please-animate"; + * } + * ``` + * + * The inline method will be run each time the trigger + * value changes + * * ## Disable Animations * A special animation control binding called `@.disabled` can be placed on an element which will then disable animations for any inner animation triggers situated within the element as well as @@ -844,7 +878,8 @@ export function keyframes(steps: AnimationStyleMetadata[]): AnimationKeyframesSe * @experimental Animation support is experimental. */ export function transition( - stateChangeExpr: string | ((fromState: string, toState: string) => boolean), + stateChangeExpr: string | ((fromState: string, toState: string, element?: any, + params?: {[key: string]: any}) => boolean), steps: AnimationMetadata | AnimationMetadata[], options: AnimationOptions | null = null): AnimationTransitionMetadata { return {type: AnimationMetadataType.Transition, expr: stateChangeExpr, animation: steps, options}; diff --git a/packages/core/src/animation/animation_metadata_wrapped.ts b/packages/core/src/animation/animation_metadata_wrapped.ts index d8b6da3a37..86d6c15ed6 100644 --- a/packages/core/src/animation/animation_metadata_wrapped.ts +++ b/packages/core/src/animation/animation_metadata_wrapped.ts @@ -38,7 +38,8 @@ export interface AnimationStateMetadata extends AnimationMetadata { * @deprecated This symbol has moved. Please Import from @angular/animations instead! */ export interface AnimationTransitionMetadata extends AnimationMetadata { - expr: string|((fromState: string, toState: string) => boolean); + expr: string| + ((fromState: string, toState: string, element: any, params: {[key: string]: any}) => boolean); animation: AnimationMetadata|AnimationMetadata[]; } diff --git a/packages/core/test/animation/animation_integration_spec.ts b/packages/core/test/animation/animation_integration_spec.ts index a5c92395a3..a02fdd5202 100644 --- a/packages/core/test/animation/animation_integration_spec.ts +++ b/packages/core/test/animation/animation_integration_spec.ts @@ -301,12 +301,15 @@ const DEFAULT_COMPONENT_ID = '1'; it('should allow a transition to use a function to determine what method to run', () => { let valueToMatch = ''; - const transitionFn = - (fromState: string, toState: string) => { return toState == valueToMatch; }; + let capturedElement: any; + const transitionFn = (fromState: string, toState: string, element: any) => { + capturedElement = element; + return toState == valueToMatch; + }; @Component({ selector: 'if-cmp', - template: '
', + template: '
', animations: [ trigger('myAnimation', [transition( transitionFn, @@ -314,6 +317,8 @@ const DEFAULT_COMPONENT_ID = '1'; ] }) class Cmp { + @ViewChild('element') + element: any; exp: any = ''; } @@ -323,11 +328,13 @@ const DEFAULT_COMPONENT_ID = '1'; const cmp = fixture.componentInstance; valueToMatch = cmp.exp = 'something'; fixture.detectChanges(); + const element = cmp.element.nativeElement; let players = getLog(); expect(players.length).toEqual(1); let [p1] = players; expect(p1.totalTime).toEqual(1234); + expect(capturedElement).toEqual(element); resetLog(); valueToMatch = 'something-else'; @@ -338,6 +345,49 @@ const DEFAULT_COMPONENT_ID = '1'; expect(players.length).toEqual(0); }); + it('should allow a transition to use a function to determine what method to run and expose any parameter values', + () => { + const transitionFn = + (fromState: string, toState: string, element: any, params: {[key: string]: any}) => { + return params['doMatch'] == true; + }; + + @Component({ + selector: 'if-cmp', + template: '
', + animations: [ + trigger( + 'myAnimation', + [transition( + transitionFn, [style({opacity: 0}), animate(3333, style({opacity: 1}))])]), + ] + }) + class Cmp { + doMatch = false; + exp: any = ''; + } + + TestBed.configureTestingModule({declarations: [Cmp]}); + + const fixture = TestBed.createComponent(Cmp); + const cmp = fixture.componentInstance; + cmp.doMatch = true; + fixture.detectChanges(); + + let players = getLog(); + expect(players.length).toEqual(1); + let [p1] = players; + expect(p1.totalTime).toEqual(3333); + resetLog(); + + cmp.doMatch = false; + cmp.exp = 'this-wont-match'; + fixture.detectChanges(); + + players = getLog(); + expect(players.length).toEqual(0); + }); + it('should allow a state value to be `0`', () => { @Component({ selector: 'if-cmp', diff --git a/tools/public_api_guard/animations/animations.d.ts b/tools/public_api_guard/animations/animations.d.ts index 156e002969..07f51a54f8 100644 --- a/tools/public_api_guard/animations/animations.d.ts +++ b/tools/public_api_guard/animations/animations.d.ts @@ -173,7 +173,9 @@ export interface AnimationStyleMetadata extends AnimationMetadata { /** @experimental */ export interface AnimationTransitionMetadata extends AnimationMetadata { animation: AnimationMetadata | AnimationMetadata[]; - expr: string | ((fromState: string, toState: string) => boolean); + expr: string | ((fromState: string, toState: string, element?: any, params?: { + [key: string]: any; + }) => boolean); options: AnimationOptions | null; } @@ -241,7 +243,9 @@ export declare function style(tokens: '*' | { }>): AnimationStyleMetadata; /** @experimental */ -export declare function transition(stateChangeExpr: string | ((fromState: string, toState: string) => boolean), steps: AnimationMetadata | AnimationMetadata[], options?: AnimationOptions | null): AnimationTransitionMetadata; +export declare function transition(stateChangeExpr: string | ((fromState: string, toState: string, element?: any, params?: { + [key: string]: any; +}) => boolean), steps: AnimationMetadata | AnimationMetadata[], options?: AnimationOptions | null): AnimationTransitionMetadata; /** @experimental */ export declare function trigger(name: string, definitions: AnimationMetadata[]): AnimationTriggerMetadata; diff --git a/tools/public_api_guard/core/core.d.ts b/tools/public_api_guard/core/core.d.ts index 8c9ccdc549..e843b3c638 100644 --- a/tools/public_api_guard/core/core.d.ts +++ b/tools/public_api_guard/core/core.d.ts @@ -94,7 +94,9 @@ export interface AnimationTransitionEvent { /** @deprecated */ export interface AnimationTransitionMetadata extends AnimationMetadata { animation: AnimationMetadata | AnimationMetadata[]; - expr: string | ((fromState: string, toState: string) => boolean); + expr: string | ((fromState: string, toState: string, element: any, params: { + [key: string]: any; + }) => boolean); } /** @deprecated */