feat(animations): expose `element` and `params` within transition matchers (#22693)

PR Close #22693
This commit is contained in:
Matias Niemelä 2018-03-12 17:21:02 -07:00 committed by Kara Erickson
parent db56836425
commit 58b94e6f5e
11 changed files with 119 additions and 20 deletions

View File

@ -46,7 +46,8 @@ export interface StateAst extends Ast<AnimationMetadataType.State> {
}
export interface TransitionAst extends Ast<AnimationMetadataType.Transition> {
matchers: ((fromState: string, toState: string) => boolean)[];
matchers: ((fromState: string, toState: string, element: any, params: {[key: string]:
any}) => boolean)[];
animation: Ast<AnimationMetadataType>;
queryCount: number;
depCount: number;

View File

@ -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[] {

View File

@ -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 {

View File

@ -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;
}

View File

@ -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;

View File

@ -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(

View File

@ -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 {
* <div [@myAnimationTrigger]="myStatusExp">...</div>
* ```
*
* ### 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};

View File

@ -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[];
}

View File

@ -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: '<div [@myAnimation]="exp"></div>',
template: '<div #element [@myAnimation]="exp"></div>',
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: '<div [@myAnimation]="{value:exp, params: {doMatch:doMatch}}"></div>',
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',

View File

@ -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;

View File

@ -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 */