diff --git a/modules/@angular/compiler/src/template_parser.ts b/modules/@angular/compiler/src/template_parser.ts index 4d657fa884..0201f69f48 100644 --- a/modules/@angular/compiler/src/template_parser.ts +++ b/modules/@angular/compiler/src/template_parser.ts @@ -562,6 +562,12 @@ class TemplateParseVisitor implements HtmlAstVisitor { private _parseAnimation( name: string, expression: string, sourceSpan: ParseSourceSpan, targetMatchableAttrs: string[][], targetAnimationProps: BoundElementPropertyAst[]) { + // This will occur when a @trigger is not paired with an expression. + // For animations it is valid to not have an expression since */void + // states will be applied by angular when the element is attached/detached + if (!isPresent(expression) || expression.length == 0) { + expression = 'null'; + } var ast = this._parseBinding(expression, sourceSpan); targetMatchableAttrs.push([name, ast.source]); targetAnimationProps.push(new BoundElementPropertyAst( diff --git a/modules/@angular/core/test/animation/animation_integration_spec.ts b/modules/@angular/core/test/animation/animation_integration_spec.ts index 5456dd71a1..b449263ee5 100644 --- a/modules/@angular/core/test/animation/animation_integration_spec.ts +++ b/modules/@angular/core/test/animation/animation_integration_spec.ts @@ -150,6 +150,49 @@ function declareTests({useJit}: {useJit: boolean}) { }); }))); + it('should animate between * and void and back even when no expression is assigned', + inject( + [TestComponentBuilder, AnimationDriver], + fakeAsync((tcb: TestComponentBuilder, driver: MockAnimationDriver) => { + tcb = tcb.overrideTemplate(DummyIfCmp, ` +
+ `); + tcb.overrideAnimations(DummyIfCmp, [trigger( + 'myAnimation', + [ + state('*', style({'opacity': '1'})), + state('void', style({'opacity': '0'})), + transition('* => *', [animate('500ms')]) + ])]) + .createAsync(DummyIfCmp) + .then((fixture) => { + tick(); + + var cmp = fixture.debugElement.componentInstance; + cmp.exp = true; + fixture.detectChanges(); + flushMicrotasks(); + + var result = driver.log.pop(); + expect(result['duration']).toEqual(500); + expect(result['startingStyles']).toEqual({'opacity': '0'}); + expect(result['keyframeLookup']).toEqual([ + [0, {'opacity': '0'}], [1, {'opacity': '1'}] + ]); + + cmp.exp = false; + fixture.detectChanges(); + flushMicrotasks(); + + result = driver.log.pop(); + expect(result['duration']).toEqual(500); + expect(result['startingStyles']).toEqual({'opacity': '1'}); + expect(result['keyframeLookup']).toEqual([ + [0, {'opacity': '1'}], [1, {'opacity': '0'}] + ]); + }); + }))); + it('should combine repeated style steps into a single step', inject( [TestComponentBuilder, AnimationDriver],