Permalink
Browse files

feat(animations): allow animation integration support into host params

Closes #9044
Closes #9933
  • Loading branch information...
1 parent 5af1e89 commit 806a25413cae012c6334520667372c06c7b30bf9 @matsko matsko committed Jul 9, 2016
@@ -15,7 +15,7 @@ import {BaseException} from '../facade/exceptions';
import {isArray, isBlank, isPresent} from '../facade/lang';
import {Identifiers} from '../identifiers';
import * as o from '../output/output_ast';
-import {PropertyBindingType, TemplateAst, TemplateAstVisitor, NgContentAst, EmbeddedTemplateAst, ElementAst, ReferenceAst, VariableAst, BoundEventAst, BoundElementPropertyAst, AttrAst, BoundTextAst, TextAst, DirectiveAst, BoundDirectivePropertyAst, templateVisitAll,} from '../template_ast';
+import * as t from '../template_ast';
import {AnimationAst, AnimationAstVisitor, AnimationEntryAst, AnimationGroupAst, AnimationKeyframeAst, AnimationSequenceAst, AnimationStateAst, AnimationStateDeclarationAst, AnimationStateTransitionAst, AnimationStepAst, AnimationStylesAst} from './animation_ast';
import {AnimationParseError, ParsedAnimationResult, parseAnimationEntry} from './animation_parser';
@@ -28,10 +28,9 @@ export class CompiledAnimation {
}
export class AnimationCompiler {
- compileComponent(component: CompileDirectiveMetadata, template: TemplateAst[]):
+ compileComponent(component: CompileDirectiveMetadata, template: t.TemplateAst[]):
CompiledAnimation[] {
var compiledAnimations: CompiledAnimation[] = [];
- var index = 0;
var groupedErrors: string[] = [];
var triggerLookup: {[key: string]: CompiledAnimation} = {};
var componentName = component.type.name;
@@ -44,19 +43,16 @@ export class AnimationCompiler {
`Unable to parse the animation sequence for "${triggerName}" due to the following errors:`;
result.errors.forEach(
(error: AnimationParseError) => { errorMessage += '\n-- ' + error.msg; });
- // todo (matsko): include the component name when throwing
groupedErrors.push(errorMessage);
}
if (triggerLookup[triggerName]) {
groupedErrors.push(
`The animation trigger "${triggerName}" has already been registered on "${componentName}"`);
} else {
- var factoryName = `${component.type.name}_${entry.name}_${index}`;
- index++;
-
+ var factoryName = `${componentName}_${entry.name}`;
var visitor = new _AnimationBuilder(triggerName, factoryName);
- var compileResult = visitor.build(result.ast)
+ var compileResult = visitor.build(result.ast);
compiledAnimations.push(compileResult);
triggerLookup[entry.name] = compileResult;
}
@@ -387,14 +383,13 @@ function _getStylesArray(obj: any): {[key: string]: any}[] {
}
function _validateAnimationProperties(
- compiledAnimations: CompiledAnimation[], template: TemplateAst[]): AnimationParseError[] {
+ compiledAnimations: CompiledAnimation[], template: t.TemplateAst[]): AnimationParseError[] {
var visitor = new _AnimationTemplatePropertyVisitor(compiledAnimations);
- templateVisitAll(visitor, template);
+ t.templateVisitAll(visitor, template);
return visitor.errors;
}
-class _AnimationTemplatePropertyVisitor implements TemplateAstVisitor {
- private _nodeIndex: number = 0;
+class _AnimationTemplatePropertyVisitor implements t.TemplateAstVisitor {
private _animationRegistry: {[key: string]: boolean} = {};
public errors: AnimationParseError[] = [];
@@ -403,31 +398,28 @@ class _AnimationTemplatePropertyVisitor implements TemplateAstVisitor {
animations.forEach(entry => { this._animationRegistry[entry.name] = true; });
}
- visitElement(ast: ElementAst, ctx: any): any {
+ visitElement(ast: t.ElementAst, ctx: any): any {
ast.inputs.forEach(input => {
- if (input.type == PropertyBindingType.Animation) {
+ if (input.type == t.PropertyBindingType.Animation) {
var animationName = input.name;
if (!isPresent(this._animationRegistry[animationName])) {
this.errors.push(
new AnimationParseError(`couldn't find an animation entry for ${animationName}`));
}
}
});
- templateVisitAll(this, ast.children);
+ t.templateVisitAll(this, ast.children);
}
- visitBoundText(ast: BoundTextAst, ctx: any): any { this._nodeIndex++; }
-
- visitText(ast: TextAst, ctx: any): any { this._nodeIndex++; }
-
- visitEmbeddedTemplate(ast: EmbeddedTemplateAst, ctx: any): any { this._nodeIndex++; }
-
- visitNgContent(ast: NgContentAst, ctx: any): any {}
- visitAttr(ast: AttrAst, ctx: any): any {}
- visitDirective(ast: DirectiveAst, ctx: any): any {}
- visitEvent(ast: BoundEventAst, ctx: any): any {}
- visitReference(ast: ReferenceAst, ctx: any): any {}
- visitVariable(ast: VariableAst, ctx: any): any {}
- visitDirectiveProperty(ast: BoundDirectivePropertyAst, ctx: any): any {}
- visitElementProperty(ast: BoundElementPropertyAst, ctx: any): any {}
+ visitBoundText(ast: t.BoundTextAst, ctx: any): any {}
+ visitText(ast: t.TextAst, ctx: any): any {}
+ visitEmbeddedTemplate(ast: t.EmbeddedTemplateAst, ctx: any): any {}
+ visitNgContent(ast: t.NgContentAst, ctx: any): any {}
+ visitAttr(ast: t.AttrAst, ctx: any): any {}
+ visitDirective(ast: t.DirectiveAst, ctx: any): any {}
+ visitEvent(ast: t.BoundEventAst, ctx: any): any {}
+ visitReference(ast: t.ReferenceAst, ctx: any): any {}
+ visitVariable(ast: t.VariableAst, ctx: any): any {}
+ visitDirectiveProperty(ast: t.BoundDirectivePropertyAst, ctx: any): any {}
+ visitElementProperty(ast: t.BoundElementPropertyAst, ctx: any): any {}
}
@@ -18,9 +18,11 @@ import {getUrlScheme} from './url_resolver';
import {sanitizeIdentifier, splitAtColon} from './util';
-
+// group 0: "[prop] or (event) or @trigger"
+// group 1: "prop" from "[prop]"
// group 2: "event" from "(event)"
-var HOST_REG_EXP = /^(?:(?:\[([^\]]+)\])|(?:\(([^\)]+)\)))$/g;
+// group 3: "@trigger" from "@trigger"
+var HOST_REG_EXP = /^(?:(?:\[([^\]]+)\])|(?:\(([^\)]+)\)))|(\@[-\w]+)$/g;
export abstract class CompileMetadataWithIdentifier {
abstract toJson(): {[key: string]: any};
@@ -741,6 +743,8 @@ export class CompileDirectiveMetadata implements CompileMetadataWithType {
hostProperties[matches[1]] = value;
} else if (isPresent(matches[2])) {
hostListeners[matches[2]] = value;
+ } else if (isPresent(matches[3])) {
+ hostProperties[matches[3]] = value;
}
});
}
@@ -774,13 +774,23 @@ class TemplateParseVisitor implements HtmlAstVisitor {
const parts = name.split(PROPERTY_PARTS_SEPARATOR);
let securityContext: SecurityContext;
if (parts.length === 1) {
- boundPropertyName = this._schemaRegistry.getMappedPropName(parts[0]);
- securityContext = this._schemaRegistry.securityContext(elementName, boundPropertyName);
- bindingType = PropertyBindingType.Property;
- if (!this._schemaRegistry.hasProperty(elementName, boundPropertyName)) {
+ var partValue = parts[0];
+ if (partValue[0] == '@') {
+ boundPropertyName = partValue.substr(1);
+ bindingType = PropertyBindingType.Animation;
+ securityContext = SecurityContext.NONE;
this._reportError(
- `Can't bind to '${boundPropertyName}' since it isn't a known native property`,
- sourceSpan);
+ `Assigning animation triggers within host data as attributes such as "@prop": "exp" is deprecated. Use "[@prop]": "exp" instead!`,
+ sourceSpan, ParseErrorLevel.WARNING);
+ } else {
+ boundPropertyName = this._schemaRegistry.getMappedPropName(partValue);
+ securityContext = this._schemaRegistry.securityContext(elementName, boundPropertyName);
+ bindingType = PropertyBindingType.Property;
+ if (!this._schemaRegistry.hasProperty(elementName, boundPropertyName)) {
+ this._reportError(
+ `Can't bind to '${boundPropertyName}' since it isn't a known native property`,
+ sourceSpan);
+ }
}
} else {
if (parts[0] == ATTRIBUTE_PREFIX) {
@@ -65,16 +65,14 @@ export class CompileView implements NameResolver {
public literalArrayCount = 0;
public literalMapCount = 0;
public pipeCount = 0;
- public animations = new Map<string, CompiledAnimation>();
public componentContext: o.Expression;
constructor(
public component: CompileDirectiveMetadata, public genConfig: CompilerConfig,
public pipeMetas: CompilePipeMetadata[], public styles: o.Expression,
- animations: CompiledAnimation[], public viewIndex: number,
+ public animations: CompiledAnimation[], public viewIndex: number,
public declarationElement: CompileElement, public templateVariableBindings: string[][]) {
- animations.forEach(entry => this.animations.set(entry.name, entry));
this.createMethod = new CompileMethod(this);
this.injectorGetMethod = new CompileMethod(this);
this.updateContentQueriesMethod = new CompileMethod(this);
@@ -6,8 +6,6 @@
* found in the LICENSE file at https://angular.io/license
*/
-import {BaseException, SecurityContext} from '@angular/core';
-
import {EMPTY_STATE as EMPTY_ANIMATION_STATE, LifecycleHooks, isDefaultChangeDetectionStrategy} from '../../core_private';
import * as cdAst from '../expression_parser/ast';
import {isBlank, isPresent} from '../facade/lang';
@@ -21,8 +19,7 @@ import {CompileMethod} from './compile_method';
import {camelCaseToDashCase} from '../util';
import {convertCdExpressionToIr} from './expression_converter';
import {CompileBinding} from './compile_binding';
-import {BaseException, SecurityContext} from '@angular/core';
-
+import {SecurityContext} from '@angular/core';
function createBindFieldExpr(exprIndex: number): o.ReadPropExpr {
return o.THIS_EXPR.prop(`_expr_${exprIndex}`);
@@ -85,7 +82,8 @@ export function bindRenderText(
}
function bindAndWriteToRenderer(
- boundProps: BoundElementPropertyAst[], context: o.Expression, compileElement: CompileElement) {
+ boundProps: BoundElementPropertyAst[], context: o.Expression, compileElement: CompileElement,
+ isHostProp: boolean) {
var view = compileElement.view;
var renderNode = compileElement.renderNode;
boundProps.forEach((boundProp) => {
@@ -129,6 +127,7 @@ function bindAndWriteToRenderer(
if (isPresent(boundProp.unit)) {
strValue = strValue.plus(o.literal(boundProp.unit));
}
+
renderValue = renderValue.isBlank().conditional(o.NULL_EXPR, strValue);
updateStmts.push(
o.THIS_EXPR.prop('renderer')
@@ -137,7 +136,13 @@ function bindAndWriteToRenderer(
break;
case PropertyBindingType.Animation:
var animationName = boundProp.name;
- var animation = view.componentView.animations.get(animationName);
+ var targetViewExpr: o.Expression = o.THIS_EXPR;
+ if (isHostProp) {
+ targetViewExpr = compileElement.appElement.prop('componentView');
+ }
+
+ var animationFnExpr =
+ targetViewExpr.prop('componentType').prop('animations').key(o.literal(animationName));
// it's important to normalize the void value as `void` explicitly
// so that the styles data can be obtained from the stringmap
@@ -158,11 +163,10 @@ function bindAndWriteToRenderer(
[newRenderVar.set(emptyStateValue).toStmt()]));
updateStmts.push(
- animation.fnVariable.callFn([o.THIS_EXPR, renderNode, oldRenderVar, newRenderVar])
- .toStmt());
+ animationFnExpr.callFn([o.THIS_EXPR, renderNode, oldRenderVar, newRenderVar]).toStmt());
view.detachMethod.addStmt(
- animation.fnVariable.callFn([o.THIS_EXPR, renderNode, oldRenderValue, emptyStateValue])
+ animationFnExpr.callFn([o.THIS_EXPR, renderNode, oldRenderValue, emptyStateValue])
.toStmt());
if (!_animationViewCheckedFlagMap.get(view)) {
@@ -212,13 +216,13 @@ function sanitizedValue(
export function bindRenderInputs(
boundProps: BoundElementPropertyAst[], compileElement: CompileElement): void {
- bindAndWriteToRenderer(boundProps, compileElement.view.componentContext, compileElement);
+ bindAndWriteToRenderer(boundProps, compileElement.view.componentContext, compileElement, false);
}
export function bindDirectiveHostProps(
directiveAst: DirectiveAst, directiveInstance: o.Expression,
compileElement: CompileElement): void {
- bindAndWriteToRenderer(directiveAst.hostProperties, directiveInstance, compileElement);
+ bindAndWriteToRenderer(directiveAst.hostProperties, directiveInstance, compileElement, true);
}
export function bindDirectiveInputs(
@@ -500,14 +500,16 @@ function createViewFactory(
templateUrlInfo = view.component.template.templateUrl;
}
if (view.viewIndex === 0) {
+ var animationsExpr = o.literalMap(view.animations.map(entry => [entry.name, entry.fnVariable]));
initRenderCompTypeStmts = [new o.IfStmt(renderCompTypeVar.identical(o.NULL_EXPR), [
renderCompTypeVar
.set(ViewConstructorVars.viewUtils.callMethod(
'createRenderComponentType',
[
o.literal(templateUrlInfo),
o.literal(view.component.template.ngContentSelectors.length),
- ViewEncapsulationEnum.fromValue(view.component.template.encapsulation), view.styles
+ ViewEncapsulationEnum.fromValue(view.component.template.encapsulation), view.styles,
+ animationsExpr
]))
.toStmt()
])];
@@ -66,21 +66,12 @@ export function main() {
});
it('should throw an error when two or more animation triggers contain the same name', () => {
- var doCompile = () => {
- var t1Data: any[] = [];
- var t2Data: any[] = [];
- compileTriggers([['myTrigger', t1Data], ['myTrigger', t2Data]]);
- };
+ var t1Data: any[] = [];
+ var t2Data: any[] = [];
- var capturedErrorMessage: string;
- try {
- doCompile();
- } catch (e) {
- capturedErrorMessage = e.message;
- }
-
- expect(capturedErrorMessage)
- .toMatch(/The animation trigger "myTrigger" has already been registered on "myCmp"/);
+ expect(() => {
+ compileTriggers([['myTrigger', t1Data], ['myTrigger', t2Data]]);
+ }).toThrowError(/The animation trigger "myTrigger" has already been registered on "myCmp"/);
});
});
}
@@ -298,6 +298,22 @@ export function main() {
].join('\n')]);
});
+ it('should issue a warning when host attributes contain a non property-bound animation trigger',
+ () => {
+ var dirA = CompileDirectiveMetadata.create({
+ selector: 'div',
+ type: new CompileTypeMetadata({moduleUrl: someModuleUrl, name: 'DirA'}),
+ host: {'@prop': 'expr'}
+ });
+
+ humanizeTplAst(parse('<div></div>', [dirA]));
+
+ expect(console.warnings).toEqual([[
+ 'Template parse warnings:',
+ `Assigning animation triggers within host data as attributes such as "@prop": "exp" is deprecated. Use "[@prop]": "exp" instead! ("[ERROR ->]<div></div>"): TestComp@0:0`
+ ].join('\n')]);
+ });
+
it('should not issue a warning when an animation property is bound without an expression',
() => {
humanizeTplAst(parse('<div @something>', []));
@@ -34,11 +34,13 @@ export class ViewUtils {
/**
* Used by the generated code
*/
+ // TODO (matsko): add typing for the animation function
createRenderComponentType(
templateUrl: string, slotCount: number, encapsulation: ViewEncapsulation,
- styles: Array<string|any[]>): RenderComponentType {
+ styles: Array<string|any[]>, animations: {[key: string]: Function}): RenderComponentType {
return new RenderComponentType(
- `${this._appId}-${this._nextCompTypeId++}`, templateUrl, slotCount, encapsulation, styles);
+ `${this._appId}-${this._nextCompTypeId++}`, templateUrl, slotCount, encapsulation, styles,
+ animations);
}
/** @internal */
@@ -13,14 +13,15 @@ import {Injector} from '../di/injector';
import {unimplemented} from '../facade/exceptions';
import {ViewEncapsulation} from '../metadata/view';
-
/**
* @experimental
*/
+// TODO (matsko): add typing for the animation function
export class RenderComponentType {
constructor(
public id: string, public templateUrl: string, public slotCount: number,
- public encapsulation: ViewEncapsulation, public styles: Array<string|any[]>) {}
+ public encapsulation: ViewEncapsulation, public styles: Array<string|any[]>,
+ public animations: {[key: string]: Function}) {}
}
export abstract class RenderDebugInfo {
Oops, something went wrong.

0 comments on commit 806a254

Please sign in to comment.