refactor(animations): ensure animation input/outputs are managed within the template parser (#11782)
Closes #11782 Closes #11601 Related #11707
This commit is contained in:

committed by
Rado Kirov

parent
45ad13560b
commit
f1b6c6efa1
@ -6,75 +6,26 @@
|
|||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {CompileDirectiveMetadata} from '../compile_metadata';
|
|
||||||
import {StringMapWrapper} from '../facade/collection';
|
import {StringMapWrapper} from '../facade/collection';
|
||||||
import {isBlank, isPresent} from '../facade/lang';
|
import {isBlank, isPresent} from '../facade/lang';
|
||||||
import {Identifiers, resolveIdentifier} from '../identifiers';
|
import {Identifiers, resolveIdentifier} from '../identifiers';
|
||||||
import * as o from '../output/output_ast';
|
import * as o from '../output/output_ast';
|
||||||
import {ANY_STATE, AnimationOutput, DEFAULT_STATE, EMPTY_STATE} from '../private_import_core';
|
import {ANY_STATE, DEFAULT_STATE, EMPTY_STATE} from '../private_import_core';
|
||||||
import * as t from '../template_parser/template_ast';
|
|
||||||
|
|
||||||
import {AnimationAst, AnimationAstVisitor, AnimationEntryAst, AnimationGroupAst, AnimationKeyframeAst, AnimationSequenceAst, AnimationStateAst, AnimationStateDeclarationAst, AnimationStateTransitionAst, AnimationStepAst, AnimationStylesAst} from './animation_ast';
|
import {AnimationAst, AnimationAstVisitor, AnimationEntryAst, AnimationGroupAst, AnimationKeyframeAst, AnimationSequenceAst, AnimationStateDeclarationAst, AnimationStateTransitionAst, AnimationStepAst, AnimationStylesAst} from './animation_ast';
|
||||||
import {AnimationParseError, ParsedAnimationResult, parseAnimationEntry, parseAnimationOutputName} from './animation_parser';
|
|
||||||
|
|
||||||
const animationCompilationCache =
|
export class AnimationEntryCompileResult {
|
||||||
new Map<CompileDirectiveMetadata, CompiledAnimationTriggerResult[]>();
|
constructor(public name: string, public statements: o.Statement[], public fnExp: o.Expression) {}
|
||||||
|
|
||||||
export class CompiledAnimationTriggerResult {
|
|
||||||
constructor(
|
|
||||||
public name: string, public statesMapStatement: o.Statement,
|
|
||||||
public statesVariableName: string, public fnStatement: o.Statement,
|
|
||||||
public fnVariable: o.Expression) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class CompiledComponentAnimationResult {
|
|
||||||
constructor(
|
|
||||||
public outputs: AnimationOutput[], public triggers: CompiledAnimationTriggerResult[]) {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AnimationCompiler {
|
export class AnimationCompiler {
|
||||||
compileComponent(component: CompileDirectiveMetadata, template: t.TemplateAst[]):
|
compile(factoryNamePrefix: string, parsedAnimations: AnimationEntryAst[]):
|
||||||
CompiledComponentAnimationResult {
|
AnimationEntryCompileResult[] {
|
||||||
var compiledAnimations: CompiledAnimationTriggerResult[] = [];
|
return parsedAnimations.map(entry => {
|
||||||
var groupedErrors: string[] = [];
|
const factoryName = `${factoryNamePrefix}_${entry.name}`;
|
||||||
var triggerLookup: {[key: string]: CompiledAnimationTriggerResult} = {};
|
const visitor = new _AnimationBuilder(entry.name, factoryName);
|
||||||
var componentName = component.type.name;
|
return visitor.build(entry);
|
||||||
|
|
||||||
component.template.animations.forEach(entry => {
|
|
||||||
var result = parseAnimationEntry(entry);
|
|
||||||
var triggerName = entry.name;
|
|
||||||
if (result.errors.length > 0) {
|
|
||||||
var errorMessage =
|
|
||||||
`Unable to parse the animation sequence for "${triggerName}" due to the following errors:`;
|
|
||||||
result.errors.forEach(
|
|
||||||
(error: AnimationParseError) => { errorMessage += '\n-- ' + error.msg; });
|
|
||||||
groupedErrors.push(errorMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (triggerLookup[triggerName]) {
|
|
||||||
groupedErrors.push(
|
|
||||||
`The animation trigger "${triggerName}" has already been registered on "${componentName}"`);
|
|
||||||
} else {
|
|
||||||
var factoryName = `${componentName}_${entry.name}`;
|
|
||||||
var visitor = new _AnimationBuilder(triggerName, factoryName);
|
|
||||||
var compileResult = visitor.build(result.ast);
|
|
||||||
compiledAnimations.push(compileResult);
|
|
||||||
triggerLookup[entry.name] = compileResult;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
var validatedProperties = _validateAnimationProperties(compiledAnimations, template);
|
|
||||||
validatedProperties.errors.forEach(error => { groupedErrors.push(error.msg); });
|
|
||||||
|
|
||||||
if (groupedErrors.length > 0) {
|
|
||||||
var errorMessageStr =
|
|
||||||
`Animation parsing for ${component.type.name} has failed due to the following errors:`;
|
|
||||||
groupedErrors.forEach(error => errorMessageStr += `\n- ${error}`);
|
|
||||||
throw new Error(errorMessageStr);
|
|
||||||
}
|
|
||||||
|
|
||||||
animationCompilationCache.set(component, compiledAnimations);
|
|
||||||
return new CompiledComponentAnimationResult(validatedProperties.outputs, compiledAnimations);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -334,7 +285,7 @@ class _AnimationBuilder implements AnimationAstVisitor {
|
|||||||
statements);
|
statements);
|
||||||
}
|
}
|
||||||
|
|
||||||
build(ast: AnimationAst): CompiledAnimationTriggerResult {
|
build(ast: AnimationAst): AnimationEntryCompileResult {
|
||||||
var context = new _AnimationBuilderContext();
|
var context = new _AnimationBuilderContext();
|
||||||
var fnStatement = ast.visit(this, context).toDeclStmt(this._fnVarName);
|
var fnStatement = ast.visit(this, context).toDeclStmt(this._fnVarName);
|
||||||
var fnVariable = o.variable(this._fnVarName);
|
var fnVariable = o.variable(this._fnVarName);
|
||||||
@ -353,9 +304,10 @@ class _AnimationBuilder implements AnimationAstVisitor {
|
|||||||
lookupMap.push([stateName, variableValue]);
|
lookupMap.push([stateName, variableValue]);
|
||||||
});
|
});
|
||||||
|
|
||||||
var compiledStatesMapExpr = this._statesMapVar.set(o.literalMap(lookupMap)).toDeclStmt();
|
const compiledStatesMapStmt = this._statesMapVar.set(o.literalMap(lookupMap)).toDeclStmt();
|
||||||
return new CompiledAnimationTriggerResult(
|
const statements: o.Statement[] = [compiledStatesMapStmt, fnStatement];
|
||||||
this.animationName, compiledStatesMapExpr, this._statesMapVarName, fnStatement, fnVariable);
|
|
||||||
|
return new AnimationEntryCompileResult(this.animationName, statements, fnVariable);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -405,99 +357,3 @@ function _isEndStateAnimateStep(step: AnimationAst): boolean {
|
|||||||
function _getStylesArray(obj: any): {[key: string]: any}[] {
|
function _getStylesArray(obj: any): {[key: string]: any}[] {
|
||||||
return obj.styles.styles;
|
return obj.styles.styles;
|
||||||
}
|
}
|
||||||
|
|
||||||
function _validateAnimationProperties(
|
|
||||||
compiledAnimations: CompiledAnimationTriggerResult[],
|
|
||||||
template: t.TemplateAst[]): AnimationPropertyValidationOutput {
|
|
||||||
var visitor = new _AnimationTemplatePropertyVisitor(compiledAnimations);
|
|
||||||
t.templateVisitAll(visitor, template);
|
|
||||||
return new AnimationPropertyValidationOutput(visitor.outputs, visitor.errors);
|
|
||||||
}
|
|
||||||
|
|
||||||
export class AnimationPropertyValidationOutput {
|
|
||||||
constructor(public outputs: AnimationOutput[], public errors: AnimationParseError[]) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _AnimationTemplatePropertyVisitor implements t.TemplateAstVisitor {
|
|
||||||
private _animationRegistry: {[key: string]: boolean};
|
|
||||||
public errors: AnimationParseError[] = [];
|
|
||||||
public outputs: AnimationOutput[] = [];
|
|
||||||
|
|
||||||
constructor(animations: CompiledAnimationTriggerResult[]) {
|
|
||||||
this._animationRegistry = this._buildCompileAnimationLookup(animations);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _buildCompileAnimationLookup(animations: CompiledAnimationTriggerResult[]):
|
|
||||||
{[key: string]: boolean} {
|
|
||||||
var map: {[key: string]: boolean} = {};
|
|
||||||
animations.forEach(entry => { map[entry.name] = true; });
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _validateAnimationInputOutputPairs(
|
|
||||||
inputAsts: t.BoundElementPropertyAst[], outputAsts: t.BoundEventAst[],
|
|
||||||
animationRegistry: {[key: string]: any}, isHostLevel: boolean): void {
|
|
||||||
var detectedAnimationInputs: {[key: string]: boolean} = {};
|
|
||||||
inputAsts.forEach(input => {
|
|
||||||
if (input.type == t.PropertyBindingType.Animation) {
|
|
||||||
var triggerName = input.name;
|
|
||||||
if (isPresent(animationRegistry[triggerName])) {
|
|
||||||
detectedAnimationInputs[triggerName] = true;
|
|
||||||
} else {
|
|
||||||
this.errors.push(
|
|
||||||
new AnimationParseError(`Couldn't find an animation entry for ${triggerName}`));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
outputAsts.forEach(output => {
|
|
||||||
if (output.name[0] == '@') {
|
|
||||||
var normalizedOutputData = parseAnimationOutputName(output.name.substr(1), this.errors);
|
|
||||||
let triggerName = normalizedOutputData.name;
|
|
||||||
let triggerEventPhase = normalizedOutputData.phase;
|
|
||||||
if (!animationRegistry[triggerName]) {
|
|
||||||
this.errors.push(new AnimationParseError(
|
|
||||||
`Couldn't find the corresponding ${isHostLevel ? 'host-level ' : '' }animation trigger definition for (@${triggerName})`));
|
|
||||||
} else if (!detectedAnimationInputs[triggerName]) {
|
|
||||||
this.errors.push(new AnimationParseError(
|
|
||||||
`Unable to listen on (@${triggerName}.${triggerEventPhase}) because the animation trigger [@${triggerName}] isn't being used on the same element`));
|
|
||||||
} else {
|
|
||||||
this.outputs.push(normalizedOutputData);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
visitElement(ast: t.ElementAst, ctx: any): any {
|
|
||||||
this._validateAnimationInputOutputPairs(
|
|
||||||
ast.inputs, ast.outputs, this._animationRegistry, false);
|
|
||||||
|
|
||||||
var componentOnElement: t.DirectiveAst =
|
|
||||||
ast.directives.find(directive => directive.directive.isComponent);
|
|
||||||
if (componentOnElement) {
|
|
||||||
let cachedComponentAnimations = animationCompilationCache.get(componentOnElement.directive);
|
|
||||||
if (cachedComponentAnimations) {
|
|
||||||
this._validateAnimationInputOutputPairs(
|
|
||||||
componentOnElement.hostProperties, componentOnElement.hostEvents,
|
|
||||||
this._buildCompileAnimationLookup(cachedComponentAnimations), true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
t.templateVisitAll(this, ast.children);
|
|
||||||
}
|
|
||||||
|
|
||||||
visitEmbeddedTemplate(ast: t.EmbeddedTemplateAst, ctx: any): any {
|
|
||||||
t.templateVisitAll(this, ast.children);
|
|
||||||
}
|
|
||||||
|
|
||||||
visitEvent(ast: t.BoundEventAst, ctx: any): any {}
|
|
||||||
visitBoundText(ast: t.BoundTextAst, ctx: any): any {}
|
|
||||||
visitText(ast: t.TextAst, ctx: any): any {}
|
|
||||||
visitNgContent(ast: t.NgContentAst, ctx: any): any {}
|
|
||||||
visitAttr(ast: t.AttrAst, ctx: any): any {}
|
|
||||||
visitDirective(ast: t.DirectiveAst, 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 {}
|
|
||||||
}
|
|
||||||
|
@ -6,13 +6,13 @@
|
|||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {CompileAnimationAnimateMetadata, CompileAnimationEntryMetadata, CompileAnimationGroupMetadata, CompileAnimationKeyframesSequenceMetadata, CompileAnimationMetadata, CompileAnimationSequenceMetadata, CompileAnimationStateDeclarationMetadata, CompileAnimationStateTransitionMetadata, CompileAnimationStyleMetadata, CompileAnimationWithStepsMetadata} from '../compile_metadata';
|
import {CompileAnimationAnimateMetadata, CompileAnimationEntryMetadata, CompileAnimationGroupMetadata, CompileAnimationKeyframesSequenceMetadata, CompileAnimationMetadata, CompileAnimationSequenceMetadata, CompileAnimationStateDeclarationMetadata, CompileAnimationStateTransitionMetadata, CompileAnimationStyleMetadata, CompileAnimationWithStepsMetadata, CompileDirectiveMetadata} from '../compile_metadata';
|
||||||
import {ListWrapper, StringMapWrapper} from '../facade/collection';
|
import {ListWrapper, StringMapWrapper} from '../facade/collection';
|
||||||
import {isArray, isBlank, isPresent, isString, isStringMap} from '../facade/lang';
|
import {isArray, isBlank, isPresent, isString, isStringMap} from '../facade/lang';
|
||||||
import {Math} from '../facade/math';
|
import {Math} from '../facade/math';
|
||||||
import {ParseError} from '../parse_util';
|
import {ParseError} from '../parse_util';
|
||||||
|
import {ANY_STATE, FILL_STYLE_FLAG} from '../private_import_core';
|
||||||
|
|
||||||
import {ANY_STATE, AnimationOutput, FILL_STYLE_FLAG} from '../private_import_core';
|
|
||||||
import {AnimationAst, AnimationEntryAst, AnimationGroupAst, AnimationKeyframeAst, AnimationSequenceAst, AnimationStateDeclarationAst, AnimationStateTransitionAst, AnimationStateTransitionExpression, AnimationStepAst, AnimationStylesAst, AnimationWithStepsAst} from './animation_ast';
|
import {AnimationAst, AnimationEntryAst, AnimationGroupAst, AnimationKeyframeAst, AnimationSequenceAst, AnimationStateDeclarationAst, AnimationStateTransitionAst, AnimationStateTransitionExpression, AnimationStepAst, AnimationStylesAst, AnimationWithStepsAst} from './animation_ast';
|
||||||
import {StylesCollection} from './styles_collection';
|
import {StylesCollection} from './styles_collection';
|
||||||
|
|
||||||
@ -21,62 +21,70 @@ const _TERMINAL_KEYFRAME = 1;
|
|||||||
const _ONE_SECOND = 1000;
|
const _ONE_SECOND = 1000;
|
||||||
|
|
||||||
export class AnimationParseError extends ParseError {
|
export class AnimationParseError extends ParseError {
|
||||||
constructor(message: any /** TODO #9100 */) { super(null, message); }
|
constructor(message: string) { super(null, message); }
|
||||||
toString(): string { return `${this.msg}`; }
|
toString(): string { return `${this.msg}`; }
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ParsedAnimationResult {
|
export class AnimationEntryParseResult {
|
||||||
constructor(public ast: AnimationEntryAst, public errors: AnimationParseError[]) {}
|
constructor(public ast: AnimationEntryAst, public errors: AnimationParseError[]) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parseAnimationEntry(entry: CompileAnimationEntryMetadata): ParsedAnimationResult {
|
export class AnimationParser {
|
||||||
var errors: AnimationParseError[] = [];
|
parseComponent(component: CompileDirectiveMetadata): AnimationEntryAst[] {
|
||||||
var stateStyles: {[key: string]: AnimationStylesAst} = {};
|
const errors: string[] = [];
|
||||||
var transitions: CompileAnimationStateTransitionMetadata[] = [];
|
const componentName = component.type.name;
|
||||||
|
const animationTriggerNames = new Set<string>();
|
||||||
|
const asts = component.template.animations.map(entry => {
|
||||||
|
const result = this.parseEntry(entry);
|
||||||
|
const ast = result.ast;
|
||||||
|
const triggerName = ast.name;
|
||||||
|
if (animationTriggerNames.has(triggerName)) {
|
||||||
|
result.errors.push(new AnimationParseError(
|
||||||
|
`The animation trigger "${triggerName}" has already been registered for the ${componentName} component`));
|
||||||
|
} else {
|
||||||
|
animationTriggerNames.add(triggerName);
|
||||||
|
}
|
||||||
|
if (result.errors.length > 0) {
|
||||||
|
let errorMessage =
|
||||||
|
`- Unable to parse the animation sequence for "${triggerName}" on the ${componentName} component due to the following errors:`;
|
||||||
|
result.errors.forEach(
|
||||||
|
(error: AnimationParseError) => { errorMessage += '\n-- ' + error.msg; });
|
||||||
|
errors.push(errorMessage);
|
||||||
|
}
|
||||||
|
return ast;
|
||||||
|
});
|
||||||
|
|
||||||
var stateDeclarationAsts: any[] /** TODO #9100 */ = [];
|
if (errors.length > 0) {
|
||||||
entry.definitions.forEach(def => {
|
const errorString = errors.join('\n');
|
||||||
if (def instanceof CompileAnimationStateDeclarationMetadata) {
|
throw new Error(`Animation parse errors:\n${errorString}`);
|
||||||
_parseAnimationDeclarationStates(def, errors).forEach(ast => {
|
|
||||||
stateDeclarationAsts.push(ast);
|
|
||||||
stateStyles[ast.stateName] = ast.styles;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
transitions.push(<CompileAnimationStateTransitionMetadata>def);
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
var stateTransitionAsts =
|
return asts;
|
||||||
transitions.map(transDef => _parseAnimationStateTransition(transDef, stateStyles, errors));
|
}
|
||||||
|
|
||||||
var ast = new AnimationEntryAst(entry.name, stateDeclarationAsts, stateTransitionAsts);
|
parseEntry(entry: CompileAnimationEntryMetadata): AnimationEntryParseResult {
|
||||||
return new ParsedAnimationResult(ast, errors);
|
var errors: AnimationParseError[] = [];
|
||||||
}
|
var stateStyles: {[key: string]: AnimationStylesAst} = {};
|
||||||
|
var transitions: CompileAnimationStateTransitionMetadata[] = [];
|
||||||
export function parseAnimationOutputName(
|
|
||||||
outputName: string, errors: AnimationParseError[]): AnimationOutput {
|
var stateDeclarationAsts: AnimationStateDeclarationAst[] = [];
|
||||||
var values = outputName.split('.');
|
entry.definitions.forEach(def => {
|
||||||
var name: string;
|
if (def instanceof CompileAnimationStateDeclarationMetadata) {
|
||||||
var phase: string = '';
|
_parseAnimationDeclarationStates(def, errors).forEach(ast => {
|
||||||
if (values.length > 1) {
|
stateDeclarationAsts.push(ast);
|
||||||
name = values[0];
|
stateStyles[ast.stateName] = ast.styles;
|
||||||
let parsedPhase = values[1];
|
});
|
||||||
switch (parsedPhase) {
|
} else {
|
||||||
case 'start':
|
transitions.push(<CompileAnimationStateTransitionMetadata>def);
|
||||||
case 'done':
|
}
|
||||||
phase = parsedPhase;
|
});
|
||||||
break;
|
|
||||||
|
var stateTransitionAsts =
|
||||||
default:
|
transitions.map(transDef => _parseAnimationStateTransition(transDef, stateStyles, errors));
|
||||||
errors.push(new AnimationParseError(
|
|
||||||
`The provided animation output phase value "${parsedPhase}" for "@${name}" is not supported (use start or done)`));
|
var ast = new AnimationEntryAst(entry.name, stateDeclarationAsts, stateTransitionAsts);
|
||||||
}
|
return new AnimationEntryParseResult(ast, errors);
|
||||||
} else {
|
|
||||||
name = outputName;
|
|
||||||
errors.push(new AnimationParseError(
|
|
||||||
`The animation trigger output event (@${name}) is missing its phase value name (start or done are currently supported)`));
|
|
||||||
}
|
}
|
||||||
return new AnimationOutput(name, phase, outputName);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function _parseAnimationDeclarationStates(
|
function _parseAnimationDeclarationStates(
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
import {ANALYZE_FOR_ENTRY_COMPONENTS, ChangeDetectionStrategy, ChangeDetectorRef, ComponentFactory, ComponentFactoryResolver, ElementRef, Injector, LOCALE_ID as LOCALE_ID_, NgModuleFactory, QueryList, RenderComponentType, Renderer, SecurityContext, SimpleChange, TRANSLATIONS_FORMAT as TRANSLATIONS_FORMAT_, TemplateRef, ViewContainerRef, ViewEncapsulation} from '@angular/core';
|
import {ANALYZE_FOR_ENTRY_COMPONENTS, ChangeDetectionStrategy, ChangeDetectorRef, ComponentFactory, ComponentFactoryResolver, ElementRef, Injector, LOCALE_ID as LOCALE_ID_, NgModuleFactory, QueryList, RenderComponentType, Renderer, SecurityContext, SimpleChange, TRANSLATIONS_FORMAT as TRANSLATIONS_FORMAT_, TemplateRef, ViewContainerRef, ViewEncapsulation} from '@angular/core';
|
||||||
|
|
||||||
import {CompileIdentifierMetadata, CompileTokenMetadata} from './compile_metadata';
|
import {CompileIdentifierMetadata, CompileTokenMetadata} from './compile_metadata';
|
||||||
import {AnimationGroupPlayer, AnimationKeyframe, AnimationOutput, AnimationSequencePlayer, AnimationStyles, AppElement, AppView, ChangeDetectorStatus, CodegenComponentFactoryResolver, DebugAppView, DebugContext, EMPTY_ARRAY, EMPTY_MAP, NgModuleInjector, NoOpAnimationPlayer, StaticNodeDebugInfo, TemplateRef_, UNINITIALIZED, ValueUnwrapper, ViewType, ViewUtils, balanceAnimationKeyframes, castByValue, checkBinding, clearStyles, collectAndResolveStyles, devModeEqual, flattenNestedViewRenderNodes, interpolate, prepareFinalAnimationStyles, pureProxy1, pureProxy10, pureProxy2, pureProxy3, pureProxy4, pureProxy5, pureProxy6, pureProxy7, pureProxy8, pureProxy9, reflector, registerModuleFactory, renderStyles} from './private_import_core';
|
import {AnimationGroupPlayer, AnimationKeyframe, AnimationSequencePlayer, AnimationStyles, AppElement, AppView, ChangeDetectorStatus, CodegenComponentFactoryResolver, DebugAppView, DebugContext, EMPTY_ARRAY, EMPTY_MAP, NgModuleInjector, NoOpAnimationPlayer, StaticNodeDebugInfo, TemplateRef_, UNINITIALIZED, ValueUnwrapper, ViewType, ViewUtils, balanceAnimationKeyframes, castByValue, checkBinding, clearStyles, collectAndResolveStyles, devModeEqual, flattenNestedViewRenderNodes, interpolate, prepareFinalAnimationStyles, pureProxy1, pureProxy10, pureProxy2, pureProxy3, pureProxy4, pureProxy5, pureProxy6, pureProxy7, pureProxy8, pureProxy9, reflector, registerModuleFactory, renderStyles} from './private_import_core';
|
||||||
import {assetUrl} from './util';
|
import {assetUrl} from './util';
|
||||||
|
|
||||||
var APP_VIEW_MODULE_URL = assetUrl('core', 'linker/view');
|
var APP_VIEW_MODULE_URL = assetUrl('core', 'linker/view');
|
||||||
@ -266,11 +266,6 @@ export class Identifiers {
|
|||||||
moduleUrl: assetUrl('core', 'i18n/tokens'),
|
moduleUrl: assetUrl('core', 'i18n/tokens'),
|
||||||
runtime: TRANSLATIONS_FORMAT_
|
runtime: TRANSLATIONS_FORMAT_
|
||||||
};
|
};
|
||||||
static AnimationOutput: IdentifierSpec = {
|
|
||||||
name: 'AnimationOutput',
|
|
||||||
moduleUrl: assetUrl('core', 'animation/animation_output'),
|
|
||||||
runtime: AnimationOutput
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function resolveIdentifier(identifier: IdentifierSpec) {
|
export function resolveIdentifier(identifier: IdentifierSpec) {
|
||||||
|
@ -8,6 +8,8 @@
|
|||||||
|
|
||||||
import {SchemaMetadata} from '@angular/core';
|
import {SchemaMetadata} from '@angular/core';
|
||||||
|
|
||||||
|
import {AnimationCompiler} from './animation/animation_compiler';
|
||||||
|
import {AnimationParser} from './animation/animation_parser';
|
||||||
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompilePipeMetadata, CompileProviderMetadata, CompileTokenMetadata, StaticSymbol, createHostComponentMeta} from './compile_metadata';
|
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompilePipeMetadata, CompileProviderMetadata, CompileTokenMetadata, StaticSymbol, createHostComponentMeta} from './compile_metadata';
|
||||||
import {DirectiveNormalizer} from './directive_normalizer';
|
import {DirectiveNormalizer} from './directive_normalizer';
|
||||||
import {Identifiers, resolveIdentifier, resolveIdentifierToken} from './identifiers';
|
import {Identifiers, resolveIdentifier, resolveIdentifierToken} from './identifiers';
|
||||||
@ -28,6 +30,9 @@ export class NgModulesSummary {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class OfflineCompiler {
|
export class OfflineCompiler {
|
||||||
|
private _animationParser = new AnimationParser();
|
||||||
|
private _animationCompiler = new AnimationCompiler();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private _metadataResolver: CompileMetadataResolver,
|
private _metadataResolver: CompileMetadataResolver,
|
||||||
private _directiveNormalizer: DirectiveNormalizer, private _templateParser: TemplateParser,
|
private _directiveNormalizer: DirectiveNormalizer, private _templateParser: TemplateParser,
|
||||||
@ -162,14 +167,19 @@ export class OfflineCompiler {
|
|||||||
compMeta: CompileDirectiveMetadata, directives: CompileDirectiveMetadata[],
|
compMeta: CompileDirectiveMetadata, directives: CompileDirectiveMetadata[],
|
||||||
pipes: CompilePipeMetadata[], schemas: SchemaMetadata[], componentStyles: CompiledStylesheet,
|
pipes: CompilePipeMetadata[], schemas: SchemaMetadata[], componentStyles: CompiledStylesheet,
|
||||||
fileSuffix: string, targetStatements: o.Statement[]): string {
|
fileSuffix: string, targetStatements: o.Statement[]): string {
|
||||||
|
const parsedAnimations = this._animationParser.parseComponent(compMeta);
|
||||||
const parsedTemplate = this._templateParser.parse(
|
const parsedTemplate = this._templateParser.parse(
|
||||||
compMeta, compMeta.template.template, directives, pipes, schemas, compMeta.type.name);
|
compMeta, compMeta.template.template, directives, pipes, schemas, compMeta.type.name);
|
||||||
const stylesExpr = componentStyles ? o.variable(componentStyles.stylesVar) : o.literalArr([]);
|
const stylesExpr = componentStyles ? o.variable(componentStyles.stylesVar) : o.literalArr([]);
|
||||||
const viewResult =
|
const compiledAnimations =
|
||||||
this._viewCompiler.compileComponent(compMeta, parsedTemplate, stylesExpr, pipes);
|
this._animationCompiler.compile(compMeta.type.name, parsedAnimations);
|
||||||
|
const viewResult = this._viewCompiler.compileComponent(
|
||||||
|
compMeta, parsedTemplate, stylesExpr, pipes, compiledAnimations);
|
||||||
if (componentStyles) {
|
if (componentStyles) {
|
||||||
targetStatements.push(..._resolveStyleStatements(componentStyles, fileSuffix));
|
targetStatements.push(..._resolveStyleStatements(componentStyles, fileSuffix));
|
||||||
}
|
}
|
||||||
|
compiledAnimations.forEach(
|
||||||
|
entry => { entry.statements.forEach(statement => { targetStatements.push(statement); }); });
|
||||||
targetStatements.push(..._resolveViewStatements(viewResult));
|
targetStatements.push(..._resolveViewStatements(viewResult));
|
||||||
return viewResult.viewFactoryVar;
|
return viewResult.viewFactoryVar;
|
||||||
}
|
}
|
||||||
|
@ -75,8 +75,6 @@ export type AnimationKeyframe = typeof r._AnimationKeyframe;
|
|||||||
export const AnimationKeyframe: typeof r.AnimationKeyframe = r.AnimationKeyframe;
|
export const AnimationKeyframe: typeof r.AnimationKeyframe = r.AnimationKeyframe;
|
||||||
export type AnimationStyles = typeof r._AnimationStyles;
|
export type AnimationStyles = typeof r._AnimationStyles;
|
||||||
export const AnimationStyles: typeof r.AnimationStyles = r.AnimationStyles;
|
export const AnimationStyles: typeof r.AnimationStyles = r.AnimationStyles;
|
||||||
export type AnimationOutput = typeof r._AnimationOutput;
|
|
||||||
export const AnimationOutput: typeof r.AnimationOutput = r.AnimationOutput;
|
|
||||||
export const ANY_STATE = r.ANY_STATE;
|
export const ANY_STATE = r.ANY_STATE;
|
||||||
export const DEFAULT_STATE = r.DEFAULT_STATE;
|
export const DEFAULT_STATE = r.DEFAULT_STATE;
|
||||||
export const EMPTY_STATE = r.EMPTY_STATE;
|
export const EMPTY_STATE = r.EMPTY_STATE;
|
||||||
|
@ -50,14 +50,14 @@ export class ProviderElementContext {
|
|||||||
private _hasViewContainer: boolean = false;
|
private _hasViewContainer: boolean = false;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private _viewContext: ProviderViewContext, private _parent: ProviderElementContext,
|
public viewContext: ProviderViewContext, private _parent: ProviderElementContext,
|
||||||
private _isViewRoot: boolean, private _directiveAsts: DirectiveAst[], attrs: AttrAst[],
|
private _isViewRoot: boolean, private _directiveAsts: DirectiveAst[], attrs: AttrAst[],
|
||||||
refs: ReferenceAst[], private _sourceSpan: ParseSourceSpan) {
|
refs: ReferenceAst[], private _sourceSpan: ParseSourceSpan) {
|
||||||
this._attrs = {};
|
this._attrs = {};
|
||||||
attrs.forEach((attrAst) => this._attrs[attrAst.name] = attrAst.value);
|
attrs.forEach((attrAst) => this._attrs[attrAst.name] = attrAst.value);
|
||||||
var directivesMeta = _directiveAsts.map(directiveAst => directiveAst.directive);
|
var directivesMeta = _directiveAsts.map(directiveAst => directiveAst.directive);
|
||||||
this._allProviders =
|
this._allProviders =
|
||||||
_resolveProvidersFromDirectives(directivesMeta, _sourceSpan, _viewContext.errors);
|
_resolveProvidersFromDirectives(directivesMeta, _sourceSpan, viewContext.errors);
|
||||||
this._contentQueries = _getContentQueries(directivesMeta);
|
this._contentQueries = _getContentQueries(directivesMeta);
|
||||||
var queriedTokens = new Map<any, boolean>();
|
var queriedTokens = new Map<any, boolean>();
|
||||||
MapWrapper.values(this._allProviders).forEach((provider) => {
|
MapWrapper.values(this._allProviders).forEach((provider) => {
|
||||||
@ -124,7 +124,7 @@ export class ProviderElementContext {
|
|||||||
}
|
}
|
||||||
currentEl = currentEl._parent;
|
currentEl = currentEl._parent;
|
||||||
}
|
}
|
||||||
queries = this._viewContext.viewQueries.get(token.reference);
|
queries = this.viewContext.viewQueries.get(token.reference);
|
||||||
if (isPresent(queries)) {
|
if (isPresent(queries)) {
|
||||||
ListWrapper.addAll(result, queries);
|
ListWrapper.addAll(result, queries);
|
||||||
}
|
}
|
||||||
@ -150,7 +150,7 @@ export class ProviderElementContext {
|
|||||||
return transformedProviderAst;
|
return transformedProviderAst;
|
||||||
}
|
}
|
||||||
if (isPresent(this._seenProviders.get(token.reference))) {
|
if (isPresent(this._seenProviders.get(token.reference))) {
|
||||||
this._viewContext.errors.push(new ProviderError(
|
this.viewContext.errors.push(new ProviderError(
|
||||||
`Cannot instantiate cyclic dependency! ${token.name}`, this._sourceSpan));
|
`Cannot instantiate cyclic dependency! ${token.name}`, this._sourceSpan));
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -254,9 +254,9 @@ export class ProviderElementContext {
|
|||||||
}
|
}
|
||||||
// check @Host restriction
|
// check @Host restriction
|
||||||
if (isBlank(result)) {
|
if (isBlank(result)) {
|
||||||
if (!dep.isHost || this._viewContext.component.type.isHost ||
|
if (!dep.isHost || this.viewContext.component.type.isHost ||
|
||||||
this._viewContext.component.type.reference === dep.token.reference ||
|
this.viewContext.component.type.reference === dep.token.reference ||
|
||||||
isPresent(this._viewContext.viewProviders.get(dep.token.reference))) {
|
isPresent(this.viewContext.viewProviders.get(dep.token.reference))) {
|
||||||
result = dep;
|
result = dep;
|
||||||
} else {
|
} else {
|
||||||
result = dep.isOptional ?
|
result = dep.isOptional ?
|
||||||
@ -266,7 +266,7 @@ export class ProviderElementContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (isBlank(result)) {
|
if (isBlank(result)) {
|
||||||
this._viewContext.errors.push(
|
this.viewContext.errors.push(
|
||||||
new ProviderError(`No provider for ${dep.token.name}`, this._sourceSpan));
|
new ProviderError(`No provider for ${dep.token.name}`, this._sourceSpan));
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
|
@ -7,7 +7,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {Compiler, ComponentFactory, Injectable, Injector, ModuleWithComponentFactories, NgModuleFactory, Optional, Provider, SchemaMetadata, SkipSelf, Type} from '@angular/core';
|
import {Compiler, ComponentFactory, Injectable, Injector, ModuleWithComponentFactories, NgModuleFactory, Optional, Provider, SchemaMetadata, SkipSelf, Type} from '@angular/core';
|
||||||
|
import {AnimationCompiler} from './animation/animation_compiler';
|
||||||
|
import {AnimationParser} from './animation/animation_parser';
|
||||||
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompilePipeMetadata, ProviderMeta, createHostComponentMeta} from './compile_metadata';
|
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompilePipeMetadata, ProviderMeta, createHostComponentMeta} from './compile_metadata';
|
||||||
import {CompilerConfig} from './config';
|
import {CompilerConfig} from './config';
|
||||||
import {DirectiveNormalizer} from './directive_normalizer';
|
import {DirectiveNormalizer} from './directive_normalizer';
|
||||||
@ -23,8 +24,6 @@ import {TemplateParser} from './template_parser/template_parser';
|
|||||||
import {SyncAsyncResult} from './util';
|
import {SyncAsyncResult} from './util';
|
||||||
import {ComponentFactoryDependency, ViewCompiler, ViewFactoryDependency} from './view_compiler/view_compiler';
|
import {ComponentFactoryDependency, ViewCompiler, ViewFactoryDependency} from './view_compiler/view_compiler';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An internal module of the Angular compiler that begins with component types,
|
* An internal module of the Angular compiler that begins with component types,
|
||||||
* extracts templates, and eventually produces a compiled version of the component
|
* extracts templates, and eventually produces a compiled version of the component
|
||||||
@ -39,6 +38,8 @@ export class RuntimeCompiler implements Compiler {
|
|||||||
private _compiledTemplateCache = new Map<Type<any>, CompiledTemplate>();
|
private _compiledTemplateCache = new Map<Type<any>, CompiledTemplate>();
|
||||||
private _compiledHostTemplateCache = new Map<Type<any>, CompiledTemplate>();
|
private _compiledHostTemplateCache = new Map<Type<any>, CompiledTemplate>();
|
||||||
private _compiledNgModuleCache = new Map<Type<any>, NgModuleFactory<any>>();
|
private _compiledNgModuleCache = new Map<Type<any>, NgModuleFactory<any>>();
|
||||||
|
private _animationParser = new AnimationParser();
|
||||||
|
private _animationCompiler = new AnimationCompiler();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private _injector: Injector, private _metadataResolver: CompileMetadataResolver,
|
private _injector: Injector, private _metadataResolver: CompileMetadataResolver,
|
||||||
@ -253,12 +254,15 @@ export class RuntimeCompiler implements Compiler {
|
|||||||
stylesCompileResult.componentStylesheet, externalStylesheetsByModuleUrl);
|
stylesCompileResult.componentStylesheet, externalStylesheetsByModuleUrl);
|
||||||
const viewCompMetas = template.viewComponentTypes.map(
|
const viewCompMetas = template.viewComponentTypes.map(
|
||||||
(compType) => this._assertComponentLoaded(compType, false).normalizedCompMeta);
|
(compType) => this._assertComponentLoaded(compType, false).normalizedCompMeta);
|
||||||
|
const parsedAnimations = this._animationParser.parseComponent(compMeta);
|
||||||
const parsedTemplate = this._templateParser.parse(
|
const parsedTemplate = this._templateParser.parse(
|
||||||
compMeta, compMeta.template.template, template.viewDirectives.concat(viewCompMetas),
|
compMeta, compMeta.template.template, template.viewDirectives.concat(viewCompMetas),
|
||||||
template.viewPipes, template.schemas, compMeta.type.name);
|
template.viewPipes, template.schemas, compMeta.type.name);
|
||||||
|
const compiledAnimations =
|
||||||
|
this._animationCompiler.compile(compMeta.type.name, parsedAnimations);
|
||||||
const compileResult = this._viewCompiler.compileComponent(
|
const compileResult = this._viewCompiler.compileComponent(
|
||||||
compMeta, parsedTemplate, ir.variable(stylesCompileResult.componentStylesheet.stylesVar),
|
compMeta, parsedTemplate, ir.variable(stylesCompileResult.componentStylesheet.stylesVar),
|
||||||
template.viewPipes);
|
template.viewPipes, compiledAnimations);
|
||||||
compileResult.dependencies.forEach((dep) => {
|
compileResult.dependencies.forEach((dep) => {
|
||||||
let depTemplate: CompiledTemplate;
|
let depTemplate: CompiledTemplate;
|
||||||
if (dep instanceof ViewFactoryDependency) {
|
if (dep instanceof ViewFactoryDependency) {
|
||||||
@ -275,6 +279,8 @@ export class RuntimeCompiler implements Compiler {
|
|||||||
});
|
});
|
||||||
const statements =
|
const statements =
|
||||||
stylesCompileResult.componentStylesheet.statements.concat(compileResult.statements);
|
stylesCompileResult.componentStylesheet.statements.concat(compileResult.statements);
|
||||||
|
compiledAnimations.forEach(
|
||||||
|
entry => { entry.statements.forEach(statement => { statements.push(statement); }); });
|
||||||
let factory: any;
|
let factory: any;
|
||||||
if (!this._compilerConfig.useJit) {
|
if (!this._compilerConfig.useJit) {
|
||||||
factory = interpretStatements(statements, compileResult.viewFactoryVar);
|
factory = interpretStatements(statements, compileResult.viewFactoryVar);
|
||||||
|
@ -12,11 +12,8 @@ import {CompileDirectiveMetadata, CompileProviderMetadata, CompileTokenMetadata}
|
|||||||
import {AST} from '../expression_parser/ast';
|
import {AST} from '../expression_parser/ast';
|
||||||
import {isPresent} from '../facade/lang';
|
import {isPresent} from '../facade/lang';
|
||||||
import {ParseSourceSpan} from '../parse_util';
|
import {ParseSourceSpan} from '../parse_util';
|
||||||
|
|
||||||
import {LifecycleHooks} from '../private_import_core';
|
import {LifecycleHooks} from '../private_import_core';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An Abstract Syntax Tree node representing part of a parsed Angular template.
|
* An Abstract Syntax Tree node representing part of a parsed Angular template.
|
||||||
*/
|
*/
|
||||||
@ -61,7 +58,8 @@ export class AttrAst implements TemplateAst {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A binding for an element property (e.g. `[property]="expression"`).
|
* A binding for an element property (e.g. `[property]="expression"`) or an animation trigger (e.g.
|
||||||
|
* `[@trigger]="stateExp"`)
|
||||||
*/
|
*/
|
||||||
export class BoundElementPropertyAst implements TemplateAst {
|
export class BoundElementPropertyAst implements TemplateAst {
|
||||||
constructor(
|
constructor(
|
||||||
@ -71,14 +69,16 @@ export class BoundElementPropertyAst implements TemplateAst {
|
|||||||
visit(visitor: TemplateAstVisitor, context: any): any {
|
visit(visitor: TemplateAstVisitor, context: any): any {
|
||||||
return visitor.visitElementProperty(this, context);
|
return visitor.visitElementProperty(this, context);
|
||||||
}
|
}
|
||||||
|
get isAnimation(): boolean { return this.type === PropertyBindingType.Animation; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A binding for an element event (e.g. `(event)="handler()"`).
|
* A binding for an element event (e.g. `(event)="handler()"`) or an animation trigger event (e.g.
|
||||||
|
* `(@trigger.phase)="callback($event)"`).
|
||||||
*/
|
*/
|
||||||
export class BoundEventAst implements TemplateAst {
|
export class BoundEventAst implements TemplateAst {
|
||||||
constructor(
|
constructor(
|
||||||
public name: string, public target: string, public handler: AST,
|
public name: string, public target: string, public phase: string, public handler: AST,
|
||||||
public sourceSpan: ParseSourceSpan) {}
|
public sourceSpan: ParseSourceSpan) {}
|
||||||
visit(visitor: TemplateAstVisitor, context: any): any {
|
visit(visitor: TemplateAstVisitor, context: any): any {
|
||||||
return visitor.visitEvent(this, context);
|
return visitor.visitEvent(this, context);
|
||||||
@ -90,6 +90,7 @@ export class BoundEventAst implements TemplateAst {
|
|||||||
return this.name;
|
return this.name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
get isAnimation(): boolean { return !!this.phase; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
import {Inject, Injectable, OpaqueToken, Optional, SchemaMetadata, SecurityContext} from '@angular/core';
|
import {Inject, Injectable, OpaqueToken, Optional, SchemaMetadata, SecurityContext} from '@angular/core';
|
||||||
|
|
||||||
import {CompileDirectiveMetadata, CompilePipeMetadata, CompileTokenMetadata, removeIdentifierDuplicates} from '../compile_metadata';
|
import {CompileDirectiveMetadata, CompilePipeMetadata, CompileTemplateMetadata, CompileTokenMetadata, removeIdentifierDuplicates} from '../compile_metadata';
|
||||||
import {AST, ASTWithSource, BindingPipe, EmptyExpr, Interpolation, ParserError, RecursiveAstVisitor, TemplateBinding} from '../expression_parser/ast';
|
import {AST, ASTWithSource, BindingPipe, EmptyExpr, Interpolation, ParserError, RecursiveAstVisitor, TemplateBinding} from '../expression_parser/ast';
|
||||||
import {Parser} from '../expression_parser/parser';
|
import {Parser} from '../expression_parser/parser';
|
||||||
import {StringMapWrapper} from '../facade/collection';
|
import {StringMapWrapper} from '../facade/collection';
|
||||||
@ -26,12 +26,13 @@ import {ProviderElementContext, ProviderViewContext} from '../provider_analyzer'
|
|||||||
import {ElementSchemaRegistry} from '../schema/element_schema_registry';
|
import {ElementSchemaRegistry} from '../schema/element_schema_registry';
|
||||||
import {CssSelector, SelectorMatcher} from '../selector';
|
import {CssSelector, SelectorMatcher} from '../selector';
|
||||||
import {isStyleUrlResolvable} from '../style_url_resolver';
|
import {isStyleUrlResolvable} from '../style_url_resolver';
|
||||||
import {splitAtColon} from '../util';
|
import {splitAtColon, splitAtPeriod} from '../util';
|
||||||
|
|
||||||
import {AttrAst, BoundDirectivePropertyAst, BoundElementPropertyAst, BoundEventAst, BoundTextAst, DirectiveAst, ElementAst, EmbeddedTemplateAst, NgContentAst, PropertyBindingType, ReferenceAst, TemplateAst, TemplateAstVisitor, TextAst, VariableAst, templateVisitAll} from './template_ast';
|
import {AttrAst, BoundDirectivePropertyAst, BoundElementPropertyAst, BoundEventAst, BoundTextAst, DirectiveAst, ElementAst, EmbeddedTemplateAst, NgContentAst, PropertyBindingType, ReferenceAst, TemplateAst, TemplateAstVisitor, TextAst, VariableAst, templateVisitAll} from './template_ast';
|
||||||
import {PreparsedElementType, preparseElement} from './template_preparser';
|
import {PreparsedElementType, preparseElement} from './template_preparser';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Group 1 = "bind-"
|
// Group 1 = "bind-"
|
||||||
// Group 2 = "let-"
|
// Group 2 = "let-"
|
||||||
// Group 3 = "ref-/#"
|
// Group 3 = "ref-/#"
|
||||||
@ -142,7 +143,6 @@ export class TemplateParser {
|
|||||||
const parseVisitor = new TemplateParseVisitor(
|
const parseVisitor = new TemplateParseVisitor(
|
||||||
providerViewContext, uniqDirectives, uniqPipes, schemas, this._exprParser,
|
providerViewContext, uniqDirectives, uniqPipes, schemas, this._exprParser,
|
||||||
this._schemaRegistry);
|
this._schemaRegistry);
|
||||||
|
|
||||||
result = html.visitAll(parseVisitor, htmlAstWithErrors.rootNodes, EMPTY_ELEMENT_CONTEXT);
|
result = html.visitAll(parseVisitor, htmlAstWithErrors.rootNodes, EMPTY_ELEMENT_CONTEXT);
|
||||||
errors.push(...parseVisitor.errors, ...providerViewContext.errors);
|
errors.push(...parseVisitor.errors, ...providerViewContext.errors);
|
||||||
} else {
|
} else {
|
||||||
@ -444,6 +444,15 @@ class TemplateParseVisitor implements html.Visitor {
|
|||||||
providerContext.transformedDirectiveAsts, providerContext.transformProviders,
|
providerContext.transformedDirectiveAsts, providerContext.transformProviders,
|
||||||
providerContext.transformedHasViewContainer, children,
|
providerContext.transformedHasViewContainer, children,
|
||||||
hasInlineTemplates ? null : ngContentIndex, element.sourceSpan);
|
hasInlineTemplates ? null : ngContentIndex, element.sourceSpan);
|
||||||
|
|
||||||
|
this._findComponentDirectives(directiveAsts)
|
||||||
|
.forEach(
|
||||||
|
componentDirectiveAst => this._validateElementAnimationInputOutputs(
|
||||||
|
componentDirectiveAst.hostProperties, componentDirectiveAst.hostEvents,
|
||||||
|
componentDirectiveAst.directive.template));
|
||||||
|
|
||||||
|
const componentTemplate = providerContext.viewContext.component.template;
|
||||||
|
this._validateElementAnimationInputOutputs(elementProps, events, componentTemplate);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasInlineTemplates) {
|
if (hasInlineTemplates) {
|
||||||
@ -469,9 +478,36 @@ class TemplateParseVisitor implements html.Visitor {
|
|||||||
templateProviderContext.transformedHasViewContainer, [parsedElement], ngContentIndex,
|
templateProviderContext.transformedHasViewContainer, [parsedElement], ngContentIndex,
|
||||||
element.sourceSpan);
|
element.sourceSpan);
|
||||||
}
|
}
|
||||||
|
|
||||||
return parsedElement;
|
return parsedElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _validateElementAnimationInputOutputs(
|
||||||
|
inputs: BoundElementPropertyAst[], outputs: BoundEventAst[],
|
||||||
|
template: CompileTemplateMetadata) {
|
||||||
|
const triggerLookup = new Set<string>();
|
||||||
|
template.animations.forEach(entry => { triggerLookup.add(entry.name); });
|
||||||
|
|
||||||
|
const animationInputs = inputs.filter(input => input.isAnimation);
|
||||||
|
animationInputs.forEach(input => {
|
||||||
|
const name = input.name;
|
||||||
|
if (!triggerLookup.has(name)) {
|
||||||
|
this._reportError(`Couldn't find an animation entry for "${name}"`, input.sourceSpan);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
outputs.forEach(output => {
|
||||||
|
if (output.isAnimation) {
|
||||||
|
const found = animationInputs.find(input => input.name == output.name);
|
||||||
|
if (!found) {
|
||||||
|
this._reportError(
|
||||||
|
`Unable to listen on (@${output.name}.${output.phase}) because the animation trigger [@${output.name}] isn't being used on the same element`,
|
||||||
|
output.sourceSpan);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private _parseInlineTemplateBinding(
|
private _parseInlineTemplateBinding(
|
||||||
attr: html.Attribute, targetMatchableAttrs: string[][],
|
attr: html.Attribute, targetMatchableAttrs: string[][],
|
||||||
targetProps: BoundElementOrDirectiveProperty[], targetVars: VariableAst[]): boolean {
|
targetProps: BoundElementOrDirectiveProperty[], targetVars: VariableAst[]): boolean {
|
||||||
@ -533,7 +569,7 @@ class TemplateParseVisitor implements html.Visitor {
|
|||||||
this._parseReference(identifier, value, srcSpan, targetRefs);
|
this._parseReference(identifier, value, srcSpan, targetRefs);
|
||||||
|
|
||||||
} else if (bindParts[KW_ON_IDX]) {
|
} else if (bindParts[KW_ON_IDX]) {
|
||||||
this._parseEvent(
|
this._parseEventOrAnimationEvent(
|
||||||
bindParts[IDENT_KW_IDX], value, srcSpan, targetMatchableAttrs, targetEvents);
|
bindParts[IDENT_KW_IDX], value, srcSpan, targetMatchableAttrs, targetEvents);
|
||||||
|
|
||||||
} else if (bindParts[KW_BINDON_IDX]) {
|
} else if (bindParts[KW_BINDON_IDX]) {
|
||||||
@ -544,7 +580,7 @@ class TemplateParseVisitor implements html.Visitor {
|
|||||||
bindParts[IDENT_KW_IDX], value, srcSpan, targetMatchableAttrs, targetEvents);
|
bindParts[IDENT_KW_IDX], value, srcSpan, targetMatchableAttrs, targetEvents);
|
||||||
|
|
||||||
} else if (bindParts[KW_AT_IDX]) {
|
} else if (bindParts[KW_AT_IDX]) {
|
||||||
if (name[0] == '@' && isPresent(value) && value.length > 0) {
|
if (_isAnimationLabel(name) && isPresent(value) && value.length > 0) {
|
||||||
this._reportError(
|
this._reportError(
|
||||||
`Assigning animation triggers via @prop="exp" attributes with an expression is invalid.` +
|
`Assigning animation triggers via @prop="exp" attributes with an expression is invalid.` +
|
||||||
` Use property bindings (e.g. [@prop]="exp") or use an attribute without a value (e.g. @prop) instead.`,
|
` Use property bindings (e.g. [@prop]="exp") or use an attribute without a value (e.g. @prop) instead.`,
|
||||||
@ -565,7 +601,7 @@ class TemplateParseVisitor implements html.Visitor {
|
|||||||
targetAnimationProps);
|
targetAnimationProps);
|
||||||
|
|
||||||
} else if (bindParts[IDENT_EVENT_IDX]) {
|
} else if (bindParts[IDENT_EVENT_IDX]) {
|
||||||
this._parseEvent(
|
this._parseEventOrAnimationEvent(
|
||||||
bindParts[IDENT_EVENT_IDX], value, srcSpan, targetMatchableAttrs, targetEvents);
|
bindParts[IDENT_EVENT_IDX], value, srcSpan, targetMatchableAttrs, targetEvents);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -608,7 +644,7 @@ class TemplateParseVisitor implements html.Visitor {
|
|||||||
targetMatchableAttrs: string[][], targetProps: BoundElementOrDirectiveProperty[],
|
targetMatchableAttrs: string[][], targetProps: BoundElementOrDirectiveProperty[],
|
||||||
targetAnimationProps: BoundElementPropertyAst[]) {
|
targetAnimationProps: BoundElementPropertyAst[]) {
|
||||||
const animatePropLength = ANIMATE_PROP_PREFIX.length;
|
const animatePropLength = ANIMATE_PROP_PREFIX.length;
|
||||||
var isAnimationProp = name[0] == '@';
|
var isAnimationProp = _isAnimationLabel(name);
|
||||||
var animationPrefixLength = 1;
|
var animationPrefixLength = 1;
|
||||||
if (name.substring(0, animatePropLength) == ANIMATE_PROP_PREFIX) {
|
if (name.substring(0, animatePropLength) == ANIMATE_PROP_PREFIX) {
|
||||||
isAnimationProp = true;
|
isAnimationProp = true;
|
||||||
@ -635,6 +671,7 @@ class TemplateParseVisitor implements html.Visitor {
|
|||||||
if (!isPresent(expression) || expression.length == 0) {
|
if (!isPresent(expression) || expression.length == 0) {
|
||||||
expression = 'null';
|
expression = 'null';
|
||||||
}
|
}
|
||||||
|
|
||||||
const ast = this._parseBinding(expression, sourceSpan);
|
const ast = this._parseBinding(expression, sourceSpan);
|
||||||
targetMatchableAttrs.push([name, ast.source]);
|
targetMatchableAttrs.push([name, ast.source]);
|
||||||
targetAnimationProps.push(new BoundElementPropertyAst(
|
targetAnimationProps.push(new BoundElementPropertyAst(
|
||||||
@ -662,20 +699,56 @@ class TemplateParseVisitor implements html.Visitor {
|
|||||||
private _parseAssignmentEvent(
|
private _parseAssignmentEvent(
|
||||||
name: string, expression: string, sourceSpan: ParseSourceSpan,
|
name: string, expression: string, sourceSpan: ParseSourceSpan,
|
||||||
targetMatchableAttrs: string[][], targetEvents: BoundEventAst[]) {
|
targetMatchableAttrs: string[][], targetEvents: BoundEventAst[]) {
|
||||||
this._parseEvent(
|
this._parseEventOrAnimationEvent(
|
||||||
`${name}Change`, `${expression}=$event`, sourceSpan, targetMatchableAttrs, targetEvents);
|
`${name}Change`, `${expression}=$event`, sourceSpan, targetMatchableAttrs, targetEvents);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _parseEventOrAnimationEvent(
|
||||||
|
name: string, expression: string, sourceSpan: ParseSourceSpan,
|
||||||
|
targetMatchableAttrs: string[][], targetEvents: BoundEventAst[]) {
|
||||||
|
if (_isAnimationLabel(name)) {
|
||||||
|
name = name.substr(1);
|
||||||
|
this._parseAnimationEvent(name, expression, sourceSpan, targetEvents);
|
||||||
|
} else {
|
||||||
|
this._parseEvent(name, expression, sourceSpan, targetMatchableAttrs, targetEvents);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _parseAnimationEvent(
|
||||||
|
name: string, expression: string, sourceSpan: ParseSourceSpan,
|
||||||
|
targetEvents: BoundEventAst[]) {
|
||||||
|
const matches = splitAtPeriod(name, [name, '']);
|
||||||
|
const eventName = matches[0];
|
||||||
|
const phase = matches[1].toLowerCase();
|
||||||
|
if (phase) {
|
||||||
|
switch (phase) {
|
||||||
|
case 'start':
|
||||||
|
case 'done':
|
||||||
|
const ast = this._parseAction(expression, sourceSpan);
|
||||||
|
targetEvents.push(new BoundEventAst(eventName, null, phase, ast, sourceSpan));
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
this._reportError(
|
||||||
|
`The provided animation output phase value "${phase}" for "@${eventName}" is not supported (use start or done)`,
|
||||||
|
sourceSpan);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this._reportError(
|
||||||
|
`The animation trigger output event (@${eventName}) is missing its phase value name (start or done are currently supported)`,
|
||||||
|
sourceSpan);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private _parseEvent(
|
private _parseEvent(
|
||||||
name: string, expression: string, sourceSpan: ParseSourceSpan,
|
name: string, expression: string, sourceSpan: ParseSourceSpan,
|
||||||
targetMatchableAttrs: string[][], targetEvents: BoundEventAst[]) {
|
targetMatchableAttrs: string[][], targetEvents: BoundEventAst[]) {
|
||||||
// long format: 'target: eventName'
|
// long format: 'target: eventName'
|
||||||
const parts = splitAtColon(name, [null, name]);
|
const [target, eventName] = splitAtColon(name, [null, name]);
|
||||||
const target = parts[0];
|
|
||||||
const eventName = parts[1];
|
|
||||||
const ast = this._parseAction(expression, sourceSpan);
|
const ast = this._parseAction(expression, sourceSpan);
|
||||||
targetMatchableAttrs.push([name, ast.source]);
|
targetMatchableAttrs.push([name, ast.source]);
|
||||||
targetEvents.push(new BoundEventAst(eventName, target, ast, sourceSpan));
|
targetEvents.push(new BoundEventAst(eventName, target, null, ast, sourceSpan));
|
||||||
// Don't detect directives for event names for now,
|
// Don't detect directives for event names for now,
|
||||||
// so don't add the event name to the matchableAttrs
|
// so don't add the event name to the matchableAttrs
|
||||||
}
|
}
|
||||||
@ -779,7 +852,7 @@ class TemplateParseVisitor implements html.Visitor {
|
|||||||
if (hostListeners) {
|
if (hostListeners) {
|
||||||
StringMapWrapper.forEach(hostListeners, (expression: string, propName: string) => {
|
StringMapWrapper.forEach(hostListeners, (expression: string, propName: string) => {
|
||||||
if (isString(expression)) {
|
if (isString(expression)) {
|
||||||
this._parseEvent(propName, expression, sourceSpan, [], targetEventAsts);
|
this._parseEventOrAnimationEvent(propName, expression, sourceSpan, [], targetEventAsts);
|
||||||
} else {
|
} else {
|
||||||
this._reportError(
|
this._reportError(
|
||||||
`Value of the host listener "${propName}" needs to be a string representing an expression but got "${expression}" (${typeof expression})`,
|
`Value of the host listener "${propName}" needs to be a string representing an expression but got "${expression}" (${typeof expression})`,
|
||||||
@ -846,7 +919,7 @@ class TemplateParseVisitor implements html.Visitor {
|
|||||||
|
|
||||||
if (parts.length === 1) {
|
if (parts.length === 1) {
|
||||||
var partValue = parts[0];
|
var partValue = parts[0];
|
||||||
if (partValue[0] == '@') {
|
if (_isAnimationLabel(partValue)) {
|
||||||
boundPropertyName = partValue.substr(1);
|
boundPropertyName = partValue.substr(1);
|
||||||
bindingType = PropertyBindingType.Animation;
|
bindingType = PropertyBindingType.Animation;
|
||||||
securityContext = SecurityContext.NONE;
|
securityContext = SecurityContext.NONE;
|
||||||
@ -922,15 +995,13 @@ class TemplateParseVisitor implements html.Visitor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _findComponentDirectives(directives: DirectiveAst[]): DirectiveAst[] {
|
||||||
|
return directives.filter(directive => directive.directive.isComponent);
|
||||||
|
}
|
||||||
|
|
||||||
private _findComponentDirectiveNames(directives: DirectiveAst[]): string[] {
|
private _findComponentDirectiveNames(directives: DirectiveAst[]): string[] {
|
||||||
const componentTypeNames: string[] = [];
|
return this._findComponentDirectives(directives)
|
||||||
directives.forEach(directive => {
|
.map(directive => directive.directive.type.name);
|
||||||
const typeName = directive.directive.type.name;
|
|
||||||
if (directive.directive.isComponent) {
|
|
||||||
componentTypeNames.push(typeName);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return componentTypeNames;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _assertOnlyOneComponent(directives: DirectiveAst[], sourceSpan: ParseSourceSpan) {
|
private _assertOnlyOneComponent(directives: DirectiveAst[], sourceSpan: ParseSourceSpan) {
|
||||||
@ -1114,3 +1185,7 @@ export class PipeCollector extends RecursiveAstVisitor {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function _isAnimationLabel(name: string): boolean {
|
||||||
|
return name[0] == '@';
|
||||||
|
}
|
||||||
|
@ -21,9 +21,17 @@ export function camelCaseToDashCase(input: string): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function splitAtColon(input: string, defaultValues: string[]): string[] {
|
export function splitAtColon(input: string, defaultValues: string[]): string[] {
|
||||||
const colonIndex = input.indexOf(':');
|
return _splitAt(input, ':', defaultValues);
|
||||||
if (colonIndex == -1) return defaultValues;
|
}
|
||||||
return [input.slice(0, colonIndex).trim(), input.slice(colonIndex + 1).trim()];
|
|
||||||
|
export function splitAtPeriod(input: string, defaultValues: string[]): string[] {
|
||||||
|
return _splitAt(input, '.', defaultValues);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _splitAt(input: string, character: string, defaultValues: string[]): string[] {
|
||||||
|
const characterIndex = input.indexOf(character);
|
||||||
|
if (characterIndex == -1) return defaultValues;
|
||||||
|
return [input.slice(0, characterIndex).trim(), input.slice(characterIndex + 1).trim()];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function sanitizeIdentifier(name: string): string {
|
export function sanitizeIdentifier(name: string): string {
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {CompiledAnimationTriggerResult} from '../animation/animation_compiler';
|
import {AnimationEntryCompileResult} from '../animation/animation_compiler';
|
||||||
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompilePipeMetadata, CompileTokenMetadata} from '../compile_metadata';
|
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompilePipeMetadata, CompileTokenMetadata} from '../compile_metadata';
|
||||||
import {CompilerConfig} from '../config';
|
import {CompilerConfig} from '../config';
|
||||||
import {ListWrapper, MapWrapper} from '../facade/collection';
|
import {ListWrapper, MapWrapper} from '../facade/collection';
|
||||||
@ -72,7 +72,7 @@ export class CompileView implements NameResolver {
|
|||||||
constructor(
|
constructor(
|
||||||
public component: CompileDirectiveMetadata, public genConfig: CompilerConfig,
|
public component: CompileDirectiveMetadata, public genConfig: CompilerConfig,
|
||||||
public pipeMetas: CompilePipeMetadata[], public styles: o.Expression,
|
public pipeMetas: CompilePipeMetadata[], public styles: o.Expression,
|
||||||
public animations: CompiledAnimationTriggerResult[], public viewIndex: number,
|
public animations: AnimationEntryCompileResult[], public viewIndex: number,
|
||||||
public declarationElement: CompileElement, public templateVariableBindings: string[][]) {
|
public declarationElement: CompileElement, public templateVariableBindings: string[][]) {
|
||||||
this.createMethod = new CompileMethod(this);
|
this.createMethod = new CompileMethod(this);
|
||||||
this.animationBindingsMethod = new CompileMethod(this);
|
this.animationBindingsMethod = new CompileMethod(this);
|
||||||
|
@ -11,7 +11,6 @@ import {ListWrapper, StringMapWrapper} from '../facade/collection';
|
|||||||
import {StringWrapper, isBlank, isPresent} from '../facade/lang';
|
import {StringWrapper, isBlank, isPresent} from '../facade/lang';
|
||||||
import {Identifiers, identifierToken, resolveIdentifier} from '../identifiers';
|
import {Identifiers, identifierToken, resolveIdentifier} from '../identifiers';
|
||||||
import * as o from '../output/output_ast';
|
import * as o from '../output/output_ast';
|
||||||
import {AnimationOutput} from '../private_import_core';
|
|
||||||
import {BoundEventAst, DirectiveAst} from '../template_parser/template_ast';
|
import {BoundEventAst, DirectiveAst} from '../template_parser/template_ast';
|
||||||
|
|
||||||
import {CompileBinding} from './compile_binding';
|
import {CompileBinding} from './compile_binding';
|
||||||
@ -20,10 +19,6 @@ import {CompileMethod} from './compile_method';
|
|||||||
import {EventHandlerVars, ViewProperties} from './constants';
|
import {EventHandlerVars, ViewProperties} from './constants';
|
||||||
import {convertCdStatementToIr} from './expression_converter';
|
import {convertCdStatementToIr} from './expression_converter';
|
||||||
|
|
||||||
export class CompileElementAnimationOutput {
|
|
||||||
constructor(public listener: CompileEventListener, public output: AnimationOutput) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class CompileEventListener {
|
export class CompileEventListener {
|
||||||
private _method: CompileMethod;
|
private _method: CompileMethod;
|
||||||
private _hasComponentHostListener: boolean = false;
|
private _hasComponentHostListener: boolean = false;
|
||||||
@ -32,13 +27,14 @@ export class CompileEventListener {
|
|||||||
private _actionResultExprs: o.Expression[] = [];
|
private _actionResultExprs: o.Expression[] = [];
|
||||||
|
|
||||||
static getOrCreate(
|
static getOrCreate(
|
||||||
compileElement: CompileElement, eventTarget: string, eventName: string,
|
compileElement: CompileElement, eventTarget: string, eventName: string, eventPhase: string,
|
||||||
targetEventListeners: CompileEventListener[]): CompileEventListener {
|
targetEventListeners: CompileEventListener[]): CompileEventListener {
|
||||||
var listener = targetEventListeners.find(
|
var listener = targetEventListeners.find(
|
||||||
listener => listener.eventTarget == eventTarget && listener.eventName == eventName);
|
listener => listener.eventTarget == eventTarget && listener.eventName == eventName &&
|
||||||
|
listener.eventPhase == eventPhase);
|
||||||
if (isBlank(listener)) {
|
if (isBlank(listener)) {
|
||||||
listener = new CompileEventListener(
|
listener = new CompileEventListener(
|
||||||
compileElement, eventTarget, eventName, targetEventListeners.length);
|
compileElement, eventTarget, eventName, eventPhase, targetEventListeners.length);
|
||||||
targetEventListeners.push(listener);
|
targetEventListeners.push(listener);
|
||||||
}
|
}
|
||||||
return listener;
|
return listener;
|
||||||
@ -48,7 +44,7 @@ export class CompileEventListener {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public compileElement: CompileElement, public eventTarget: string, public eventName: string,
|
public compileElement: CompileElement, public eventTarget: string, public eventName: string,
|
||||||
listenerIndex: number) {
|
public eventPhase: string, listenerIndex: number) {
|
||||||
this._method = new CompileMethod(compileElement.view);
|
this._method = new CompileMethod(compileElement.view);
|
||||||
this._methodName =
|
this._methodName =
|
||||||
`_handle_${santitizeEventName(eventName)}_${compileElement.nodeIndex}_${listenerIndex}`;
|
`_handle_${santitizeEventName(eventName)}_${compileElement.nodeIndex}_${listenerIndex}`;
|
||||||
@ -119,7 +115,7 @@ export class CompileEventListener {
|
|||||||
disposable.set(listenExpr).toDeclStmt(o.FUNCTION_TYPE, [o.StmtModifier.Private]));
|
disposable.set(listenExpr).toDeclStmt(o.FUNCTION_TYPE, [o.StmtModifier.Private]));
|
||||||
}
|
}
|
||||||
|
|
||||||
listenToAnimation(output: AnimationOutput) {
|
listenToAnimation() {
|
||||||
var outputListener = o.THIS_EXPR.callMethod(
|
var outputListener = o.THIS_EXPR.callMethod(
|
||||||
'eventHandler',
|
'eventHandler',
|
||||||
[o.THIS_EXPR.prop(this._methodName).callMethod(o.BuiltinMethod.Bind, [o.THIS_EXPR])]);
|
[o.THIS_EXPR.prop(this._methodName).callMethod(o.BuiltinMethod.Bind, [o.THIS_EXPR])]);
|
||||||
@ -129,11 +125,8 @@ export class CompileEventListener {
|
|||||||
.callMethod(
|
.callMethod(
|
||||||
'registerAnimationOutput',
|
'registerAnimationOutput',
|
||||||
[
|
[
|
||||||
this.compileElement.renderNode,
|
this.compileElement.renderNode, o.literal(this.eventName),
|
||||||
o.importExpr(resolveIdentifier(Identifiers.AnimationOutput)).instantiate([
|
o.literal(this.eventPhase), outputListener
|
||||||
o.literal(output.name), o.literal(output.phase)
|
|
||||||
]),
|
|
||||||
outputListener
|
|
||||||
])
|
])
|
||||||
.toStmt();
|
.toStmt();
|
||||||
this.compileElement.view.createMethod.addStmt(stmt);
|
this.compileElement.view.createMethod.addStmt(stmt);
|
||||||
@ -160,7 +153,7 @@ export function collectEventListeners(
|
|||||||
hostEvents.forEach((hostEvent) => {
|
hostEvents.forEach((hostEvent) => {
|
||||||
compileElement.view.bindings.push(new CompileBinding(compileElement, hostEvent));
|
compileElement.view.bindings.push(new CompileBinding(compileElement, hostEvent));
|
||||||
var listener = CompileEventListener.getOrCreate(
|
var listener = CompileEventListener.getOrCreate(
|
||||||
compileElement, hostEvent.target, hostEvent.name, eventListeners);
|
compileElement, hostEvent.target, hostEvent.name, hostEvent.phase, eventListeners);
|
||||||
listener.addAction(hostEvent, null, null);
|
listener.addAction(hostEvent, null, null);
|
||||||
});
|
});
|
||||||
dirs.forEach((directiveAst) => {
|
dirs.forEach((directiveAst) => {
|
||||||
@ -169,7 +162,7 @@ export function collectEventListeners(
|
|||||||
directiveAst.hostEvents.forEach((hostEvent) => {
|
directiveAst.hostEvents.forEach((hostEvent) => {
|
||||||
compileElement.view.bindings.push(new CompileBinding(compileElement, hostEvent));
|
compileElement.view.bindings.push(new CompileBinding(compileElement, hostEvent));
|
||||||
var listener = CompileEventListener.getOrCreate(
|
var listener = CompileEventListener.getOrCreate(
|
||||||
compileElement, hostEvent.target, hostEvent.name, eventListeners);
|
compileElement, hostEvent.target, hostEvent.name, hostEvent.phase, eventListeners);
|
||||||
listener.addAction(hostEvent, directiveAst.directive, directiveInstance);
|
listener.addAction(hostEvent, directiveAst.directive, directiveInstance);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -190,11 +183,13 @@ export function bindDirectiveOutputs(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function bindRenderOutputs(eventListeners: CompileEventListener[]) {
|
export function bindRenderOutputs(eventListeners: CompileEventListener[]) {
|
||||||
eventListeners.forEach(listener => listener.listenToRenderer());
|
eventListeners.forEach(listener => {
|
||||||
}
|
if (listener.eventPhase) {
|
||||||
|
listener.listenToAnimation();
|
||||||
export function bindAnimationOutputs(eventListeners: CompileElementAnimationOutput[]) {
|
} else {
|
||||||
eventListeners.forEach(entry => { entry.listener.listenToAnimation(entry.output); });
|
listener.listenToRenderer();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function convertStmtIntoExpression(stmt: o.Statement): o.Expression {
|
function convertStmtIntoExpression(stmt: o.Statement): o.Expression {
|
||||||
|
@ -31,8 +31,6 @@ function createCurrValueExpr(exprIndex: number): o.ReadVarExpr {
|
|||||||
return o.variable(`currVal_${exprIndex}`); // fix syntax highlighting: `
|
return o.variable(`currVal_${exprIndex}`); // fix syntax highlighting: `
|
||||||
}
|
}
|
||||||
|
|
||||||
const _animationViewCheckedFlagMap = new Map<CompileView, boolean>();
|
|
||||||
|
|
||||||
function bind(
|
function bind(
|
||||||
view: CompileView, currValExpr: o.ReadVarExpr, fieldExpr: o.ReadPropExpr,
|
view: CompileView, currValExpr: o.ReadVarExpr, fieldExpr: o.ReadPropExpr,
|
||||||
parsedExpression: cdAst.AST, context: o.Expression, actions: o.Statement[],
|
parsedExpression: cdAst.AST, context: o.Expression, actions: o.Statement[],
|
||||||
|
@ -7,18 +7,16 @@
|
|||||||
*/
|
*/
|
||||||
import {ListWrapper} from '../facade/collection';
|
import {ListWrapper} from '../facade/collection';
|
||||||
import {identifierToken} from '../identifiers';
|
import {identifierToken} from '../identifiers';
|
||||||
import {AnimationOutput} from '../private_import_core';
|
|
||||||
import {AttrAst, BoundDirectivePropertyAst, BoundElementPropertyAst, BoundEventAst, BoundTextAst, DirectiveAst, ElementAst, EmbeddedTemplateAst, NgContentAst, ReferenceAst, TemplateAst, TemplateAstVisitor, TextAst, VariableAst, templateVisitAll} from '../template_parser/template_ast';
|
import {AttrAst, BoundDirectivePropertyAst, BoundElementPropertyAst, BoundEventAst, BoundTextAst, DirectiveAst, ElementAst, EmbeddedTemplateAst, NgContentAst, ReferenceAst, TemplateAst, TemplateAstVisitor, TextAst, VariableAst, templateVisitAll} from '../template_parser/template_ast';
|
||||||
|
|
||||||
import {CompileElement, CompileNode} from './compile_element';
|
import {CompileElement, CompileNode} from './compile_element';
|
||||||
import {CompileView} from './compile_view';
|
import {CompileView} from './compile_view';
|
||||||
import {CompileElementAnimationOutput, CompileEventListener, bindAnimationOutputs, bindDirectiveOutputs, bindRenderOutputs, collectEventListeners} from './event_binder';
|
import {CompileEventListener, bindDirectiveOutputs, bindRenderOutputs, collectEventListeners} from './event_binder';
|
||||||
import {bindDirectiveAfterContentLifecycleCallbacks, bindDirectiveAfterViewLifecycleCallbacks, bindDirectiveDetectChangesLifecycleCallbacks, bindInjectableDestroyLifecycleCallbacks, bindPipeDestroyLifecycleCallbacks} from './lifecycle_binder';
|
import {bindDirectiveAfterContentLifecycleCallbacks, bindDirectiveAfterViewLifecycleCallbacks, bindDirectiveDetectChangesLifecycleCallbacks, bindInjectableDestroyLifecycleCallbacks, bindPipeDestroyLifecycleCallbacks} from './lifecycle_binder';
|
||||||
import {bindDirectiveHostProps, bindDirectiveInputs, bindRenderInputs, bindRenderText} from './property_binder';
|
import {bindDirectiveHostProps, bindDirectiveInputs, bindRenderInputs, bindRenderText} from './property_binder';
|
||||||
|
|
||||||
export function bindView(
|
export function bindView(view: CompileView, parsedTemplate: TemplateAst[]): void {
|
||||||
view: CompileView, parsedTemplate: TemplateAst[], animationOutputs: AnimationOutput[]): void {
|
var visitor = new ViewBinderVisitor(view);
|
||||||
var visitor = new ViewBinderVisitor(view, animationOutputs);
|
|
||||||
templateVisitAll(visitor, parsedTemplate);
|
templateVisitAll(visitor, parsedTemplate);
|
||||||
view.pipes.forEach(
|
view.pipes.forEach(
|
||||||
(pipe) => { bindPipeDestroyLifecycleCallbacks(pipe.meta, pipe.instance, pipe.view); });
|
(pipe) => { bindPipeDestroyLifecycleCallbacks(pipe.meta, pipe.instance, pipe.view); });
|
||||||
@ -26,12 +24,8 @@ export function bindView(
|
|||||||
|
|
||||||
class ViewBinderVisitor implements TemplateAstVisitor {
|
class ViewBinderVisitor implements TemplateAstVisitor {
|
||||||
private _nodeIndex: number = 0;
|
private _nodeIndex: number = 0;
|
||||||
private _animationOutputsMap: {[key: string]: AnimationOutput} = {};
|
|
||||||
|
|
||||||
constructor(public view: CompileView, public animationOutputs: AnimationOutput[]) {
|
constructor(public view: CompileView) {}
|
||||||
animationOutputs.forEach(
|
|
||||||
entry => { this._animationOutputsMap[entry.fullPropertyName] = entry; });
|
|
||||||
}
|
|
||||||
|
|
||||||
visitBoundText(ast: BoundTextAst, parent: CompileElement): any {
|
visitBoundText(ast: BoundTextAst, parent: CompileElement): any {
|
||||||
var node = this.view.nodes[this._nodeIndex++];
|
var node = this.view.nodes[this._nodeIndex++];
|
||||||
@ -48,22 +42,9 @@ class ViewBinderVisitor implements TemplateAstVisitor {
|
|||||||
visitElement(ast: ElementAst, parent: CompileElement): any {
|
visitElement(ast: ElementAst, parent: CompileElement): any {
|
||||||
var compileElement = <CompileElement>this.view.nodes[this._nodeIndex++];
|
var compileElement = <CompileElement>this.view.nodes[this._nodeIndex++];
|
||||||
var eventListeners: CompileEventListener[] = [];
|
var eventListeners: CompileEventListener[] = [];
|
||||||
var animationEventListeners: CompileElementAnimationOutput[] = [];
|
|
||||||
collectEventListeners(ast.outputs, ast.directives, compileElement).forEach(entry => {
|
collectEventListeners(ast.outputs, ast.directives, compileElement).forEach(entry => {
|
||||||
// TODO: figure out how to abstract this `if` statement elsewhere
|
eventListeners.push(entry);
|
||||||
if (entry.eventName[0] == '@') {
|
|
||||||
let animationOutputName = entry.eventName.substr(1);
|
|
||||||
let output = this._animationOutputsMap[animationOutputName];
|
|
||||||
// no need to report an error here since the parser will
|
|
||||||
// have caught the missing animation trigger definition
|
|
||||||
if (output) {
|
|
||||||
animationEventListeners.push(new CompileElementAnimationOutput(entry, output));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
eventListeners.push(entry);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
bindAnimationOutputs(animationEventListeners);
|
|
||||||
bindRenderInputs(ast.inputs, compileElement);
|
bindRenderInputs(ast.inputs, compileElement);
|
||||||
bindRenderOutputs(eventListeners);
|
bindRenderOutputs(eventListeners);
|
||||||
ast.directives.forEach((directiveAst) => {
|
ast.directives.forEach((directiveAst) => {
|
||||||
@ -108,7 +89,7 @@ class ViewBinderVisitor implements TemplateAstVisitor {
|
|||||||
var providerInstance = compileElement.instances.get(providerAst.token.reference);
|
var providerInstance = compileElement.instances.get(providerAst.token.reference);
|
||||||
bindInjectableDestroyLifecycleCallbacks(providerAst, providerInstance, compileElement);
|
bindInjectableDestroyLifecycleCallbacks(providerAst, providerInstance, compileElement);
|
||||||
});
|
});
|
||||||
bindView(compileElement.embeddedView, ast.children, this.animationOutputs);
|
bindView(compileElement.embeddedView, ast.children);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,8 +7,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {ChangeDetectionStrategy, ViewEncapsulation} from '@angular/core';
|
import {ChangeDetectionStrategy, ViewEncapsulation} from '@angular/core';
|
||||||
|
|
||||||
import {AnimationCompiler} from '../animation/animation_compiler';
|
|
||||||
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileTokenMetadata, CompileTypeMetadata} from '../compile_metadata';
|
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileTokenMetadata, CompileTypeMetadata} from '../compile_metadata';
|
||||||
import {ListWrapper, SetWrapper, StringMapWrapper} from '../facade/collection';
|
import {ListWrapper, SetWrapper, StringMapWrapper} from '../facade/collection';
|
||||||
import {StringWrapper, isPresent} from '../facade/lang';
|
import {StringWrapper, isPresent} from '../facade/lang';
|
||||||
@ -65,8 +63,6 @@ export function finishView(view: CompileView, targetStatements: o.Statement[]) {
|
|||||||
class ViewBuilderVisitor implements TemplateAstVisitor {
|
class ViewBuilderVisitor implements TemplateAstVisitor {
|
||||||
nestedViewCount: number = 0;
|
nestedViewCount: number = 0;
|
||||||
|
|
||||||
private _animationCompiler = new AnimationCompiler();
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public view: CompileView,
|
public view: CompileView,
|
||||||
public targetDependencies: Array<ViewFactoryDependency|ComponentFactoryDependency>) {}
|
public targetDependencies: Array<ViewFactoryDependency|ComponentFactoryDependency>) {}
|
||||||
@ -279,12 +275,10 @@ class ViewBuilderVisitor implements TemplateAstVisitor {
|
|||||||
ast.hasViewContainer, true, ast.references);
|
ast.hasViewContainer, true, ast.references);
|
||||||
this.view.nodes.push(compileElement);
|
this.view.nodes.push(compileElement);
|
||||||
|
|
||||||
var compiledAnimations = this._animationCompiler.compileComponent(this.view.component, [ast]);
|
|
||||||
|
|
||||||
this.nestedViewCount++;
|
this.nestedViewCount++;
|
||||||
var embeddedView = new CompileView(
|
var embeddedView = new CompileView(
|
||||||
this.view.component, this.view.genConfig, this.view.pipeMetas, o.NULL_EXPR,
|
this.view.component, this.view.genConfig, this.view.pipeMetas, o.NULL_EXPR,
|
||||||
compiledAnimations.triggers, this.view.viewIndex + this.nestedViewCount, compileElement,
|
this.view.animations, this.view.viewIndex + this.nestedViewCount, compileElement,
|
||||||
templateVariableBindings);
|
templateVariableBindings);
|
||||||
this.nestedViewCount += buildView(embeddedView, ast.children, this.targetDependencies);
|
this.nestedViewCount += buildView(embeddedView, ast.children, this.targetDependencies);
|
||||||
|
|
||||||
@ -518,7 +512,7 @@ function createViewFactory(
|
|||||||
templateUrlInfo = view.component.template.templateUrl;
|
templateUrlInfo = view.component.template.templateUrl;
|
||||||
}
|
}
|
||||||
if (view.viewIndex === 0) {
|
if (view.viewIndex === 0) {
|
||||||
var animationsExpr = o.literalMap(view.animations.map(entry => [entry.name, entry.fnVariable]));
|
var animationsExpr = o.literalMap(view.animations.map(entry => [entry.name, entry.fnExp]));
|
||||||
initRenderCompTypeStmts = [new o.IfStmt(renderCompTypeVar.identical(o.NULL_EXPR), [
|
initRenderCompTypeStmts = [new o.IfStmt(renderCompTypeVar.identical(o.NULL_EXPR), [
|
||||||
renderCompTypeVar
|
renderCompTypeVar
|
||||||
.set(ViewConstructorVars.viewUtils.callMethod(
|
.set(ViewConstructorVars.viewUtils.callMethod(
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
import {Injectable} from '@angular/core';
|
import {Injectable} from '@angular/core';
|
||||||
|
|
||||||
import {AnimationCompiler} from '../animation/animation_compiler';
|
import {AnimationCompiler, AnimationEntryCompileResult} from '../animation/animation_compiler';
|
||||||
import {CompileDirectiveMetadata, CompilePipeMetadata} from '../compile_metadata';
|
import {CompileDirectiveMetadata, CompilePipeMetadata} from '../compile_metadata';
|
||||||
import {CompilerConfig} from '../config';
|
import {CompilerConfig} from '../config';
|
||||||
import * as o from '../output/output_ast';
|
import * as o from '../output/output_ast';
|
||||||
@ -34,22 +34,18 @@ export class ViewCompiler {
|
|||||||
|
|
||||||
compileComponent(
|
compileComponent(
|
||||||
component: CompileDirectiveMetadata, template: TemplateAst[], styles: o.Expression,
|
component: CompileDirectiveMetadata, template: TemplateAst[], styles: o.Expression,
|
||||||
pipes: CompilePipeMetadata[]): ViewCompileResult {
|
pipes: CompilePipeMetadata[],
|
||||||
var dependencies: Array<ViewFactoryDependency|ComponentFactoryDependency> = [];
|
compiledAnimations: AnimationEntryCompileResult[]): ViewCompileResult {
|
||||||
var compiledAnimations = this._animationCompiler.compileComponent(component, template);
|
const dependencies: Array<ViewFactoryDependency|ComponentFactoryDependency> = [];
|
||||||
var statements: o.Statement[] = [];
|
const view = new CompileView(
|
||||||
var animationTriggers = compiledAnimations.triggers;
|
component, this._genConfig, pipes, styles, compiledAnimations, 0,
|
||||||
animationTriggers.forEach(entry => {
|
|
||||||
statements.push(entry.statesMapStatement);
|
|
||||||
statements.push(entry.fnStatement);
|
|
||||||
});
|
|
||||||
var view = new CompileView(
|
|
||||||
component, this._genConfig, pipes, styles, animationTriggers, 0,
|
|
||||||
CompileElement.createNull(), []);
|
CompileElement.createNull(), []);
|
||||||
|
|
||||||
|
const statements: o.Statement[] = [];
|
||||||
buildView(view, template, dependencies);
|
buildView(view, template, dependencies);
|
||||||
// Need to separate binding from creation to be able to refer to
|
// Need to separate binding from creation to be able to refer to
|
||||||
// variables that have been declared after usage.
|
// variables that have been declared after usage.
|
||||||
bindView(view, template, compiledAnimations.outputs);
|
bindView(view, template);
|
||||||
finishView(view, statements);
|
finishView(view, statements);
|
||||||
|
|
||||||
return new ViewCompileResult(statements, view.viewFactory.name, dependencies);
|
return new ViewCompileResult(statements, view.viewFactory.name, dependencies);
|
||||||
|
@ -10,7 +10,8 @@ import {AnimationMetadata, animate, group, sequence, style, transition, trigger}
|
|||||||
import {AsyncTestCompleter, beforeEach, beforeEachProviders, ddescribe, describe, expect, iit, inject, it, xdescribe, xit} from '@angular/core/testing/testing_internal';
|
import {AsyncTestCompleter, beforeEach, beforeEachProviders, ddescribe, describe, expect, iit, inject, it, xdescribe, xit} from '@angular/core/testing/testing_internal';
|
||||||
|
|
||||||
import {StringMapWrapper} from '../../../platform-browser-dynamic/src/facade/collection';
|
import {StringMapWrapper} from '../../../platform-browser-dynamic/src/facade/collection';
|
||||||
import {AnimationCompiler, CompiledAnimationTriggerResult} from '../../src/animation/animation_compiler';
|
import {AnimationCompiler, AnimationEntryCompileResult} from '../../src/animation/animation_compiler';
|
||||||
|
import {AnimationParser} from '../../src/animation/animation_parser';
|
||||||
import {CompileAnimationEntryMetadata, CompileDirectiveMetadata, CompileTemplateMetadata, CompileTypeMetadata} from '../../src/compile_metadata';
|
import {CompileAnimationEntryMetadata, CompileDirectiveMetadata, CompileTemplateMetadata, CompileTypeMetadata} from '../../src/compile_metadata';
|
||||||
import {CompileMetadataResolver} from '../../src/metadata_resolver';
|
import {CompileMetadataResolver} from '../../src/metadata_resolver';
|
||||||
|
|
||||||
@ -20,12 +21,13 @@ export function main() {
|
|||||||
beforeEach(
|
beforeEach(
|
||||||
inject([CompileMetadataResolver], (res: CompileMetadataResolver) => { resolver = res; }));
|
inject([CompileMetadataResolver], (res: CompileMetadataResolver) => { resolver = res; }));
|
||||||
|
|
||||||
var compiler = new AnimationCompiler();
|
const parser = new AnimationParser();
|
||||||
|
const compiler = new AnimationCompiler();
|
||||||
|
|
||||||
var compileAnimations =
|
var compileAnimations =
|
||||||
(component: CompileDirectiveMetadata): CompiledAnimationTriggerResult => {
|
(component: CompileDirectiveMetadata): AnimationEntryCompileResult[] => {
|
||||||
var result = compiler.compileComponent(component, []);
|
const parsedAnimations = parser.parseComponent(component);
|
||||||
return result.triggers[0];
|
return compiler.compile(component.type.name, parsedAnimations);
|
||||||
};
|
};
|
||||||
|
|
||||||
var compileTriggers = (input: any[]) => {
|
var compileTriggers = (input: any[]) => {
|
||||||
@ -66,14 +68,5 @@ export function main() {
|
|||||||
expect(capturedErrorMessage)
|
expect(capturedErrorMessage)
|
||||||
.toMatch(/Animation states via styles must be prefixed with a ":"/);
|
.toMatch(/Animation states via styles must be prefixed with a ":"/);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw an error when two or more animation triggers contain the same name', () => {
|
|
||||||
var t1Data: any[] = [];
|
|
||||||
var t2Data: any[] = [];
|
|
||||||
|
|
||||||
expect(() => {
|
|
||||||
compileTriggers([['myTrigger', t1Data], ['myTrigger', t2Data]]);
|
|
||||||
}).toThrowError(/The animation trigger "myTrigger" has already been registered on "myCmp"/);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,8 @@ import {AsyncTestCompleter, beforeEach, beforeEachProviders, ddescribe, describe
|
|||||||
import {expect} from '@angular/platform-browser/testing/matchers';
|
import {expect} from '@angular/platform-browser/testing/matchers';
|
||||||
|
|
||||||
import {AnimationAst, AnimationEntryAst, AnimationGroupAst, AnimationKeyframeAst, AnimationSequenceAst, AnimationStateTransitionAst, AnimationStepAst, AnimationStylesAst} from '../../src/animation/animation_ast';
|
import {AnimationAst, AnimationEntryAst, AnimationGroupAst, AnimationKeyframeAst, AnimationSequenceAst, AnimationStateTransitionAst, AnimationStepAst, AnimationStylesAst} from '../../src/animation/animation_ast';
|
||||||
import {parseAnimationEntry} from '../../src/animation/animation_parser';
|
import {AnimationParser} from '../../src/animation/animation_parser';
|
||||||
|
import {CompileDirectiveMetadata, CompileTemplateMetadata, CompileTypeMetadata} from '../../src/compile_metadata';
|
||||||
import {StringMapWrapper} from '../../src/facade/collection';
|
import {StringMapWrapper} from '../../src/facade/collection';
|
||||||
import {CompileMetadataResolver} from '../../src/metadata_resolver';
|
import {CompileMetadataResolver} from '../../src/metadata_resolver';
|
||||||
import {FILL_STYLE_FLAG, flattenStyles} from '../private_import_core';
|
import {FILL_STYLE_FLAG, flattenStyles} from '../private_import_core';
|
||||||
@ -46,9 +47,10 @@ export function main() {
|
|||||||
inject([CompileMetadataResolver], (res: CompileMetadataResolver) => { resolver = res; }));
|
inject([CompileMetadataResolver], (res: CompileMetadataResolver) => { resolver = res; }));
|
||||||
|
|
||||||
var parseAnimation = (data: AnimationMetadata[]) => {
|
var parseAnimation = (data: AnimationMetadata[]) => {
|
||||||
var entry = trigger('myAnimation', [transition('state1 => state2', sequence(data))]);
|
const entry = trigger('myAnimation', [transition('state1 => state2', sequence(data))]);
|
||||||
var compiledAnimationEntry = resolver.getAnimationEntryMetadata(entry);
|
const compiledAnimationEntry = resolver.getAnimationEntryMetadata(entry);
|
||||||
return parseAnimationEntry(compiledAnimationEntry);
|
const parser = new AnimationParser();
|
||||||
|
return parser.parseEntry(compiledAnimationEntry);
|
||||||
};
|
};
|
||||||
|
|
||||||
var getAnimationAstFromEntryAst =
|
var getAnimationAstFromEntryAst =
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {CompileDiDependencyMetadata, CompileDirectiveMetadata, CompilePipeMetadata, CompileProviderMetadata, CompileQueryMetadata, CompileTemplateMetadata, CompileTokenMetadata, CompileTypeMetadata} from '@angular/compiler/src/compile_metadata';
|
import {CompileAnimationEntryMetadata, CompileDiDependencyMetadata, CompileDirectiveMetadata, CompilePipeMetadata, CompileProviderMetadata, CompileQueryMetadata, CompileTemplateMetadata, CompileTokenMetadata, CompileTypeMetadata} from '@angular/compiler/src/compile_metadata';
|
||||||
import {DomElementSchemaRegistry} from '@angular/compiler/src/schema/dom_element_schema_registry';
|
import {DomElementSchemaRegistry} from '@angular/compiler/src/schema/dom_element_schema_registry';
|
||||||
import {ElementSchemaRegistry} from '@angular/compiler/src/schema/element_schema_registry';
|
import {ElementSchemaRegistry} from '@angular/compiler/src/schema/element_schema_registry';
|
||||||
import {AttrAst, BoundDirectivePropertyAst, BoundElementPropertyAst, BoundEventAst, BoundTextAst, DirectiveAst, ElementAst, EmbeddedTemplateAst, NgContentAst, PropertyBindingType, ProviderAstType, ReferenceAst, TemplateAst, TemplateAstVisitor, TextAst, VariableAst, templateVisitAll} from '@angular/compiler/src/template_parser/template_ast';
|
import {AttrAst, BoundDirectivePropertyAst, BoundElementPropertyAst, BoundEventAst, BoundTextAst, DirectiveAst, ElementAst, EmbeddedTemplateAst, NgContentAst, PropertyBindingType, ProviderAstType, ReferenceAst, TemplateAst, TemplateAstVisitor, TextAst, VariableAst, templateVisitAll} from '@angular/compiler/src/template_parser/template_ast';
|
||||||
@ -16,7 +16,6 @@ import {SchemaMetadata, SecurityContext, Type} from '@angular/core';
|
|||||||
import {Console} from '@angular/core/src/console';
|
import {Console} from '@angular/core/src/console';
|
||||||
import {TestBed} from '@angular/core/testing';
|
import {TestBed} from '@angular/core/testing';
|
||||||
import {afterEach, beforeEach, beforeEachProviders, ddescribe, describe, expect, iit, inject, it, xit} from '@angular/core/testing/testing_internal';
|
import {afterEach, beforeEach, beforeEachProviders, ddescribe, describe, expect, iit, inject, it, xit} from '@angular/core/testing/testing_internal';
|
||||||
|
|
||||||
import {Identifiers, identifierToken, resolveIdentifierToken} from '../../src/identifiers';
|
import {Identifiers, identifierToken, resolveIdentifierToken} from '../../src/identifiers';
|
||||||
import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from '../../src/ml_parser/interpolation_config';
|
import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from '../../src/ml_parser/interpolation_config';
|
||||||
import {MockSchemaRegistry} from '../../testing/index';
|
import {MockSchemaRegistry} from '../../testing/index';
|
||||||
@ -32,9 +31,9 @@ const MOCK_SCHEMA_REGISTRY = [{
|
|||||||
|
|
||||||
export function main() {
|
export function main() {
|
||||||
var ngIf: CompileDirectiveMetadata;
|
var ngIf: CompileDirectiveMetadata;
|
||||||
var parse:
|
var parse: (
|
||||||
(template: string, directives: CompileDirectiveMetadata[], pipes?: CompilePipeMetadata[]) =>
|
template: string, directives: CompileDirectiveMetadata[], pipes?: CompilePipeMetadata[],
|
||||||
TemplateAst[];
|
schemas?: SchemaMetadata[]) => TemplateAst[];
|
||||||
var console: ArrayConsole;
|
var console: ArrayConsole;
|
||||||
|
|
||||||
function commonBeforeEach() {
|
function commonBeforeEach() {
|
||||||
@ -43,14 +42,18 @@ export function main() {
|
|||||||
TestBed.configureCompiler({providers: [{provide: Console, useValue: console}]});
|
TestBed.configureCompiler({providers: [{provide: Console, useValue: console}]});
|
||||||
});
|
});
|
||||||
beforeEach(inject([TemplateParser], (parser: TemplateParser) => {
|
beforeEach(inject([TemplateParser], (parser: TemplateParser) => {
|
||||||
|
var someAnimation = new CompileAnimationEntryMetadata('someAnimation', []);
|
||||||
|
var someTemplate = new CompileTemplateMetadata({animations: [someAnimation]});
|
||||||
var component = CompileDirectiveMetadata.create({
|
var component = CompileDirectiveMetadata.create({
|
||||||
selector: 'root',
|
selector: 'root',
|
||||||
|
template: someTemplate,
|
||||||
type: new CompileTypeMetadata(
|
type: new CompileTypeMetadata(
|
||||||
{moduleUrl: someModuleUrl, name: 'Root', reference: {} as Type<any>}),
|
{moduleUrl: someModuleUrl, name: 'Root', reference: {} as Type<any>}),
|
||||||
isComponent: true
|
isComponent: true
|
||||||
});
|
});
|
||||||
ngIf = CompileDirectiveMetadata.create({
|
ngIf = CompileDirectiveMetadata.create({
|
||||||
selector: '[ngIf]',
|
selector: '[ngIf]',
|
||||||
|
template: someTemplate,
|
||||||
type: new CompileTypeMetadata(
|
type: new CompileTypeMetadata(
|
||||||
{moduleUrl: someModuleUrl, name: 'NgIf', reference: {} as Type<any>}),
|
{moduleUrl: someModuleUrl, name: 'NgIf', reference: {} as Type<any>}),
|
||||||
inputs: ['ngIf']
|
inputs: ['ngIf']
|
||||||
@ -302,27 +305,31 @@ Can't bind to 'invalidProp' since it isn't a known property of 'my-component'.
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should parse bound properties via bind-animate- and not report them as animation properties',
|
it('should parse bound properties via bind-animate- and not report them as attributes',
|
||||||
() => {
|
() => {
|
||||||
expect(humanizeTplAst(parse('<div bind-animate-something="value2">', []))).toEqual([
|
expect(humanizeTplAst(parse('<div bind-animate-someAnimation="value2">', [], [], [])))
|
||||||
[ElementAst, 'div'],
|
.toEqual([
|
||||||
[
|
[ElementAst, 'div'],
|
||||||
BoundElementPropertyAst, PropertyBindingType.Animation, 'something', 'value2', null
|
[
|
||||||
]
|
BoundElementPropertyAst, PropertyBindingType.Animation, 'someAnimation',
|
||||||
]);
|
'value2', null
|
||||||
|
]
|
||||||
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw an error when parsing detects non-bound properties via @ that contain a value',
|
it('should throw an error when parsing detects non-bound properties via @ that contain a value',
|
||||||
() => {
|
() => {
|
||||||
expect(() => { parse('<div @something="value2">', []); })
|
expect(() => { parse('<div @someAnimation="value2">', [], [], []); })
|
||||||
.toThrowError(
|
.toThrowError(
|
||||||
/Assigning animation triggers via @prop="exp" attributes with an expression is invalid. Use property bindings \(e.g. \[@prop\]="exp"\) or use an attribute without a value \(e.g. @prop\) instead. \("<div \[ERROR ->\]@something="value2">"\): TestComp@0:5/);
|
/Assigning animation triggers via @prop="exp" attributes with an expression is invalid. Use property bindings \(e.g. \[@prop\]="exp"\) or use an attribute without a value \(e.g. @prop\) instead. \("<div \[ERROR ->\]@someAnimation="value2">"\): TestComp@0:5/);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not issue a warning when host attributes contain a valid property-bound animation trigger',
|
it('should not issue a warning when host attributes contain a valid property-bound animation trigger',
|
||||||
() => {
|
() => {
|
||||||
|
const animationEntries = [new CompileAnimationEntryMetadata('prop', [])];
|
||||||
var dirA = CompileDirectiveMetadata.create({
|
var dirA = CompileDirectiveMetadata.create({
|
||||||
selector: 'div',
|
selector: 'div',
|
||||||
|
template: new CompileTemplateMetadata({animations: animationEntries}),
|
||||||
type: new CompileTypeMetadata(
|
type: new CompileTypeMetadata(
|
||||||
{moduleUrl: someModuleUrl, name: 'DirA', reference: {} as Type<any>}),
|
{moduleUrl: someModuleUrl, name: 'DirA', reference: {} as Type<any>}),
|
||||||
host: {'[@prop]': 'expr'}
|
host: {'[@prop]': 'expr'}
|
||||||
@ -360,14 +367,17 @@ Can't bind to 'invalidProp' since it isn't a known property of 'my-component'.
|
|||||||
|
|
||||||
it('should not issue a warning when an animation property is bound without an expression',
|
it('should not issue a warning when an animation property is bound without an expression',
|
||||||
() => {
|
() => {
|
||||||
humanizeTplAst(parse('<div @something>', []));
|
humanizeTplAst(parse('<div @someAnimation>', [], [], []));
|
||||||
expect(console.warnings.length).toEqual(0);
|
expect(console.warnings.length).toEqual(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should parse bound properties via [@] and not report them as attributes', () => {
|
it('should parse bound properties via [@] and not report them as attributes', () => {
|
||||||
expect(humanizeTplAst(parse('<div [@something]="value2">', []))).toEqual([
|
expect(humanizeTplAst(parse('<div [@someAnimation]="value2">', [], [], []))).toEqual([
|
||||||
[ElementAst, 'div'],
|
[ElementAst, 'div'],
|
||||||
[BoundElementPropertyAst, PropertyBindingType.Animation, 'something', 'value2', null]
|
[
|
||||||
|
BoundElementPropertyAst, PropertyBindingType.Animation, 'someAnimation', 'value2',
|
||||||
|
null
|
||||||
|
]
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,10 +0,0 @@
|
|||||||
/**
|
|
||||||
* @license
|
|
||||||
* Copyright Google Inc. All Rights Reserved.
|
|
||||||
*
|
|
||||||
* Use of this source code is governed by an MIT-style license that can be
|
|
||||||
* found in the LICENSE file at https://angular.io/license
|
|
||||||
*/
|
|
||||||
export class AnimationOutput {
|
|
||||||
constructor(public name: string, public phase: string, public fullPropertyName: string) {}
|
|
||||||
}
|
|
@ -9,7 +9,6 @@
|
|||||||
import {ANY_STATE as ANY_STATE_, DEFAULT_STATE as DEFAULT_STATE_, EMPTY_STATE as EMPTY_STATE_, FILL_STYLE_FLAG as FILL_STYLE_FLAG_} from './animation/animation_constants';
|
import {ANY_STATE as ANY_STATE_, DEFAULT_STATE as DEFAULT_STATE_, EMPTY_STATE as EMPTY_STATE_, FILL_STYLE_FLAG as FILL_STYLE_FLAG_} from './animation/animation_constants';
|
||||||
import {AnimationGroupPlayer as AnimationGroupPlayer_} from './animation/animation_group_player';
|
import {AnimationGroupPlayer as AnimationGroupPlayer_} from './animation/animation_group_player';
|
||||||
import {AnimationKeyframe as AnimationKeyframe_} from './animation/animation_keyframe';
|
import {AnimationKeyframe as AnimationKeyframe_} from './animation/animation_keyframe';
|
||||||
import {AnimationOutput as AnimationOutput_} from './animation/animation_output';
|
|
||||||
import {AnimationPlayer as AnimationPlayer_, NoOpAnimationPlayer as NoOpAnimationPlayer_} from './animation/animation_player';
|
import {AnimationPlayer as AnimationPlayer_, NoOpAnimationPlayer as NoOpAnimationPlayer_} from './animation/animation_player';
|
||||||
import {AnimationSequencePlayer as AnimationSequencePlayer_} from './animation/animation_sequence_player';
|
import {AnimationSequencePlayer as AnimationSequencePlayer_} from './animation/animation_sequence_player';
|
||||||
import * as animationUtils from './animation/animation_style_util';
|
import * as animationUtils from './animation/animation_style_util';
|
||||||
@ -115,7 +114,6 @@ export var __core_private__: {
|
|||||||
renderStyles: typeof animationUtils.renderStyles,
|
renderStyles: typeof animationUtils.renderStyles,
|
||||||
collectAndResolveStyles: typeof animationUtils.collectAndResolveStyles,
|
collectAndResolveStyles: typeof animationUtils.collectAndResolveStyles,
|
||||||
AnimationStyles: typeof AnimationStyles_, _AnimationStyles?: AnimationStyles_,
|
AnimationStyles: typeof AnimationStyles_, _AnimationStyles?: AnimationStyles_,
|
||||||
AnimationOutput: typeof AnimationOutput_, _AnimationOutput?: AnimationOutput_,
|
|
||||||
ANY_STATE: typeof ANY_STATE_,
|
ANY_STATE: typeof ANY_STATE_,
|
||||||
DEFAULT_STATE: typeof DEFAULT_STATE_,
|
DEFAULT_STATE: typeof DEFAULT_STATE_,
|
||||||
EMPTY_STATE: typeof EMPTY_STATE_,
|
EMPTY_STATE: typeof EMPTY_STATE_,
|
||||||
@ -183,7 +181,6 @@ export var __core_private__: {
|
|||||||
renderStyles: animationUtils.renderStyles,
|
renderStyles: animationUtils.renderStyles,
|
||||||
collectAndResolveStyles: animationUtils.collectAndResolveStyles,
|
collectAndResolveStyles: animationUtils.collectAndResolveStyles,
|
||||||
AnimationStyles: AnimationStyles_,
|
AnimationStyles: AnimationStyles_,
|
||||||
AnimationOutput: AnimationOutput_,
|
|
||||||
ANY_STATE: ANY_STATE_,
|
ANY_STATE: ANY_STATE_,
|
||||||
DEFAULT_STATE: DEFAULT_STATE_,
|
DEFAULT_STATE: DEFAULT_STATE_,
|
||||||
EMPTY_STATE: EMPTY_STATE_,
|
EMPTY_STATE: EMPTY_STATE_,
|
||||||
|
@ -7,7 +7,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {AnimationGroupPlayer} from '../animation/animation_group_player';
|
import {AnimationGroupPlayer} from '../animation/animation_group_player';
|
||||||
import {AnimationOutput} from '../animation/animation_output';
|
|
||||||
import {AnimationPlayer, NoOpAnimationPlayer} from '../animation/animation_player';
|
import {AnimationPlayer, NoOpAnimationPlayer} from '../animation/animation_player';
|
||||||
import {queueAnimation} from '../animation/animation_queue';
|
import {queueAnimation} from '../animation/animation_queue';
|
||||||
import {AnimationTransitionEvent} from '../animation/animation_transition_event';
|
import {AnimationTransitionEvent} from '../animation/animation_transition_event';
|
||||||
@ -53,7 +52,7 @@ export abstract class AppView<T> {
|
|||||||
|
|
||||||
public animationPlayers = new ViewAnimationMap();
|
public animationPlayers = new ViewAnimationMap();
|
||||||
|
|
||||||
private _animationListeners = new Map<any, _AnimationOutputWithHandler[]>();
|
private _animationListeners = new Map<any, _AnimationOutputHandler[]>();
|
||||||
|
|
||||||
public context: T;
|
public context: T;
|
||||||
|
|
||||||
@ -107,7 +106,7 @@ export abstract class AppView<T> {
|
|||||||
let listener = listeners[i];
|
let listener = listeners[i];
|
||||||
// we check for both the name in addition to the phase in the event
|
// we check for both the name in addition to the phase in the event
|
||||||
// that there may be more than one @trigger on the same element
|
// that there may be more than one @trigger on the same element
|
||||||
if (listener.output.name == animationName && listener.output.phase == phase) {
|
if (listener.eventName === animationName && listener.eventPhase === phase) {
|
||||||
listener.handler(event);
|
listener.handler(event);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -115,14 +114,13 @@ export abstract class AppView<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
registerAnimationOutput(element: any, outputEvent: AnimationOutput, eventHandler: Function):
|
registerAnimationOutput(
|
||||||
void {
|
element: any, eventName: string, eventPhase: string, eventHandler: Function): void {
|
||||||
var entry = new _AnimationOutputWithHandler(outputEvent, eventHandler);
|
|
||||||
var animations = this._animationListeners.get(element);
|
var animations = this._animationListeners.get(element);
|
||||||
if (!isPresent(animations)) {
|
if (!isPresent(animations)) {
|
||||||
this._animationListeners.set(element, animations = []);
|
this._animationListeners.set(element, animations = []);
|
||||||
}
|
}
|
||||||
animations.push(entry);
|
animations.push(new _AnimationOutputHandler(eventName, eventPhase, eventHandler));
|
||||||
}
|
}
|
||||||
|
|
||||||
create(context: T, givenProjectableNodes: Array<any|any[]>, rootSelectorOrNode: string|any):
|
create(context: T, givenProjectableNodes: Array<any|any[]>, rootSelectorOrNode: string|any):
|
||||||
@ -469,6 +467,6 @@ function _findLastRenderNode(node: any): any {
|
|||||||
return lastNode;
|
return lastNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
class _AnimationOutputWithHandler {
|
class _AnimationOutputHandler {
|
||||||
constructor(public output: AnimationOutput, public handler: Function) {}
|
constructor(public eventName: string, public eventPhase: string, public handler: Function) {}
|
||||||
}
|
}
|
||||||
|
@ -997,7 +997,7 @@ function declareTests({useJit}: {useJit: boolean}) {
|
|||||||
<div *ngIf="exp" [@outer]="exp">
|
<div *ngIf="exp" [@outer]="exp">
|
||||||
outer
|
outer
|
||||||
<div *ngIf="exp2" [@inner]="exp">
|
<div *ngIf="exp2" [@inner]="exp">
|
||||||
inner
|
inner
|
||||||
< </div>
|
< </div>
|
||||||
< </div>
|
< </div>
|
||||||
`,
|
`,
|
||||||
@ -1234,8 +1234,7 @@ function declareTests({useJit}: {useJit: boolean}) {
|
|||||||
message = e.message;
|
message = e.message;
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(message).toMatch(
|
expect(message).toMatch(/Couldn't find an animation entry for "something"/);
|
||||||
/- Couldn't find the corresponding animation trigger definition for \(@something\)/);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw an error if an animation output is referenced that is not bound to as a property on the same element',
|
it('should throw an error if an animation output is referenced that is not bound to as a property on the same element',
|
||||||
@ -1258,7 +1257,7 @@ function declareTests({useJit}: {useJit: boolean}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
expect(message).toMatch(
|
expect(message).toMatch(
|
||||||
/- Unable to listen on \(@trigger.done\) because the animation trigger \[@trigger\] isn't being used on the same element/);
|
/Unable to listen on \(@trigger.done\) because the animation trigger \[@trigger\] isn't being used on the same element/);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw an error if an unsupported animation output phase name is used', () => {
|
it('should throw an error if an unsupported animation output phase name is used', () => {
|
||||||
@ -1287,7 +1286,7 @@ function declareTests({useJit}: {useJit: boolean}) {
|
|||||||
TestBed.overrideComponent(DummyIfCmp, {
|
TestBed.overrideComponent(DummyIfCmp, {
|
||||||
set: {
|
set: {
|
||||||
template: `
|
template: `
|
||||||
<div (@trigger)="callback($event)"></div>
|
<div [@trigger]="exp" (@trigger)="callback($event)"></div>
|
||||||
`,
|
`,
|
||||||
animations: [trigger('trigger', [transition('one => two', [animate(1000)])])]
|
animations: [trigger('trigger', [transition('one => two', [animate(1000)])])]
|
||||||
}
|
}
|
||||||
@ -1319,7 +1318,7 @@ function declareTests({useJit}: {useJit: boolean}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
expect(message).toMatch(
|
expect(message).toMatch(
|
||||||
/Couldn't find the corresponding host-level animation trigger definition for \(@trigger\)/);
|
/Unable to listen on \(@trigger.done\) because the animation trigger \[@trigger\] isn't being used on the same element/);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should allow host and element-level animation bindings to be defined on the same tag/component',
|
it('should allow host and element-level animation bindings to be defined on the same tag/component',
|
||||||
@ -1480,11 +1479,27 @@ function declareTests({useJit}: {useJit: boolean}) {
|
|||||||
failureMessage = e.message;
|
failureMessage = e.message;
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(failureMessage)
|
expect(failureMessage).toMatch(/Template parse errors:/);
|
||||||
.toMatch(/Animation parsing for DummyIfCmp has failed due to the following errors:/);
|
expect(failureMessage).toMatch(/Couldn't find an animation entry for "status"/);
|
||||||
expect(failureMessage).toMatch(/- Couldn't find an animation entry for status/);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should throw an error if an animation trigger is registered but is already in use', () => {
|
||||||
|
TestBed.overrideComponent(
|
||||||
|
DummyIfCmp, {set: {animations: [trigger('matias', []), trigger('matias', [])]}});
|
||||||
|
|
||||||
|
var failureMessage = '';
|
||||||
|
try {
|
||||||
|
const fixture = TestBed.createComponent(DummyLoadingCmp);
|
||||||
|
} catch (e) {
|
||||||
|
failureMessage = e.message;
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(failureMessage).toMatch(/Animation parse errors:/);
|
||||||
|
expect(failureMessage)
|
||||||
|
.toMatch(
|
||||||
|
/The animation trigger "matias" has already been registered for the DummyIfCmp component/);
|
||||||
|
});
|
||||||
|
|
||||||
it('should be permitted to be registered on the host element', fakeAsync(() => {
|
it('should be permitted to be registered on the host element', fakeAsync(() => {
|
||||||
TestBed.overrideComponent(DummyLoadingCmp, {
|
TestBed.overrideComponent(DummyLoadingCmp, {
|
||||||
set: {
|
set: {
|
||||||
@ -1521,7 +1536,7 @@ function declareTests({useJit}: {useJit: boolean}) {
|
|||||||
failureMessage = e.message;
|
failureMessage = e.message;
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(failureMessage).toMatch(/- Couldn't find an animation entry for loading/);
|
expect(failureMessage).toMatch(/Couldn't find an animation entry for "loading"/);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should retain the destination animation state styles once the animation is complete',
|
it('should retain the destination animation state styles once the animation is complete',
|
||||||
|
Reference in New Issue
Block a user