diff --git a/packages/animations/browser/src/dsl/animation_transition_expr.ts b/packages/animations/browser/src/dsl/animation_transition_expr.ts
index 0458e3fc71..23e3cdfc52 100644
--- a/packages/animations/browser/src/dsl/animation_transition_expr.ts
+++ b/packages/animations/browser/src/dsl/animation_transition_expr.ts
@@ -24,8 +24,14 @@ export function parseTransitionExpr(
function parseInnerTransitionStr(
eventStr: string, expressions: TransitionMatcherFn[], errors: string[]) {
if (eventStr[0] == ':') {
- eventStr = parseAnimationAlias(eventStr, errors);
+ const result = parseAnimationAlias(eventStr, errors);
+ if (typeof result == 'function') {
+ expressions.push(result);
+ return;
+ }
+ eventStr = result as string;
}
+
const match = eventStr.match(/^(\*|[-\w]+)\s*([=-]>)\s*(\*|[-\w]+)$/);
if (match == null || match.length < 4) {
errors.push(`The provided transition expression "${eventStr}" is not supported`);
@@ -43,12 +49,16 @@ function parseInnerTransitionStr(
}
}
-function parseAnimationAlias(alias: string, errors: string[]): string {
+function parseAnimationAlias(alias: string, errors: string[]): string|TransitionMatcherFn {
switch (alias) {
case ':enter':
return 'void => *';
case ':leave':
return '* => void';
+ case ':increment':
+ return (fromState: any, toState: any): boolean => parseFloat(toState) > parseFloat(fromState);
+ case ':decrement':
+ return (fromState: any, toState: any): boolean => parseFloat(toState) < parseFloat(fromState);
default:
errors.push(`The transition alias value "${alias}" is not supported`);
return '* => *';
diff --git a/packages/animations/src/animation_metadata.ts b/packages/animations/src/animation_metadata.ts
index 1cf6157eae..cabf6dd429 100755
--- a/packages/animations/src/animation_metadata.ts
+++ b/packages/animations/src/animation_metadata.ts
@@ -707,7 +707,7 @@ export function keyframes(steps: AnimationStyleMetadata[]): AnimationKeyframesSe
* ])
* ```
*
- * ### Transition Aliases (`:enter` and `:leave`)
+ * ### Using :enter and :leave
*
* Given that enter (insertion) and leave (removal) animations are so common, the `transition`
* function accepts both `:enter` and `:leave` values which are aliases for the `void => *` and `*
@@ -717,12 +717,88 @@ export function keyframes(steps: AnimationStyleMetadata[]): AnimationKeyframesSe
* transition(":enter", [
* style({ opacity: 0 }),
* animate(500, style({ opacity: 1 }))
- * ])
+ * ]),
* transition(":leave", [
* animate(500, style({ opacity: 0 }))
* ])
* ```
*
+ * ### Using :increment and :decrement
+ * In addition to the :enter and :leave transition aliases, the :increment and :decrement aliases
+ * can be used to kick off a transition when a numeric value has increased or decreased in value.
+ *
+ * ```
+ * import {group, animate, query, transition, style, trigger} from '@angular/animations';
+ * import {Component} from '@angular/core';
+ *
+ * @Component({
+ * selector: 'banner-carousel-component',
+ * styles: [`
+ * .banner-container {
+ * position:relative;
+ * height:500px;
+ * overflow:hidden;
+ * }
+ * .banner-container > .banner {
+ * position:absolute;
+ * left:0;
+ * top:0;
+ * font-size:200px;
+ * line-height:500px;
+ * font-weight:bold;
+ * text-align:center;
+ * width:100%;
+ * }
+ * `],
+ * template: `
+ *
+ *
+ *
+ *
+ * `
+ * animations: [
+ * trigger('bannerAnimation', [
+ * transition(":increment", group([
+ * query(':enter', [
+ * style({ left: '100%' }),
+ * animate('0.5s ease-out', style('*'))
+ * ]),
+ * query(':leave', [
+ * animate('0.5s ease-out', style({ left: '-100%' }))
+ * ])
+ * ])),
+ * transition(":decrement", group([
+ * query(':enter', [
+ * style({ left: '-100%' }),
+ * animate('0.5s ease-out', style('*'))
+ * ]),
+ * query(':leave', [
+ * animate('0.5s ease-out', style({ left: '100%' }))
+ * ])
+ * ])),
+ * ])
+ * ]
+ * })
+ * class BannerCarouselComponent {
+ * allBanners: string[] = ['1', '2', '3', '4'];
+ * selectedIndex: number = 0;
+ *
+ * get banners() {
+ * return [this.allBanners[this.selectedIndex]];
+ * }
+ *
+ * previous() {
+ * this.selectedIndex = Math.max(this.selectedIndex - 1, 0);
+ * }
+ *
+ * next() {
+ * this.selectedIndex = Math.min(this.selectedIndex + 1, this.allBanners.length - 1);
+ * }
+ * }
+ * ```
+ *
* {@example core/animation/ts/dsl/animation_example.ts region='Component'}
*
* @experimental Animation support is experimental.
diff --git a/packages/core/test/animation/animation_integration_spec.ts b/packages/core/test/animation/animation_integration_spec.ts
index 775c73c2a4..ee5d8ec6cd 100644
--- a/packages/core/test/animation/animation_integration_spec.ts
+++ b/packages/core/test/animation/animation_integration_spec.ts
@@ -1541,6 +1541,98 @@ export function main() {
const players = getLog();
expect(players.length).toEqual(2);
});
+
+ describe('transition aliases', () => {
+ describe(':increment', () => {
+ it('should detect when a value has incremented', () => {
+ @Component({
+ selector: 'if-cmp',
+ template: `
+
+ `,
+ animations: [
+ trigger(
+ 'myAnimation',
+ [
+ transition(
+ ':increment',
+ [
+ animate(1234, style({background: 'red'})),
+ ]),
+ ]),
+ ]
+ })
+ class Cmp {
+ exp: number = 0;
+ }
+
+ TestBed.configureTestingModule({declarations: [Cmp]});
+ const fixture = TestBed.createComponent(Cmp);
+ const cmp = fixture.componentInstance;
+ fixture.detectChanges();
+ let players = getLog();
+ expect(players.length).toEqual(0);
+
+ cmp.exp++;
+ fixture.detectChanges();
+ players = getLog();
+ expect(players.length).toEqual(1);
+ expect(players[0].duration).toEqual(1234);
+ resetLog();
+
+ cmp.exp = 5;
+ fixture.detectChanges();
+ players = getLog();
+ expect(players.length).toEqual(1);
+ expect(players[0].duration).toEqual(1234);
+ });
+ });
+
+ describe(':decrement', () => {
+ it('should detect when a value has decremented', () => {
+ @Component({
+ selector: 'if-cmp',
+ template: `
+
+ `,
+ animations: [
+ trigger(
+ 'myAnimation',
+ [
+ transition(
+ ':decrement',
+ [
+ animate(1234, style({background: 'red'})),
+ ]),
+ ]),
+ ]
+ })
+ class Cmp {
+ exp: number = 5;
+ }
+
+ TestBed.configureTestingModule({declarations: [Cmp]});
+ const fixture = TestBed.createComponent(Cmp);
+ const cmp = fixture.componentInstance;
+ fixture.detectChanges();
+ let players = getLog();
+ expect(players.length).toEqual(0);
+
+ cmp.exp--;
+ fixture.detectChanges();
+ players = getLog();
+ expect(players.length).toEqual(1);
+ expect(players[0].duration).toEqual(1234);
+ resetLog();
+
+ cmp.exp = 0;
+ fixture.detectChanges();
+ players = getLog();
+ expect(players.length).toEqual(1);
+ expect(players[0].duration).toEqual(1234);
+ });
+ });
+ });
});
describe('animation listeners', () => {