feat(ivy): generate pipe references and definitions (#22034)
PR Close #22034
This commit is contained in:
parent
ee60bb5b36
commit
99909bbf2c
|
@ -20,6 +20,7 @@ import {NgModuleCompiler} from '../ng_module_compiler';
|
||||||
import {OutputEmitter} from '../output/abstract_emitter';
|
import {OutputEmitter} from '../output/abstract_emitter';
|
||||||
import * as o from '../output/output_ast';
|
import * as o from '../output/output_ast';
|
||||||
import {ParseError} from '../parse_util';
|
import {ParseError} from '../parse_util';
|
||||||
|
import {compilePipe as compileIvyPipe} from '../render3/r3_pipe_compiler';
|
||||||
import {compileComponent as compileIvyComponent, compileDirective as compileIvyDirective} from '../render3/r3_view_compiler';
|
import {compileComponent as compileIvyComponent, compileDirective as compileIvyDirective} from '../render3/r3_view_compiler';
|
||||||
import {CompiledStylesheet, StyleCompiler} from '../style_compiler';
|
import {CompiledStylesheet, StyleCompiler} from '../style_compiler';
|
||||||
import {SummaryResolver} from '../summary_resolver';
|
import {SummaryResolver} from '../summary_resolver';
|
||||||
|
@ -356,14 +357,22 @@ export class AotCompiler {
|
||||||
error(
|
error(
|
||||||
`Cannot determine the module for component '${identifierName(directiveMetadata.type)}'`);
|
`Cannot determine the module for component '${identifierName(directiveMetadata.type)}'`);
|
||||||
|
|
||||||
const {template: parsedTemplate} =
|
const {template: parsedTemplate, pipes: parsedPipes} =
|
||||||
this._parseTemplate(directiveMetadata, module, module.transitiveModule.directives);
|
this._parseTemplate(directiveMetadata, module, module.transitiveModule.directives);
|
||||||
compileIvyComponent(context, directiveMetadata, parsedTemplate, this._reflector);
|
compileIvyComponent(
|
||||||
|
context, directiveMetadata, parsedPipes, parsedTemplate, this._reflector);
|
||||||
} else {
|
} else {
|
||||||
compileIvyDirective(context, directiveMetadata, this._reflector);
|
compileIvyDirective(context, directiveMetadata, this._reflector);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
pipes.forEach(pipeType => {
|
||||||
|
const pipeMetadata = this._metadataResolver.getPipeMetadata(pipeType);
|
||||||
|
if (pipeMetadata) {
|
||||||
|
compileIvyPipe(context, pipeMetadata, this._reflector);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
injectables.forEach(injectable => this._injectableCompiler.compile(injectable, context));
|
injectables.forEach(injectable => this._injectableCompiler.compile(injectable, context));
|
||||||
|
|
||||||
if (context.statements && context.statements.length > 0) {
|
if (context.statements && context.statements.length > 0) {
|
||||||
|
|
|
@ -11,7 +11,7 @@ import {OutputContext, error} from './util';
|
||||||
|
|
||||||
const CONSTANT_PREFIX = '_c';
|
const CONSTANT_PREFIX = '_c';
|
||||||
|
|
||||||
export const enum DefinitionKind {Injector, Directive, Component}
|
export const enum DefinitionKind {Injector, Directive, Component, Pipe}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A node that is a place-holder that allows the node to be replaced when the actual
|
* A node that is a place-holder that allows the node to be replaced when the actual
|
||||||
|
@ -51,6 +51,7 @@ export class ConstantPool {
|
||||||
private injectorDefinitions = new Map<any, FixupExpression>();
|
private injectorDefinitions = new Map<any, FixupExpression>();
|
||||||
private directiveDefinitions = new Map<any, FixupExpression>();
|
private directiveDefinitions = new Map<any, FixupExpression>();
|
||||||
private componentDefinitions = new Map<any, FixupExpression>();
|
private componentDefinitions = new Map<any, FixupExpression>();
|
||||||
|
private pipeDefinitions = new Map<any, FixupExpression>();
|
||||||
|
|
||||||
private nextNameIndex = 0;
|
private nextNameIndex = 0;
|
||||||
|
|
||||||
|
@ -75,18 +76,19 @@ export class ConstantPool {
|
||||||
return fixup;
|
return fixup;
|
||||||
}
|
}
|
||||||
|
|
||||||
getDefinition(type: any, kind: DefinitionKind, ctx: OutputContext): o.Expression {
|
getDefinition(type: any, kind: DefinitionKind, ctx: OutputContext, forceShared: boolean = false):
|
||||||
const declarations = kind == DefinitionKind.Component ?
|
o.Expression {
|
||||||
this.componentDefinitions :
|
const definitions = this.definitionsOf(kind);
|
||||||
kind == DefinitionKind.Directive ? this.directiveDefinitions : this.injectorDefinitions;
|
let fixup = definitions.get(type);
|
||||||
let fixup = declarations.get(type);
|
let newValue = false;
|
||||||
if (!fixup) {
|
if (!fixup) {
|
||||||
const property = kind == DefinitionKind.Component ?
|
const property = this.propertyNameOf(kind);
|
||||||
'ngComponentDef' :
|
|
||||||
kind == DefinitionKind.Directive ? 'ngDirectiveDef' : 'ngInjectorDef';
|
|
||||||
fixup = new FixupExpression(ctx.importExpr(type).prop(property));
|
fixup = new FixupExpression(ctx.importExpr(type).prop(property));
|
||||||
declarations.set(type, fixup);
|
definitions.set(type, fixup);
|
||||||
} else if (!fixup.shared) {
|
newValue = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((!newValue && !fixup.shared) || (newValue && forceShared)) {
|
||||||
const name = this.freshName();
|
const name = this.freshName();
|
||||||
this.statements.push(
|
this.statements.push(
|
||||||
o.variable(name).set(fixup.resolved).toDeclStmt(o.INFERRED_TYPE, [o.StmtModifier.Final]));
|
o.variable(name).set(fixup.resolved).toDeclStmt(o.INFERRED_TYPE, [o.StmtModifier.Final]));
|
||||||
|
@ -104,6 +106,36 @@ export class ConstantPool {
|
||||||
*/
|
*/
|
||||||
uniqueName(prefix: string): string { return `${prefix}${this.nextNameIndex++}`; }
|
uniqueName(prefix: string): string { return `${prefix}${this.nextNameIndex++}`; }
|
||||||
|
|
||||||
|
private definitionsOf(kind: DefinitionKind): Map<any, FixupExpression> {
|
||||||
|
switch (kind) {
|
||||||
|
case DefinitionKind.Component:
|
||||||
|
return this.componentDefinitions;
|
||||||
|
case DefinitionKind.Directive:
|
||||||
|
return this.directiveDefinitions;
|
||||||
|
case DefinitionKind.Injector:
|
||||||
|
return this.injectorDefinitions;
|
||||||
|
case DefinitionKind.Pipe:
|
||||||
|
return this.pipeDefinitions;
|
||||||
|
}
|
||||||
|
error(`Unknown definition kind ${kind}`);
|
||||||
|
return this.componentDefinitions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public propertyNameOf(kind: DefinitionKind): string {
|
||||||
|
switch (kind) {
|
||||||
|
case DefinitionKind.Component:
|
||||||
|
return 'ngComponentDef';
|
||||||
|
case DefinitionKind.Directive:
|
||||||
|
return 'ngDirectiveDef';
|
||||||
|
case DefinitionKind.Injector:
|
||||||
|
return 'ngInjectorDef';
|
||||||
|
case DefinitionKind.Pipe:
|
||||||
|
return 'ngPipeDef';
|
||||||
|
}
|
||||||
|
error(`Unknown definition kind ${kind}`);
|
||||||
|
return '<unknown>';
|
||||||
|
}
|
||||||
|
|
||||||
private freshName(): string { return this.uniqueName(CONSTANT_PREFIX); }
|
private freshName(): string { return this.uniqueName(CONSTANT_PREFIX); }
|
||||||
|
|
||||||
private keyOf(expression: o.Expression) {
|
private keyOf(expression: o.Expression) {
|
||||||
|
|
|
@ -435,6 +435,173 @@ export class AstTransformer implements AstVisitor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A transformer that only creates new nodes if the transformer makes a change or
|
||||||
|
// a change is made a child node.
|
||||||
|
export class AstMemoryEfficientTransformer implements AstVisitor {
|
||||||
|
visitImplicitReceiver(ast: ImplicitReceiver, context: any): AST { return ast; }
|
||||||
|
|
||||||
|
visitInterpolation(ast: Interpolation, context: any): Interpolation {
|
||||||
|
const expressions = this.visitAll(ast.expressions);
|
||||||
|
if (expressions !== ast.expressions)
|
||||||
|
return new Interpolation(ast.span, ast.strings, expressions);
|
||||||
|
return ast;
|
||||||
|
}
|
||||||
|
|
||||||
|
visitLiteralPrimitive(ast: LiteralPrimitive, context: any): AST { return ast; }
|
||||||
|
|
||||||
|
visitPropertyRead(ast: PropertyRead, context: any): AST {
|
||||||
|
const receiver = ast.receiver.visit(this);
|
||||||
|
if (receiver !== ast.receiver) {
|
||||||
|
return new PropertyRead(ast.span, receiver, ast.name);
|
||||||
|
}
|
||||||
|
return ast;
|
||||||
|
}
|
||||||
|
|
||||||
|
visitPropertyWrite(ast: PropertyWrite, context: any): AST {
|
||||||
|
const receiver = ast.receiver.visit(this);
|
||||||
|
const value = ast.value.visit(this);
|
||||||
|
if (receiver !== ast.receiver || value !== ast.value) {
|
||||||
|
return new PropertyWrite(ast.span, receiver, ast.name, value);
|
||||||
|
}
|
||||||
|
return ast;
|
||||||
|
}
|
||||||
|
|
||||||
|
visitSafePropertyRead(ast: SafePropertyRead, context: any): AST {
|
||||||
|
const receiver = ast.receiver.visit(this);
|
||||||
|
if (receiver !== ast.receiver) {
|
||||||
|
return new SafePropertyRead(ast.span, receiver, ast.name);
|
||||||
|
}
|
||||||
|
return ast;
|
||||||
|
}
|
||||||
|
|
||||||
|
visitMethodCall(ast: MethodCall, context: any): AST {
|
||||||
|
const receiver = ast.receiver.visit(this);
|
||||||
|
if (receiver !== ast.receiver) {
|
||||||
|
return new MethodCall(ast.span, receiver, ast.name, this.visitAll(ast.args));
|
||||||
|
}
|
||||||
|
return ast;
|
||||||
|
}
|
||||||
|
|
||||||
|
visitSafeMethodCall(ast: SafeMethodCall, context: any): AST {
|
||||||
|
const receiver = ast.receiver.visit(this);
|
||||||
|
const args = this.visitAll(ast.args);
|
||||||
|
if (receiver !== ast.receiver || args !== ast.args) {
|
||||||
|
return new SafeMethodCall(ast.span, receiver, ast.name, args);
|
||||||
|
}
|
||||||
|
return ast;
|
||||||
|
}
|
||||||
|
|
||||||
|
visitFunctionCall(ast: FunctionCall, context: any): AST {
|
||||||
|
const target = ast.target && ast.target.visit(this);
|
||||||
|
const args = this.visitAll(ast.args);
|
||||||
|
if (target !== ast.target || args !== ast.args) {
|
||||||
|
return new FunctionCall(ast.span, target, args);
|
||||||
|
}
|
||||||
|
return ast;
|
||||||
|
}
|
||||||
|
|
||||||
|
visitLiteralArray(ast: LiteralArray, context: any): AST {
|
||||||
|
const expressions = this.visitAll(ast.expressions);
|
||||||
|
if (expressions !== ast.expressions) {
|
||||||
|
return new LiteralArray(ast.span, expressions);
|
||||||
|
}
|
||||||
|
return ast;
|
||||||
|
}
|
||||||
|
|
||||||
|
visitLiteralMap(ast: LiteralMap, context: any): AST {
|
||||||
|
const values = this.visitAll(ast.values);
|
||||||
|
if (values !== ast.values) {
|
||||||
|
return new LiteralMap(ast.span, ast.keys, values);
|
||||||
|
}
|
||||||
|
return ast;
|
||||||
|
}
|
||||||
|
|
||||||
|
visitBinary(ast: Binary, context: any): AST {
|
||||||
|
const left = ast.left.visit(this);
|
||||||
|
const right = ast.right.visit(this);
|
||||||
|
if (left !== ast.left || right !== ast.right) {
|
||||||
|
return new Binary(ast.span, ast.operation, left, right);
|
||||||
|
}
|
||||||
|
return ast;
|
||||||
|
}
|
||||||
|
|
||||||
|
visitPrefixNot(ast: PrefixNot, context: any): AST {
|
||||||
|
const expression = ast.expression.visit(this);
|
||||||
|
if (expression !== ast.expression) {
|
||||||
|
return new PrefixNot(ast.span, expression);
|
||||||
|
}
|
||||||
|
return ast;
|
||||||
|
}
|
||||||
|
|
||||||
|
visitNonNullAssert(ast: NonNullAssert, context: any): AST {
|
||||||
|
const expression = ast.expression.visit(this);
|
||||||
|
if (expression !== ast.expression) {
|
||||||
|
return new NonNullAssert(ast.span, expression);
|
||||||
|
}
|
||||||
|
return ast;
|
||||||
|
}
|
||||||
|
|
||||||
|
visitConditional(ast: Conditional, context: any): AST {
|
||||||
|
const condition = ast.condition.visit(this);
|
||||||
|
const trueExp = ast.trueExp.visit(this);
|
||||||
|
const falseExp = ast.falseExp.visit(this);
|
||||||
|
if (condition !== ast.condition || trueExp !== ast.trueExp || falseExp !== falseExp) {
|
||||||
|
return new Conditional(ast.span, condition, trueExp, falseExp);
|
||||||
|
}
|
||||||
|
return ast;
|
||||||
|
}
|
||||||
|
|
||||||
|
visitPipe(ast: BindingPipe, context: any): AST {
|
||||||
|
const exp = ast.exp.visit(this);
|
||||||
|
const args = this.visitAll(ast.args);
|
||||||
|
if (exp !== ast.exp || args !== ast.args) {
|
||||||
|
return new BindingPipe(ast.span, exp, ast.name, args);
|
||||||
|
}
|
||||||
|
return ast;
|
||||||
|
}
|
||||||
|
|
||||||
|
visitKeyedRead(ast: KeyedRead, context: any): AST {
|
||||||
|
const obj = ast.obj.visit(this);
|
||||||
|
const key = ast.key.visit(this);
|
||||||
|
if (obj !== ast.obj || key !== ast.key) {
|
||||||
|
return new KeyedRead(ast.span, obj, key);
|
||||||
|
}
|
||||||
|
return ast;
|
||||||
|
}
|
||||||
|
|
||||||
|
visitKeyedWrite(ast: KeyedWrite, context: any): AST {
|
||||||
|
const obj = ast.obj.visit(this);
|
||||||
|
const key = ast.key.visit(this);
|
||||||
|
const value = ast.value.visit(this);
|
||||||
|
if (obj !== ast.obj || key !== ast.key || value !== ast.value) {
|
||||||
|
return new KeyedWrite(ast.span, obj, key, value);
|
||||||
|
}
|
||||||
|
return ast;
|
||||||
|
}
|
||||||
|
|
||||||
|
visitAll(asts: any[]): any[] {
|
||||||
|
const res = new Array(asts.length);
|
||||||
|
let modified = false;
|
||||||
|
for (let i = 0; i < asts.length; ++i) {
|
||||||
|
const original = asts[i];
|
||||||
|
const value = original.visit(this);
|
||||||
|
res[i] = value;
|
||||||
|
modified = modified || value !== original;
|
||||||
|
}
|
||||||
|
return modified ? res : asts;
|
||||||
|
}
|
||||||
|
|
||||||
|
visitChain(ast: Chain, context: any): AST {
|
||||||
|
const expressions = this.visitAll(ast.expressions);
|
||||||
|
if (expressions !== ast.expressions) {
|
||||||
|
return new Chain(ast.span, expressions);
|
||||||
|
}
|
||||||
|
return ast;
|
||||||
|
}
|
||||||
|
|
||||||
|
visitQuote(ast: Quote, context: any): AST { return ast; }
|
||||||
|
}
|
||||||
|
|
||||||
export function visitAstChildren(ast: AST, visitor: AstVisitor, context?: any) {
|
export function visitAstChildren(ast: AST, visitor: AstVisitor, context?: any) {
|
||||||
function visit(ast: AST) {
|
function visit(ast: AST) {
|
||||||
visitor.visit && visitor.visit(ast, context) || ast.visit(visitor, context);
|
visitor.visit && visitor.visit(ast, context) || ast.visit(visitor, context);
|
||||||
|
|
|
@ -21,6 +21,7 @@ export class Identifiers {
|
||||||
/* Methods */
|
/* Methods */
|
||||||
static NEW_METHOD = 'n';
|
static NEW_METHOD = 'n';
|
||||||
static HOST_BINDING_METHOD = 'h';
|
static HOST_BINDING_METHOD = 'h';
|
||||||
|
static TRANSFORM_METHOD = 'transform';
|
||||||
|
|
||||||
/* Instructions */
|
/* Instructions */
|
||||||
static createElement: o.ExternalReference = {name: 'ɵE', moduleName: CORE};
|
static createElement: o.ExternalReference = {name: 'ɵE', moduleName: CORE};
|
||||||
|
@ -63,8 +64,16 @@ export class Identifiers {
|
||||||
static interpolation8: o.ExternalReference = {name: 'ɵi8', moduleName: CORE};
|
static interpolation8: o.ExternalReference = {name: 'ɵi8', moduleName: CORE};
|
||||||
static interpolationV: o.ExternalReference = {name: 'ɵiV', moduleName: CORE};
|
static interpolationV: o.ExternalReference = {name: 'ɵiV', moduleName: CORE};
|
||||||
|
|
||||||
|
static pipeBind1: o.ExternalReference = {name: 'ɵpb1', moduleName: CORE};
|
||||||
|
static pipeBind2: o.ExternalReference = {name: 'ɵpb2', moduleName: CORE};
|
||||||
|
static pipeBind3: o.ExternalReference = {name: 'ɵpb3', moduleName: CORE};
|
||||||
|
static pipeBind4: o.ExternalReference = {name: 'ɵpb4', moduleName: CORE};
|
||||||
|
static pipeBindV: o.ExternalReference = {name: 'ɵpbV', moduleName: CORE};
|
||||||
|
|
||||||
static load: o.ExternalReference = {name: 'ɵld', moduleName: CORE};
|
static load: o.ExternalReference = {name: 'ɵld', moduleName: CORE};
|
||||||
|
|
||||||
|
static pipe: o.ExternalReference = {name: 'ɵPp', moduleName: CORE};
|
||||||
|
|
||||||
static projection: o.ExternalReference = {name: 'ɵP', moduleName: CORE};
|
static projection: o.ExternalReference = {name: 'ɵP', moduleName: CORE};
|
||||||
static projectionDef: o.ExternalReference = {name: 'ɵpD', moduleName: CORE};
|
static projectionDef: o.ExternalReference = {name: 'ɵpD', moduleName: CORE};
|
||||||
|
|
||||||
|
@ -88,5 +97,7 @@ export class Identifiers {
|
||||||
moduleName: CORE,
|
moduleName: CORE,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static definePipe: o.ExternalReference = {name: 'ɵdefinePipe', moduleName: CORE};
|
||||||
|
|
||||||
static NgOnChangesFeature: o.ExternalReference = {name: 'ɵNgOnChangesFeature', moduleName: CORE};
|
static NgOnChangesFeature: o.ExternalReference = {name: 'ɵNgOnChangesFeature', moduleName: CORE};
|
||||||
}
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
/**
|
||||||
|
* @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
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {CompileDirectiveMetadata, CompilePipeMetadata, identifierName} from '../compile_metadata';
|
||||||
|
import {CompileReflector} from '../compile_reflector';
|
||||||
|
import {DefinitionKind} from '../constant_pool';
|
||||||
|
import * as o from '../output/output_ast';
|
||||||
|
import {OutputContext, error} from '../util';
|
||||||
|
|
||||||
|
import {Identifiers as R3} from './r3_identifiers';
|
||||||
|
import {createFactory} from './r3_view_compiler';
|
||||||
|
|
||||||
|
export function compilePipe(
|
||||||
|
outputCtx: OutputContext, pipe: CompilePipeMetadata, reflector: CompileReflector) {
|
||||||
|
const definitionMapValues: {key: string, quoted: boolean, value: o.Expression}[] = [];
|
||||||
|
|
||||||
|
// e.g. 'type: MyPipe`
|
||||||
|
definitionMapValues.push(
|
||||||
|
{key: 'type', value: outputCtx.importExpr(pipe.type.reference), quoted: false});
|
||||||
|
|
||||||
|
// e.g. factory: function MyPipe_Factory() { return new MyPipe(); },
|
||||||
|
const templateFactory = createFactory(pipe.type, outputCtx, reflector);
|
||||||
|
definitionMapValues.push({key: 'factory', value: templateFactory, quoted: false});
|
||||||
|
|
||||||
|
// e.g. pure: true
|
||||||
|
if (pipe.pure) {
|
||||||
|
definitionMapValues.push({key: 'pure', value: o.literal(true), quoted: false});
|
||||||
|
}
|
||||||
|
|
||||||
|
const className = identifierName(pipe.type) !;
|
||||||
|
className || error(`Cannot resolve the name of ${pipe.type}`);
|
||||||
|
|
||||||
|
outputCtx.statements.push(new o.ClassStmt(
|
||||||
|
/* name */ className,
|
||||||
|
/* parent */ null,
|
||||||
|
/* fields */[new o.ClassField(
|
||||||
|
/* name */ outputCtx.constantPool.propertyNameOf(DefinitionKind.Pipe),
|
||||||
|
/* type */ o.INFERRED_TYPE,
|
||||||
|
/* modifiers */[o.StmtModifier.Static],
|
||||||
|
/* initializer */ o.importExpr(R3.definePipe).callFn([o.literalMap(
|
||||||
|
definitionMapValues)]))],
|
||||||
|
/* getters */[],
|
||||||
|
/* constructorMethod */ new o.ClassMethod(null, [], []),
|
||||||
|
/* methods */[]));
|
||||||
|
}
|
|
@ -8,9 +8,9 @@
|
||||||
|
|
||||||
import {CompileDirectiveMetadata, CompilePipeSummary, CompileTokenMetadata, CompileTypeMetadata, flatten, identifierName, rendererTypeName, tokenReference, viewClassName} from '../compile_metadata';
|
import {CompileDirectiveMetadata, CompilePipeSummary, CompileTokenMetadata, CompileTypeMetadata, flatten, identifierName, rendererTypeName, tokenReference, viewClassName} from '../compile_metadata';
|
||||||
import {CompileReflector} from '../compile_reflector';
|
import {CompileReflector} from '../compile_reflector';
|
||||||
import {BindingForm, BuiltinConverter, EventHandlerVars, LocalResolver, convertActionBinding, convertPropertyBinding, convertPropertyBindingBuiltins} from '../compiler_util/expression_converter';
|
import {BindingForm, BuiltinConverter, ConvertPropertyBindingResult, EventHandlerVars, LocalResolver, convertActionBinding, convertPropertyBinding, convertPropertyBindingBuiltins} from '../compiler_util/expression_converter';
|
||||||
import {ConstantPool, DefinitionKind} from '../constant_pool';
|
import {ConstantPool, DefinitionKind} from '../constant_pool';
|
||||||
import {AST} from '../expression_parser/ast';
|
import {AST, AstMemoryEfficientTransformer, AstTransformer, BindingPipe, FunctionCall, ImplicitReceiver, LiteralPrimitive, MethodCall, ParseSpan, PropertyRead} from '../expression_parser/ast';
|
||||||
import {Identifiers} from '../identifiers';
|
import {Identifiers} from '../identifiers';
|
||||||
import {LifecycleHooks} from '../lifecycle_reflector';
|
import {LifecycleHooks} from '../lifecycle_reflector';
|
||||||
import * as o from '../output/output_ast';
|
import * as o from '../output/output_ast';
|
||||||
|
@ -21,6 +21,7 @@ import {OutputContext, error} from '../util';
|
||||||
|
|
||||||
import {Identifiers as R3} from './r3_identifiers';
|
import {Identifiers as R3} from './r3_identifiers';
|
||||||
|
|
||||||
|
|
||||||
/** Name of the context parameter passed into a template function */
|
/** Name of the context parameter passed into a template function */
|
||||||
const CONTEXT_NAME = 'ctx';
|
const CONTEXT_NAME = 'ctx';
|
||||||
|
|
||||||
|
@ -62,7 +63,7 @@ export function compileDirective(
|
||||||
/* name */ className,
|
/* name */ className,
|
||||||
/* parent */ null,
|
/* parent */ null,
|
||||||
/* fields */[new o.ClassField(
|
/* fields */[new o.ClassField(
|
||||||
/* name */ 'ngDirectiveDef',
|
/* name */ outputCtx.constantPool.propertyNameOf(DefinitionKind.Directive),
|
||||||
/* type */ o.INFERRED_TYPE,
|
/* type */ o.INFERRED_TYPE,
|
||||||
/* modifiers */[o.StmtModifier.Static],
|
/* modifiers */[o.StmtModifier.Static],
|
||||||
/* initializer */ o.importExpr(R3.defineDirective).callFn([o.literalMap(
|
/* initializer */ o.importExpr(R3.defineDirective).callFn([o.literalMap(
|
||||||
|
@ -73,8 +74,8 @@ export function compileDirective(
|
||||||
}
|
}
|
||||||
|
|
||||||
export function compileComponent(
|
export function compileComponent(
|
||||||
outputCtx: OutputContext, component: CompileDirectiveMetadata, template: TemplateAst[],
|
outputCtx: OutputContext, component: CompileDirectiveMetadata, pipes: CompilePipeSummary[],
|
||||||
reflector: CompileReflector) {
|
template: TemplateAst[], reflector: CompileReflector) {
|
||||||
const definitionMapValues: {key: string, quoted: boolean, value: o.Expression}[] = [];
|
const definitionMapValues: {key: string, quoted: boolean, value: o.Expression}[] = [];
|
||||||
|
|
||||||
// e.g. `type: MyApp`
|
// e.g. `type: MyApp`
|
||||||
|
@ -112,10 +113,11 @@ export function compileComponent(
|
||||||
// e.g. `template: function MyComponent_Template(_ctx, _cm) {...}`
|
// e.g. `template: function MyComponent_Template(_ctx, _cm) {...}`
|
||||||
const templateTypeName = component.type.reference.name;
|
const templateTypeName = component.type.reference.name;
|
||||||
const templateName = templateTypeName ? `${templateTypeName}_Template` : null;
|
const templateName = templateTypeName ? `${templateTypeName}_Template` : null;
|
||||||
|
const pipeMap = new Map(pipes.map<[string, CompilePipeSummary]>(pipe => [pipe.name, pipe]));
|
||||||
const templateFunctionExpression =
|
const templateFunctionExpression =
|
||||||
new TemplateDefinitionBuilder(
|
new TemplateDefinitionBuilder(
|
||||||
outputCtx, outputCtx.constantPool, reflector, CONTEXT_NAME, ROOT_SCOPE.nestedScope(), 0,
|
outputCtx, outputCtx.constantPool, reflector, CONTEXT_NAME, ROOT_SCOPE.nestedScope(), 0,
|
||||||
component.template !.ngContentSelectors, templateTypeName, templateName)
|
component.template !.ngContentSelectors, templateTypeName, templateName, pipeMap)
|
||||||
.buildTemplateFunction(template, []);
|
.buildTemplateFunction(template, []);
|
||||||
definitionMapValues.push({key: 'template', value: templateFunctionExpression, quoted: false});
|
definitionMapValues.push({key: 'template', value: templateFunctionExpression, quoted: false});
|
||||||
|
|
||||||
|
@ -143,7 +145,7 @@ export function compileComponent(
|
||||||
/* name */ className,
|
/* name */ className,
|
||||||
/* parent */ null,
|
/* parent */ null,
|
||||||
/* fields */[new o.ClassField(
|
/* fields */[new o.ClassField(
|
||||||
/* name */ 'ngComponentDef',
|
/* name */ outputCtx.constantPool.propertyNameOf(DefinitionKind.Component),
|
||||||
/* type */ o.INFERRED_TYPE,
|
/* type */ o.INFERRED_TYPE,
|
||||||
/* modifiers */[o.StmtModifier.Static],
|
/* modifiers */[o.StmtModifier.Static],
|
||||||
/* initializer */ o.importExpr(R3.defineComponent).callFn([o.literalMap(
|
/* initializer */ o.importExpr(R3.defineComponent).callFn([o.literalMap(
|
||||||
|
@ -199,6 +201,22 @@ function interpolate(args: o.Expression[]): o.Expression {
|
||||||
return o.importExpr(R3.interpolationV).callFn([o.literalArr(args)]);
|
return o.importExpr(R3.interpolationV).callFn([o.literalArr(args)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function pipeBinding(args: o.Expression[]): o.ExternalReference {
|
||||||
|
switch (args.length) {
|
||||||
|
case 0:
|
||||||
|
// The first parameter to pipeBind is always the value to be transformed followed
|
||||||
|
// by arg.length arguments so the total number of arguments to pipeBind are
|
||||||
|
// arg.length + 1.
|
||||||
|
return R3.pipeBind1;
|
||||||
|
case 1:
|
||||||
|
return R3.pipeBind2;
|
||||||
|
case 2:
|
||||||
|
return R3.pipeBind3;
|
||||||
|
default:
|
||||||
|
return R3.pipeBindV;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class BindingScope {
|
class BindingScope {
|
||||||
private map = new Map<string, o.Expression>();
|
private map = new Map<string, o.Expression>();
|
||||||
private referenceNameIndex = 0;
|
private referenceNameIndex = 0;
|
||||||
|
@ -219,10 +237,10 @@ class BindingScope {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
set(name: string, variableName: string): BindingScope {
|
set(name: string, value: o.Expression): BindingScope {
|
||||||
!this.map.has(name) ||
|
!this.map.has(name) ||
|
||||||
error(`The name ${name} is already defined in scope to be ${this.map.get(name)}`);
|
error(`The name ${name} is already defined in scope to be ${this.map.get(name)}`);
|
||||||
this.map.set(name, o.variable(variableName));
|
this.map.set(name, value);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -236,7 +254,7 @@ class BindingScope {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const ROOT_SCOPE = new BindingScope(null).set('$event', '$event');
|
const ROOT_SCOPE = new BindingScope(null).set('$event', o.variable('$event'));
|
||||||
|
|
||||||
class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
|
class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
|
||||||
private _dataIndex = 0;
|
private _dataIndex = 0;
|
||||||
|
@ -251,6 +269,7 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
|
||||||
private _postfix: o.Statement[] = [];
|
private _postfix: o.Statement[] = [];
|
||||||
private _contentProjections: Map<NgContentAst, NgContentInfo>;
|
private _contentProjections: Map<NgContentAst, NgContentInfo>;
|
||||||
private _projectionDefinitionIndex = 0;
|
private _projectionDefinitionIndex = 0;
|
||||||
|
private _pipeConverter: PipeConverter;
|
||||||
private unsupported = unsupported;
|
private unsupported = unsupported;
|
||||||
private invalid = invalid;
|
private invalid = invalid;
|
||||||
|
|
||||||
|
@ -258,7 +277,23 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
|
||||||
private outputCtx: OutputContext, private constantPool: ConstantPool,
|
private outputCtx: OutputContext, private constantPool: ConstantPool,
|
||||||
private reflector: CompileReflector, private contextParameter: string,
|
private reflector: CompileReflector, private contextParameter: string,
|
||||||
private bindingScope: BindingScope, private level = 0, private ngContentSelectors: string[],
|
private bindingScope: BindingScope, private level = 0, private ngContentSelectors: string[],
|
||||||
private contextName: string|null, private templateName: string|null) {}
|
private contextName: string|null, private templateName: string|null,
|
||||||
|
private pipes: Map<string, CompilePipeSummary>) {
|
||||||
|
this._pipeConverter =
|
||||||
|
new PipeConverter(() => this.allocateDataSlot(), (name, localName, slot, value) => {
|
||||||
|
bindingScope.set(localName, value);
|
||||||
|
const pipe = pipes.get(name) !;
|
||||||
|
pipe || error(`Could not find pipe ${name}`);
|
||||||
|
const pipeDefinition = constantPool.getDefinition(
|
||||||
|
pipe.type.reference, DefinitionKind.Pipe, outputCtx, /* forceShared */ true);
|
||||||
|
this._creationMode.push(
|
||||||
|
o.importExpr(R3.pipe)
|
||||||
|
.callFn([
|
||||||
|
o.literal(slot), pipeDefinition, pipeDefinition.callMethod(R3.NEW_METHOD, [])
|
||||||
|
])
|
||||||
|
.toStmt());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
buildTemplateFunction(asts: TemplateAst[], variables: VariableAst[]): o.FunctionExpr {
|
buildTemplateFunction(asts: TemplateAst[], variables: VariableAst[]): o.FunctionExpr {
|
||||||
// Create variable bindings
|
// Create variable bindings
|
||||||
|
@ -272,7 +307,7 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Add the reference to the local scope.
|
// Add the reference to the local scope.
|
||||||
this.bindingScope.set(variableName, scopedName);
|
this.bindingScope.set(variableName, o.variable(scopedName));
|
||||||
|
|
||||||
// Declare the local variable in binding mode
|
// Declare the local variable in binding mode
|
||||||
this._bindingMode.push(declaration);
|
this._bindingMode.push(declaration);
|
||||||
|
@ -332,8 +367,10 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
|
||||||
o.INFERRED_TYPE, null, this.templateName);
|
o.INFERRED_TYPE, null, this.templateName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LocalResolver
|
||||||
getLocal(name: string): o.Expression|null { return this.bindingScope.get(name); }
|
getLocal(name: string): o.Expression|null { return this.bindingScope.get(name); }
|
||||||
|
|
||||||
|
// TemplateAstVisitor
|
||||||
visitNgContent(ast: NgContentAst) {
|
visitNgContent(ast: NgContentAst) {
|
||||||
const info = this._contentProjections.get(ast) !;
|
const info = this._contentProjections.get(ast) !;
|
||||||
info || error(`Expected ${ast.sourceSpan} to be included in content projection collection`);
|
info || error(`Expected ${ast.sourceSpan} to be included in content projection collection`);
|
||||||
|
@ -361,6 +398,7 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TemplateAstVisitor
|
||||||
visitElement(ast: ElementAst) {
|
visitElement(ast: ElementAst) {
|
||||||
let bindingCount = 0;
|
let bindingCount = 0;
|
||||||
const elementIndex = this.allocateDataSlot();
|
const elementIndex = this.allocateDataSlot();
|
||||||
|
@ -410,7 +448,7 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
|
||||||
this._bindingMode.push(o.variable(variableName, o.INFERRED_TYPE)
|
this._bindingMode.push(o.variable(variableName, o.INFERRED_TYPE)
|
||||||
.set(o.importExpr(R3.load).callFn([o.literal(slot)]))
|
.set(o.importExpr(R3.load).callFn([o.literal(slot)]))
|
||||||
.toDeclStmt(o.INFERRED_TYPE, [o.StmtModifier.Final]));
|
.toDeclStmt(o.INFERRED_TYPE, [o.StmtModifier.Final]));
|
||||||
this.bindingScope.set(reference.name, variableName);
|
this.bindingScope.set(reference.name, o.variable(variableName));
|
||||||
return [reference.name, reference.originalValue];
|
return [reference.name, reference.originalValue];
|
||||||
})).map(value => o.literal(value));
|
})).map(value => o.literal(value));
|
||||||
parameters.push(
|
parameters.push(
|
||||||
|
@ -434,18 +472,14 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
|
||||||
if (input.isAnimation) {
|
if (input.isAnimation) {
|
||||||
this.unsupported('animations');
|
this.unsupported('animations');
|
||||||
}
|
}
|
||||||
// TODO(chuckj): Built-in transform?
|
const convertedBinding = this.convertPropertyBinding(implicit, input.value);
|
||||||
const convertedBinding = convertPropertyBinding(
|
const parameters = [o.literal(elementIndex), o.literal(input.name), convertedBinding];
|
||||||
this, implicit, input.value, this.bindingContext(), BindingForm.TrySimple, interpolate);
|
|
||||||
this._bindingMode.push(...convertedBinding.stmts);
|
|
||||||
const parameters =
|
|
||||||
[o.literal(elementIndex), o.literal(input.name), convertedBinding.currValExpr];
|
|
||||||
const instruction = BINDING_INSTRUCTION_MAP[input.type];
|
const instruction = BINDING_INSTRUCTION_MAP[input.type];
|
||||||
if (instruction) {
|
if (instruction) {
|
||||||
// TODO(chuckj): runtime: security context?
|
// TODO(chuckj): runtime: security context?
|
||||||
this.instruction(
|
this.instruction(
|
||||||
this._bindingMode, input.sourceSpan, instruction, o.literal(elementIndex),
|
this._bindingMode, input.sourceSpan, instruction, o.literal(elementIndex),
|
||||||
o.literal(input.name), convertedBinding.currValExpr);
|
o.literal(input.name), convertedBinding);
|
||||||
} else {
|
} else {
|
||||||
this.unsupported(`binding ${PropertyBindingType[input.type]}`);
|
this.unsupported(`binding ${PropertyBindingType[input.type]}`);
|
||||||
}
|
}
|
||||||
|
@ -479,13 +513,10 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
|
||||||
|
|
||||||
// Bindings
|
// Bindings
|
||||||
for (const input of directive.inputs) {
|
for (const input of directive.inputs) {
|
||||||
const convertedBinding = convertPropertyBinding(
|
const convertedBinding = this.convertPropertyBinding(implicit, input.value);
|
||||||
this, implicit, input.value, this.bindingContext(), BindingForm.TrySimple, interpolate);
|
|
||||||
this._bindingMode.push(...convertedBinding.stmts);
|
|
||||||
this.instruction(
|
this.instruction(
|
||||||
this._bindingMode, directive.sourceSpan, R3.elementProperty, o.literal(nodeIndex),
|
this._bindingMode, directive.sourceSpan, R3.elementProperty, o.literal(nodeIndex),
|
||||||
o.literal(input.templateName),
|
o.literal(input.templateName), o.importExpr(R3.bind).callFn([convertedBinding]));
|
||||||
o.importExpr(R3.bind).callFn([convertedBinding.currValExpr]));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// e.g. MyDirective.ngDirectiveDef.h(0, 0);
|
// e.g. MyDirective.ngDirectiveDef.h(0, 0);
|
||||||
|
@ -501,6 +532,7 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TemplateAstVisitor
|
||||||
visitEmbeddedTemplate(ast: EmbeddedTemplateAst) {
|
visitEmbeddedTemplate(ast: EmbeddedTemplateAst) {
|
||||||
const templateIndex = this.allocateDataSlot();
|
const templateIndex = this.allocateDataSlot();
|
||||||
|
|
||||||
|
@ -539,7 +571,7 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
|
||||||
const templateVisitor = new TemplateDefinitionBuilder(
|
const templateVisitor = new TemplateDefinitionBuilder(
|
||||||
this.outputCtx, this.constantPool, this.reflector, templateContext,
|
this.outputCtx, this.constantPool, this.reflector, templateContext,
|
||||||
this.bindingScope.nestedScope(), this.level + 1, this.ngContentSelectors, contextName,
|
this.bindingScope.nestedScope(), this.level + 1, this.ngContentSelectors, contextName,
|
||||||
templateName);
|
templateName, this.pipes);
|
||||||
const templateFunctionExpr = templateVisitor.buildTemplateFunction(ast.children, ast.variables);
|
const templateFunctionExpr = templateVisitor.buildTemplateFunction(ast.children, ast.variables);
|
||||||
this._postfix.push(templateFunctionExpr.toDeclStmt(templateName, null));
|
this._postfix.push(templateFunctionExpr.toDeclStmt(templateName, null));
|
||||||
}
|
}
|
||||||
|
@ -551,6 +583,7 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
|
||||||
readonly visitElementProperty = invalid;
|
readonly visitElementProperty = invalid;
|
||||||
readonly visitAttr = invalid;
|
readonly visitAttr = invalid;
|
||||||
|
|
||||||
|
// TemplateAstVisitor
|
||||||
visitBoundText(ast: BoundTextAst) {
|
visitBoundText(ast: BoundTextAst) {
|
||||||
const nodeIndex = this.allocateDataSlot();
|
const nodeIndex = this.allocateDataSlot();
|
||||||
|
|
||||||
|
@ -563,6 +596,7 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
|
||||||
this.bind(o.variable(CONTEXT_NAME), ast.value, ast.sourceSpan));
|
this.bind(o.variable(CONTEXT_NAME), ast.value, ast.sourceSpan));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TemplateAstVisitor
|
||||||
visitText(ast: TextAst) {
|
visitText(ast: TextAst) {
|
||||||
// Text is defined in creation mode only.
|
// Text is defined in creation mode only.
|
||||||
this.instruction(
|
this.instruction(
|
||||||
|
@ -600,8 +634,10 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
|
||||||
}
|
}
|
||||||
|
|
||||||
private convertPropertyBinding(implicit: o.Expression, value: AST): o.Expression {
|
private convertPropertyBinding(implicit: o.Expression, value: AST): o.Expression {
|
||||||
|
const pipesConvertedValue = value.visit(this._pipeConverter);
|
||||||
const convertedPropertyBinding = convertPropertyBinding(
|
const convertedPropertyBinding = convertPropertyBinding(
|
||||||
this, implicit, value, this.bindingContext(), BindingForm.TrySimple, interpolate);
|
this, implicit, pipesConvertedValue, this.bindingContext(), BindingForm.TrySimple,
|
||||||
|
interpolate);
|
||||||
this._refreshMode.push(...convertedPropertyBinding.stmts);
|
this._refreshMode.push(...convertedPropertyBinding.stmts);
|
||||||
return convertedPropertyBinding.currValExpr;
|
return convertedPropertyBinding.currValExpr;
|
||||||
}
|
}
|
||||||
|
@ -611,7 +647,7 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function createFactory(
|
export function createFactory(
|
||||||
type: CompileTypeMetadata, outputCtx: OutputContext,
|
type: CompileTypeMetadata, outputCtx: OutputContext,
|
||||||
reflector: CompileReflector): o.FunctionExpr {
|
reflector: CompileReflector): o.FunctionExpr {
|
||||||
let args: o.Expression[] = [];
|
let args: o.Expression[] = [];
|
||||||
|
@ -652,6 +688,35 @@ function createFactory(
|
||||||
o.INFERRED_TYPE, null, type.reference.name ? `${type.reference.name}_Factory` : null);
|
o.INFERRED_TYPE, null, type.reference.name ? `${type.reference.name}_Factory` : null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class PipeConverter extends AstMemoryEfficientTransformer {
|
||||||
|
private pipeSlots = new Map<string, number>();
|
||||||
|
constructor(
|
||||||
|
private allocateSlot: () => number,
|
||||||
|
private definePipe:
|
||||||
|
(name: string, localName: string, slot: number, value: o.Expression) => void) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
// AstMemoryEfficientTransformer
|
||||||
|
visitPipe(ast: BindingPipe, context: any): AST {
|
||||||
|
// Allocate a slot to create the pipe
|
||||||
|
let slot = this.pipeSlots.get(ast.name);
|
||||||
|
if (slot == null) {
|
||||||
|
slot = this.allocateSlot();
|
||||||
|
this.pipeSlots.set(ast.name, slot);
|
||||||
|
}
|
||||||
|
const slotPseudoLocal = `PIPE:${slot}`;
|
||||||
|
const target = new PropertyRead(ast.span, new ImplicitReceiver(ast.span), slotPseudoLocal);
|
||||||
|
const bindingId = pipeBinding(ast.args);
|
||||||
|
this.definePipe(ast.name, slotPseudoLocal, slot, o.importExpr(bindingId));
|
||||||
|
const value = ast.exp.visit(this);
|
||||||
|
const args = this.visitAll(ast.args);
|
||||||
|
|
||||||
|
return new FunctionCall(
|
||||||
|
ast.span, target, [new LiteralPrimitive(ast.span, slot), value, ...args]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function invalid<T>(arg: o.Expression | o.Statement | TemplateAst): never {
|
function invalid<T>(arg: o.Expression | o.Statement | TemplateAst): never {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Invalid state: Visitor ${this.constructor.name} doesn't handle ${o.constructor.name}`);
|
`Invalid state: Visitor ${this.constructor.name} doesn't handle ${o.constructor.name}`);
|
||||||
|
|
|
@ -6,12 +6,13 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {AotCompilerHost, AotCompilerOptions, AotSummaryResolver, CompileDirectiveMetadata, CompileMetadataResolver, CompilerConfig, DirectiveNormalizer, DirectiveResolver, DomElementSchemaRegistry, HtmlParser, I18NHtmlParser, Lexer, NgModuleResolver, Parser, PipeResolver, StaticReflector, StaticSymbol, StaticSymbolCache, StaticSymbolResolver, TemplateParser, TypeScriptEmitter, analyzeNgModules, createAotUrlResolver} from '@angular/compiler';
|
import {AotCompilerHost, AotCompilerOptions, AotSummaryResolver, CompileDirectiveMetadata, CompileMetadataResolver, CompilePipeSummary, CompilerConfig, DirectiveNormalizer, DirectiveResolver, DomElementSchemaRegistry, HtmlParser, I18NHtmlParser, Lexer, NgModuleResolver, Parser, PipeResolver, StaticReflector, StaticSymbol, StaticSymbolCache, StaticSymbolResolver, TemplateParser, TypeScriptEmitter, analyzeNgModules, createAotUrlResolver} from '@angular/compiler';
|
||||||
import {ViewEncapsulation} from '@angular/core';
|
import {ViewEncapsulation} from '@angular/core';
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
import {ConstantPool} from '../../src/constant_pool';
|
import {ConstantPool} from '../../src/constant_pool';
|
||||||
import * as o from '../../src/output/output_ast';
|
import * as o from '../../src/output/output_ast';
|
||||||
|
import {compilePipe} from '../../src/render3/r3_pipe_compiler';
|
||||||
import {compileComponent, compileDirective} from '../../src/render3/r3_view_compiler';
|
import {compileComponent, compileDirective} from '../../src/render3/r3_view_compiler';
|
||||||
import {OutputContext} from '../../src/util';
|
import {OutputContext} from '../../src/util';
|
||||||
import {MockAotCompilerHost, MockCompilerHost, MockData, MockDirectory, arrayToMockDir, expectNoDiagnostics, settings, setup, toMockFileArray} from '../aot/test_util';
|
import {MockAotCompilerHost, MockCompilerHost, MockData, MockDirectory, arrayToMockDir, expectNoDiagnostics, settings, setup, toMockFileArray} from '../aot/test_util';
|
||||||
|
@ -173,7 +174,7 @@ export function compile(
|
||||||
// to generate a template definition.
|
// to generate a template definition.
|
||||||
const analyzedModules = analyzeNgModules(sourceFiles, compilerHost, symbolResolver, resolver);
|
const analyzedModules = analyzeNgModules(sourceFiles, compilerHost, symbolResolver, resolver);
|
||||||
|
|
||||||
const directives = Array.from(analyzedModules.ngModuleByPipeOrDirective.keys());
|
const pipesOrDirectives = Array.from(analyzedModules.ngModuleByPipeOrDirective.keys());
|
||||||
|
|
||||||
const fakeOutputContext: OutputContext = {
|
const fakeOutputContext: OutputContext = {
|
||||||
genFilePath: 'fakeFactory.ts',
|
genFilePath: 'fakeFactory.ts',
|
||||||
|
@ -193,20 +194,20 @@ export function compile(
|
||||||
constantPool: new ConstantPool()
|
constantPool: new ConstantPool()
|
||||||
};
|
};
|
||||||
|
|
||||||
// Load All directives
|
// Load all directives and pipes
|
||||||
for (const directive of directives) {
|
for (const pipeOrDirective of pipesOrDirectives) {
|
||||||
const module = analyzedModules.ngModuleByPipeOrDirective.get(directive) !;
|
const module = analyzedModules.ngModuleByPipeOrDirective.get(pipeOrDirective) !;
|
||||||
resolver.loadNgModuleDirectiveAndPipeMetadata(module.type.reference, true);
|
resolver.loadNgModuleDirectiveAndPipeMetadata(module.type.reference, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compile the directives.
|
// Compile the directives.
|
||||||
for (const directive of directives) {
|
for (const pipeOrDirective of pipesOrDirectives) {
|
||||||
const module = analyzedModules.ngModuleByPipeOrDirective.get(directive);
|
const module = analyzedModules.ngModuleByPipeOrDirective.get(pipeOrDirective);
|
||||||
if (!module || !module.type.reference.filePath.startsWith('/app')) {
|
if (!module || !module.type.reference.filePath.startsWith('/app')) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (resolver.isDirective(directive)) {
|
if (resolver.isDirective(pipeOrDirective)) {
|
||||||
const metadata = resolver.getDirectiveMetadata(directive);
|
const metadata = resolver.getDirectiveMetadata(pipeOrDirective);
|
||||||
if (metadata.isComponent) {
|
if (metadata.isComponent) {
|
||||||
const fakeUrl = 'ng://fake-template-url.html';
|
const fakeUrl = 'ng://fake-template-url.html';
|
||||||
const htmlAst = htmlParser.parse(metadata.template !.template !, fakeUrl);
|
const htmlAst = htmlParser.parse(metadata.template !.template !, fakeUrl);
|
||||||
|
@ -217,11 +218,16 @@ export function compile(
|
||||||
module.transitiveModule.pipes.map(pipe => resolver.getPipeSummary(pipe.reference));
|
module.transitiveModule.pipes.map(pipe => resolver.getPipeSummary(pipe.reference));
|
||||||
const parsedTemplate = templateParser.parse(
|
const parsedTemplate = templateParser.parse(
|
||||||
metadata, htmlAst, directives, pipes, module.schemas, fakeUrl, false);
|
metadata, htmlAst, directives, pipes, module.schemas, fakeUrl, false);
|
||||||
|
compileComponent(
|
||||||
compileComponent(fakeOutputContext, metadata, parsedTemplate.template, staticReflector);
|
fakeOutputContext, metadata, pipes, parsedTemplate.template, staticReflector);
|
||||||
} else {
|
} else {
|
||||||
compileDirective(fakeOutputContext, metadata, staticReflector);
|
compileDirective(fakeOutputContext, metadata, staticReflector);
|
||||||
}
|
}
|
||||||
|
} else if (resolver.isPipe(pipeOrDirective)) {
|
||||||
|
const metadata = resolver.getPipeMetadata(pipeOrDirective);
|
||||||
|
if (metadata) {
|
||||||
|
compilePipe(fakeOutputContext, metadata, staticReflector);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,716 @@
|
||||||
|
/**
|
||||||
|
* @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
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {MockDirectory, setup} from '../aot/test_util';
|
||||||
|
import {compile, expectEmit} from './mock_compile';
|
||||||
|
|
||||||
|
/* These tests are codified version of the tests in compiler_canonical_spec.ts. Every
|
||||||
|
* test in compiler_canonical_spec.ts should have a corresponding test here.
|
||||||
|
*/
|
||||||
|
describe('compiler compliance', () => {
|
||||||
|
const angularFiles = setup({
|
||||||
|
compileAngular: true,
|
||||||
|
compileAnimations: false,
|
||||||
|
compileCommon: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('elements', () => {
|
||||||
|
it('should translate DOM structure', () => {
|
||||||
|
const files = {
|
||||||
|
app: {
|
||||||
|
'spec.ts': `
|
||||||
|
import {Component, NgModule} from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'my-component',
|
||||||
|
template: \`<div class="my-app" title="Hello">Hello <b>World</b>!</div>\`
|
||||||
|
})
|
||||||
|
export class MyComponent {}
|
||||||
|
|
||||||
|
@NgModule({declarations: [MyComponent]})
|
||||||
|
export class MyModule {}
|
||||||
|
`
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// The factory should look like this:
|
||||||
|
const factory = 'factory: function MyComponent_Factory() { return new MyComponent(); }';
|
||||||
|
|
||||||
|
// The template should look like this (where IDENT is a wild card for an identifier):
|
||||||
|
const template = `
|
||||||
|
const $c1$ = ['class', 'my-app', 'title', 'Hello'];
|
||||||
|
…
|
||||||
|
template: function MyComponent_Template(ctx: IDENT, cm: IDENT) {
|
||||||
|
if (cm) {
|
||||||
|
$r3$.ɵE(0, 'div', $c1$);
|
||||||
|
$r3$.ɵT(1, 'Hello ');
|
||||||
|
$r3$.ɵE(2, 'b');
|
||||||
|
$r3$.ɵT(3, 'World');
|
||||||
|
$r3$.ɵe();
|
||||||
|
$r3$.ɵT(4, '!');
|
||||||
|
$r3$.ɵe();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
|
||||||
|
const result = compile(files, angularFiles);
|
||||||
|
|
||||||
|
expectEmit(result.source, factory, 'Incorrect factory');
|
||||||
|
expectEmit(result.source, template, 'Incorrect template');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('components & directives', () => {
|
||||||
|
it('should instantiate directives', () => {
|
||||||
|
const files = {
|
||||||
|
app: {
|
||||||
|
'spec.ts': `
|
||||||
|
import {Component, Directive, NgModule} from '@angular/core';
|
||||||
|
|
||||||
|
@Component({selector: 'child', template: 'child-view'})
|
||||||
|
export class ChildComponent {}
|
||||||
|
|
||||||
|
@Directive({selector: '[some-directive]'})
|
||||||
|
export class SomeDirective {}
|
||||||
|
|
||||||
|
@Component({selector: 'my-component', template: '<child some-directive></child>!'})
|
||||||
|
export class MyComponent {}
|
||||||
|
|
||||||
|
@NgModule({declarations: [ChildComponent, SomeDirective, MyComponent]})
|
||||||
|
export class MyModule{}
|
||||||
|
`
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ChildComponent definition should be:
|
||||||
|
const ChildComponentDefinition = `
|
||||||
|
static ngComponentDef = $r3$.ɵdefineComponent({
|
||||||
|
type: ChildComponent,
|
||||||
|
tag: 'child',
|
||||||
|
factory: function ChildComponent_Factory() { return new ChildComponent(); },
|
||||||
|
template: function ChildComponent_Template(ctx: IDENT, cm: IDENT) {
|
||||||
|
if (cm) {
|
||||||
|
$r3$.ɵT(0, 'child-view');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});`;
|
||||||
|
|
||||||
|
// SomeDirective definition should be:
|
||||||
|
const SomeDirectiveDefinition = `
|
||||||
|
static ngDirectiveDef = $r3$.ɵdefineDirective({
|
||||||
|
type: SomeDirective,
|
||||||
|
factory: function SomeDirective_Factory() {return new SomeDirective(); }
|
||||||
|
});
|
||||||
|
`;
|
||||||
|
|
||||||
|
// MyComponent definition should be:
|
||||||
|
const MyComponentDefinition = `
|
||||||
|
const $c1$ = ['some-directive', ''];
|
||||||
|
const $c2$ = [SomeDirective];
|
||||||
|
…
|
||||||
|
static ngComponentDef = $r3$.ɵdefineComponent({
|
||||||
|
type: MyComponent,
|
||||||
|
tag: 'my-component',
|
||||||
|
factory: function MyComponent_Factory() { return new MyComponent(); },
|
||||||
|
template: function MyComponent_Template(ctx: IDENT, cm: IDENT) {
|
||||||
|
if (cm) {
|
||||||
|
$r3$.ɵE(0, ChildComponent, IDENT, IDENT);
|
||||||
|
$r3$.ɵe();
|
||||||
|
$r3$.ɵT(3, '!');
|
||||||
|
}
|
||||||
|
ChildComponent.ngComponentDef.h(1, 0);
|
||||||
|
SomeDirective.ngDirectiveDef.h(2, 0);
|
||||||
|
$r3$.ɵr(1, 0);
|
||||||
|
$r3$.ɵr(2, 0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
`;
|
||||||
|
|
||||||
|
|
||||||
|
const result = compile(files, angularFiles);
|
||||||
|
const source = result.source;
|
||||||
|
|
||||||
|
expectEmit(source, ChildComponentDefinition, 'Incorrect ChildComponent.ngComponentDef');
|
||||||
|
expectEmit(source, SomeDirectiveDefinition, 'Incorrect SomeDirective.ngDirectiveDef');
|
||||||
|
expectEmit(source, MyComponentDefinition, 'Incorrect MyComponentDefinition.ngComponentDef');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support structural directives', () => {
|
||||||
|
const files = {
|
||||||
|
app: {
|
||||||
|
'spec.ts': `
|
||||||
|
import {Component, Directive, NgModule, TemplateRef} from '@angular/core';
|
||||||
|
|
||||||
|
@Directive({selector: '[if]'})
|
||||||
|
export class IfDirective {
|
||||||
|
constructor(template: TemplateRef<any>) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'my-component',
|
||||||
|
template: '<ul #foo><li *if>{{salutation}} {{foo}}</li></ul>'
|
||||||
|
})
|
||||||
|
export class MyComponent {
|
||||||
|
salutation = 'Hello';
|
||||||
|
}
|
||||||
|
|
||||||
|
@NgModule({declarations: [IfDirective, MyComponent]})
|
||||||
|
export class MyModule {}
|
||||||
|
`
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const IfDirectiveDefinition = `
|
||||||
|
static ngDirectiveDef = $r3$.ɵdefineDirective({
|
||||||
|
type: IfDirective,
|
||||||
|
factory: function IfDirective_Factory() { return new IfDirective($r3$.ɵinjectTemplateRef()); }
|
||||||
|
});`;
|
||||||
|
const MyComponentDefinition = `
|
||||||
|
const $c1$ = ['foo', ''];
|
||||||
|
const $c2$ = [IfDirective];
|
||||||
|
…
|
||||||
|
static ngComponentDef = $r3$.ɵdefineComponent({
|
||||||
|
type: MyComponent,
|
||||||
|
tag: 'my-component',
|
||||||
|
factory: function MyComponent_Factory() { return new MyComponent(); },
|
||||||
|
template: function MyComponent_Template(ctx: IDENT, cm: IDENT) {
|
||||||
|
if (cm) {
|
||||||
|
$r3$.ɵE(0, 'ul', null, null, $c1$);
|
||||||
|
$r3$.ɵC(2, $c2$, MyComponent_IfDirective_Template_2);
|
||||||
|
$r3$.ɵe();
|
||||||
|
}
|
||||||
|
const $foo$ = $r3$.ɵld(1);
|
||||||
|
IfDirective.ngDirectiveDef.h(3,2);
|
||||||
|
$r3$.ɵcR(2);
|
||||||
|
$r3$.ɵr(3,2);
|
||||||
|
$r3$.ɵcr();
|
||||||
|
|
||||||
|
function MyComponent_IfDirective_Template_2(ctx0: IDENT, cm: IDENT) {
|
||||||
|
if (cm) {
|
||||||
|
$r3$.ɵE(0, 'li');
|
||||||
|
$r3$.ɵT(1);
|
||||||
|
$r3$.ɵe();
|
||||||
|
}
|
||||||
|
$r3$.ɵt(1, $r3$.ɵi2('', ctx.salutation, ' ', $foo$, ''));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});`;
|
||||||
|
|
||||||
|
const result = compile(files, angularFiles);
|
||||||
|
const source = result.source;
|
||||||
|
|
||||||
|
expectEmit(source, IfDirectiveDefinition, 'Incorrect IfDirective.ngDirectiveDef');
|
||||||
|
expectEmit(source, MyComponentDefinition, 'Incorrect MyComponent.ngComponentDef');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support content projection', () => {
|
||||||
|
const files = {
|
||||||
|
app: {
|
||||||
|
'spec.ts': `
|
||||||
|
import {Component, Directive, NgModule, TemplateRef} from '@angular/core';
|
||||||
|
|
||||||
|
@Component({selector: 'simple', template: '<div><ng-content></ng-content></div>'})
|
||||||
|
export class SimpleComponent {}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'complex',
|
||||||
|
template: \`
|
||||||
|
<div id="first"><ng-content select="span[title=toFirst]"></ng-content></div>
|
||||||
|
<div id="second"><ng-content select="span[title=toSecond]"></ng-content></div>\`
|
||||||
|
})
|
||||||
|
export class ComplexComponent { }
|
||||||
|
|
||||||
|
@NgModule({declarations: [SimpleComponent, ComplexComponent]})
|
||||||
|
export class MyModule {}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'my-app',
|
||||||
|
template: '<simple>content</simple> <complex></complex>'
|
||||||
|
})
|
||||||
|
export class MyApp {}
|
||||||
|
`
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const SimpleComponentDefinition = `
|
||||||
|
static ngComponentDef = $r3$.ɵdefineComponent({
|
||||||
|
type: SimpleComponent,
|
||||||
|
tag: 'simple',
|
||||||
|
factory: function SimpleComponent_Factory() { return new SimpleComponent(); },
|
||||||
|
template: function SimpleComponent_Template(ctx: IDENT, cm: IDENT) {
|
||||||
|
if (cm) {
|
||||||
|
$r3$.ɵpD(0);
|
||||||
|
$r3$.ɵE(1, 'div');
|
||||||
|
$r3$.ɵP(2, 0);
|
||||||
|
$r3$.ɵe();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});`;
|
||||||
|
|
||||||
|
const ComplexComponentDefinition = `
|
||||||
|
const $c1$ = [[[['span', 'title', 'tofirst'], null]], [[['span', 'title', 'tosecond'], null]]];
|
||||||
|
const $c2$ = ['id','first'];
|
||||||
|
const $c3$ = ['id','second'];
|
||||||
|
…
|
||||||
|
static ngComponentDef = $r3$.ɵdefineComponent({
|
||||||
|
type: ComplexComponent,
|
||||||
|
tag: 'complex',
|
||||||
|
factory: function ComplexComponent_Factory() { return new ComplexComponent(); },
|
||||||
|
template: function ComplexComponent_Template(ctx: IDENT, cm: IDENT) {
|
||||||
|
if (cm) {
|
||||||
|
$r3$.ɵpD(0, $c1$);
|
||||||
|
$r3$.ɵE(1, 'div', $c2$);
|
||||||
|
$r3$.ɵP(2, 0, 1);
|
||||||
|
$r3$.ɵe();
|
||||||
|
$r3$.ɵE(3, 'div', $c3$);
|
||||||
|
$r3$.ɵP(4, 0, 2);
|
||||||
|
$r3$.ɵe();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
`;
|
||||||
|
|
||||||
|
const result = compile(files, angularFiles);
|
||||||
|
const source = result.source;
|
||||||
|
|
||||||
|
expectEmit(result.source, SimpleComponentDefinition, 'Incorrect SimpleComponent definition');
|
||||||
|
expectEmit(
|
||||||
|
result.source, ComplexComponentDefinition, 'Incorrect ComplexComponent definition');
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('pipes', () => {
|
||||||
|
|
||||||
|
const files = {
|
||||||
|
app: {
|
||||||
|
'spec.ts': `
|
||||||
|
import {Component, NgModule, Pipe, PipeTransform, OnDestroy} from '@angular/core';
|
||||||
|
|
||||||
|
@Pipe({
|
||||||
|
name: 'myPipe',
|
||||||
|
pure: false
|
||||||
|
})
|
||||||
|
export class MyPipe implements PipeTransform,
|
||||||
|
OnDestroy {
|
||||||
|
transform(value: any, ...args: any[]) { return value; }
|
||||||
|
ngOnDestroy(): void { }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Pipe({
|
||||||
|
name: 'myPurePipe',
|
||||||
|
pure: true,
|
||||||
|
})
|
||||||
|
export class MyPurePipe implements PipeTransform {
|
||||||
|
transform(value: any, ...args: any[]) { return value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({selector: 'my-app', template: '{{name | myPipe:size | myPurePipe:size }}'})
|
||||||
|
export class MyApp {
|
||||||
|
name = 'World';
|
||||||
|
size = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NgModule({declarations:[MyPipe, MyPurePipe, MyApp]})
|
||||||
|
export class MyModule {}
|
||||||
|
`
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
it('should render pipes', () => {
|
||||||
|
const MyPipeDefinition = `
|
||||||
|
static ngPipeDef = $r3$.ɵdefinePipe(
|
||||||
|
{type: MyPipe, factory: function MyPipe_Factory() { return new MyPipe(); }});
|
||||||
|
`;
|
||||||
|
|
||||||
|
const MyPurePipeDefinition = `
|
||||||
|
static ngPipeDef = $r3$.ɵdefinePipe({
|
||||||
|
type: MyPurePipe,
|
||||||
|
factory: function MyPurePipe_Factory() { return new MyPurePipe(); },
|
||||||
|
pure: true
|
||||||
|
});`;
|
||||||
|
|
||||||
|
const MyAppDefinition = `
|
||||||
|
const $MyPurePipe_ngPipeDef$ = MyPurePipe.ngPipeDef;
|
||||||
|
const $MyPipe_ngPipeDef$ = MyPipe.ngPipeDef;
|
||||||
|
…
|
||||||
|
static ngComponentDef = $r3$.ɵdefineComponent({
|
||||||
|
type: MyApp,
|
||||||
|
tag: 'my-app',
|
||||||
|
factory: function MyApp_Factory() { return new MyApp(); },
|
||||||
|
template: function MyApp_Template(ctx: IDENT, cm: IDENT) {
|
||||||
|
if (cm) {
|
||||||
|
$r3$.ɵT(0);
|
||||||
|
$r3$.ɵPp(1, $MyPurePipe_ngPipeDef$, $MyPurePipe_ngPipeDef$.n());
|
||||||
|
$r3$.ɵPp(2, $MyPipe_ngPipeDef$, $MyPipe_ngPipeDef$.n());
|
||||||
|
}
|
||||||
|
$r3$.ɵt(0, $r3$.ɵi1('', $r3$.ɵpb2(1, $r3$.ɵpb2(2,ctx.name, ctx.size), ctx.size), ''));
|
||||||
|
}
|
||||||
|
});`;
|
||||||
|
|
||||||
|
const result = compile(files, angularFiles);
|
||||||
|
const source = result.source;
|
||||||
|
|
||||||
|
expectEmit(source, MyPipeDefinition, 'Invalid pipe definition');
|
||||||
|
expectEmit(source, MyPurePipeDefinition, 'Invalid pure pipe definition');
|
||||||
|
expectEmit(source, MyAppDefinition, 'Invalid MyApp definition');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('local reference', () => {
|
||||||
|
const files = {
|
||||||
|
app: {
|
||||||
|
'spec.ts': `
|
||||||
|
import {Component, NgModule} from '@angular/core';
|
||||||
|
|
||||||
|
@Component({selector: 'my-component', template: '<input #user>Hello {{user.value}}!'})
|
||||||
|
export class MyComponent {}
|
||||||
|
|
||||||
|
@NgModule({declarations: [MyComponent]})
|
||||||
|
export class MyModule {}
|
||||||
|
`
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const MyComponentDefinition = `
|
||||||
|
const $c1$ = ['user', ''];
|
||||||
|
…
|
||||||
|
static ngComponentDef = $r3$.ɵdefineComponent({
|
||||||
|
type: MyComponent,
|
||||||
|
tag: 'my-component',
|
||||||
|
factory: function MyComponent_Factory() { return new MyComponent(); },
|
||||||
|
template: function MyComponent_Template(ctx: IDENT, cm: IDENT) {
|
||||||
|
if (cm) {
|
||||||
|
$r3$.ɵE(0, 'input', null, null, $c1$);
|
||||||
|
$r3$.ɵe();
|
||||||
|
$r3$.ɵT(2);
|
||||||
|
}
|
||||||
|
const $user$ = $r3$.ɵld(1);
|
||||||
|
$r3$.ɵt(2, $r3$.ɵi1('Hello ', $user$.value, '!'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
`;
|
||||||
|
|
||||||
|
const result = compile(files, angularFiles);
|
||||||
|
const source = result.source;
|
||||||
|
|
||||||
|
expectEmit(source, MyComponentDefinition, 'Incorrect MyComponent.ngComponentDef');
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('lifecycle hooks', () => {
|
||||||
|
const files = {
|
||||||
|
app: {
|
||||||
|
'spec.ts': `
|
||||||
|
import {Component, Input, NgModule} from '@angular/core';
|
||||||
|
|
||||||
|
let events: string[] = [];
|
||||||
|
|
||||||
|
@Component({selector: 'lifecycle-comp', template: ''})
|
||||||
|
export class LifecycleComp {
|
||||||
|
@Input('name') nameMin: string;
|
||||||
|
|
||||||
|
ngOnChanges() { events.push('changes' + this.nameMin); }
|
||||||
|
|
||||||
|
ngOnInit() { events.push('init' + this.nameMin); }
|
||||||
|
ngDoCheck() { events.push('check' + this.nameMin); }
|
||||||
|
|
||||||
|
ngAfterContentInit() { events.push('content init' + this.nameMin); }
|
||||||
|
ngAfterContentChecked() { events.push('content check' + this.nameMin); }
|
||||||
|
|
||||||
|
ngAfterViewInit() { events.push('view init' + this.nameMin); }
|
||||||
|
ngAfterViewChecked() { events.push('view check' + this.nameMin); }
|
||||||
|
|
||||||
|
ngOnDestroy() { events.push(this.nameMin); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'simple-layout',
|
||||||
|
template: \`
|
||||||
|
<lifecycle-comp [name]="name1"></lifecycle-comp>
|
||||||
|
<lifecycle-comp [name]="name2"></lifecycle-comp>
|
||||||
|
\`
|
||||||
|
})
|
||||||
|
export class SimpleLayout {
|
||||||
|
name1 = '1';
|
||||||
|
name2 = '2';
|
||||||
|
}
|
||||||
|
|
||||||
|
@NgModule({declarations: [LifecycleComp, SimpleLayout]})
|
||||||
|
export class LifecycleModule {}
|
||||||
|
`
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
it('should gen hooks with a few simple components', () => {
|
||||||
|
const LifecycleCompDefinition = `
|
||||||
|
static ngComponentDef = $r3$.ɵdefineComponent({
|
||||||
|
type: LifecycleComp,
|
||||||
|
tag: 'lifecycle-comp',
|
||||||
|
factory: function LifecycleComp_Factory() { return new LifecycleComp(); },
|
||||||
|
template: function LifecycleComp_Template(ctx: IDENT, cm: IDENT) {},
|
||||||
|
inputs: {nameMin: 'name'},
|
||||||
|
features: [$r3$.ɵNgOnChangesFeature(LifecycleComp)]
|
||||||
|
});`;
|
||||||
|
|
||||||
|
const SimpleLayoutDefinition = `
|
||||||
|
const $c1$ = LifecycleComp.ngComponentDef;
|
||||||
|
…
|
||||||
|
static ngComponentDef = $r3$.ɵdefineComponent({
|
||||||
|
type: SimpleLayout,
|
||||||
|
tag: 'simple-layout',
|
||||||
|
factory: function SimpleLayout_Factory() { return new SimpleLayout(); },
|
||||||
|
template: function SimpleLayout_Template(ctx: IDENT, cm: IDENT) {
|
||||||
|
if (cm) {
|
||||||
|
$r3$.ɵE(0, LifecycleComp);
|
||||||
|
$r3$.ɵe();
|
||||||
|
$r3$.ɵE(2, LifecycleComp);
|
||||||
|
$r3$.ɵe();
|
||||||
|
}
|
||||||
|
$r3$.ɵp(0, 'name', $r3$.ɵb(ctx.name1));
|
||||||
|
$r3$.ɵp(2, 'name', $r3$.ɵb(ctx.name2));
|
||||||
|
$c1$.h(1, 0);
|
||||||
|
$c1$.h(3, 2);
|
||||||
|
$r3$.ɵr(1, 0);
|
||||||
|
$r3$.ɵr(3, 2);
|
||||||
|
}
|
||||||
|
});`;
|
||||||
|
|
||||||
|
const result = compile(files, angularFiles);
|
||||||
|
const source = result.source;
|
||||||
|
|
||||||
|
expectEmit(source, LifecycleCompDefinition, 'Invalid LifecycleComp definition');
|
||||||
|
expectEmit(source, SimpleLayoutDefinition, 'Invalid SimpleLayout definition');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('template variables', () => {
|
||||||
|
const shared = {
|
||||||
|
shared: {
|
||||||
|
'for_of.ts': `
|
||||||
|
import {Directive, Input, SimpleChanges, TemplateRef, ViewContainerRef} from '@angular/core';
|
||||||
|
|
||||||
|
export interface ForOfContext {
|
||||||
|
$implicit: any;
|
||||||
|
index: number;
|
||||||
|
even: boolean;
|
||||||
|
odd: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Directive({selector: '[forOf]'})
|
||||||
|
export class ForOfDirective {
|
||||||
|
private previous: any[];
|
||||||
|
|
||||||
|
constructor(private view: ViewContainerRef, private template: TemplateRef<any>) {}
|
||||||
|
|
||||||
|
@Input() forOf: any[];
|
||||||
|
|
||||||
|
ngOnChanges(simpleChanges: SimpleChanges) {
|
||||||
|
if ('forOf' in simpleChanges) {
|
||||||
|
this.update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ngDoCheck(): void {
|
||||||
|
const previous = this.previous;
|
||||||
|
const current = this.forOf;
|
||||||
|
if (!previous || previous.length != current.length ||
|
||||||
|
previous.some((value: any, index: number) => current[index] !== previous[index])) {
|
||||||
|
this.update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private update() {
|
||||||
|
// TODO(chuckj): Not implemented yet
|
||||||
|
// this.view.clear();
|
||||||
|
if (this.forOf) {
|
||||||
|
const current = this.forOf;
|
||||||
|
for (let i = 0; i < current.length; i++) {
|
||||||
|
const context = {$implicit: current[i], index: i, even: i % 2 == 0, odd: i % 2 == 1};
|
||||||
|
// TODO(chuckj): Not implemented yet
|
||||||
|
// this.view.createEmbeddedView(this.template, context);
|
||||||
|
}
|
||||||
|
this.previous = [...this.forOf];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
it('should support a let variable and reference', () => {
|
||||||
|
const files = {
|
||||||
|
app: {
|
||||||
|
...shared,
|
||||||
|
'spec.ts': `
|
||||||
|
import {Component, NgModule} from '@angular/core';
|
||||||
|
import {ForOfDirective} from './shared/for_of';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'my-component',
|
||||||
|
template: \`<ul><li *for="let item of items">{{item.name}}</li></ul>\`
|
||||||
|
})
|
||||||
|
export class MyComponent {
|
||||||
|
items = [{name: 'one'}, {name: 'two'}];
|
||||||
|
}
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [MyComponent, ForOfDirective]
|
||||||
|
})
|
||||||
|
export class MyModule {}
|
||||||
|
`
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO(chuckj): Enforce this when the directives are specified
|
||||||
|
const ForDirectiveDefinition = `
|
||||||
|
static ngDirectiveDef = $r3$.ɵdefineDirective({
|
||||||
|
type: ForOfDirective,
|
||||||
|
factory: function ForOfDirective_Factory() {
|
||||||
|
return new ForOfDirective($r3$.ɵinjectViewContainerRef(), $r3$.ɵinjectTemplateRef());
|
||||||
|
},
|
||||||
|
features: [$r3$.ɵNgOnChangesFeature(NgForOf)],
|
||||||
|
inputs: {forOf: 'forOf'}
|
||||||
|
});
|
||||||
|
`;
|
||||||
|
|
||||||
|
const MyComponentDefinition = `
|
||||||
|
const $c1$ = [ForOfDirective];
|
||||||
|
…
|
||||||
|
static ngComponentDef = $r3$.ɵdefineComponent({
|
||||||
|
type: MyComponent,
|
||||||
|
tag: 'my-component',
|
||||||
|
factory: function MyComponent_Factory() { return new MyComponent(); },
|
||||||
|
template: function MyComponent_Template(ctx: IDENT, cm: IDENT) {
|
||||||
|
if (cm) {
|
||||||
|
$r3$.ɵE(0, 'ul');
|
||||||
|
$r3$.ɵC(1, $c1$, MyComponent_ForOfDirective_Template_1);
|
||||||
|
$r3$.ɵe();
|
||||||
|
}
|
||||||
|
$r3$.ɵp(1, 'forOf', $r3$.ɵb(ctx.items));
|
||||||
|
ForOfDirective.ngDirectiveDef.h(2, 1);
|
||||||
|
$r3$.ɵcR(1);
|
||||||
|
$r3$.ɵr(2, 1);
|
||||||
|
$r3$.ɵcr();
|
||||||
|
|
||||||
|
function MyComponent_ForOfDirective_Template_1(ctx0: IDENT, cm: IDENT) {
|
||||||
|
if (cm) {
|
||||||
|
$r3$.ɵE(0, 'li');
|
||||||
|
$r3$.ɵT(1);
|
||||||
|
$r3$.ɵe();
|
||||||
|
}
|
||||||
|
const $item$ = ctx0.$implicit;
|
||||||
|
$r3$.ɵt(1, $r3$.ɵi1('', $item$.name, ''));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
`;
|
||||||
|
|
||||||
|
const result = compile(files, angularFiles);
|
||||||
|
const source = result.source;
|
||||||
|
|
||||||
|
// TODO(chuckj): Enforce this when the directives are specified
|
||||||
|
// expectEmit(source, ForDirectiveDefinition, 'Invalid directive definition');
|
||||||
|
expectEmit(source, MyComponentDefinition, 'Invalid component definition');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support accessing parent template variables', () => {
|
||||||
|
const files = {
|
||||||
|
app: {
|
||||||
|
...shared,
|
||||||
|
'spec.ts': `
|
||||||
|
import {Component, NgModule} from '@angular/core';
|
||||||
|
import {ForOfDirective} from './shared/for_of';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'my-component',
|
||||||
|
template: \`
|
||||||
|
<ul>
|
||||||
|
<li *for="let item of items">
|
||||||
|
<div>{{item.name}}</div>
|
||||||
|
<ul>
|
||||||
|
<li *for="let info of item.infos">
|
||||||
|
{{item.name}}: {{info.description}}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>\`
|
||||||
|
})
|
||||||
|
export class MyComponent {
|
||||||
|
items = [
|
||||||
|
{name: 'one', infos: [{description: '11'}, {description: '12'}]},
|
||||||
|
{name: 'two', infos: [{description: '21'}, {description: '22'}]}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [MyComponent, ForOfDirective]
|
||||||
|
})
|
||||||
|
export class MyModule {}
|
||||||
|
`
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const MyComponentDefinition = `
|
||||||
|
const $c1$ = [ForOfDirective];
|
||||||
|
const $c2$ = ForOfDirective.ngDirectiveDef;
|
||||||
|
…
|
||||||
|
static ngComponentDef = $r3$.ɵdefineComponent({
|
||||||
|
type: MyComponent,
|
||||||
|
tag: 'my-component',
|
||||||
|
factory: function MyComponent_Factory() { return new MyComponent(); },
|
||||||
|
template: function MyComponent_Template(ctx: IDENT, cm: IDENT) {
|
||||||
|
if (cm) {
|
||||||
|
$r3$.ɵE(0, 'ul');
|
||||||
|
$r3$.ɵC(1, $c1$, MyComponent_ForOfDirective_Template_1);
|
||||||
|
$r3$.ɵe();
|
||||||
|
}
|
||||||
|
$r3$.ɵp(1, 'forOf', $r3$.ɵb(ctx.items));
|
||||||
|
$c2$.h(2,1);
|
||||||
|
$r3$.ɵcR(1);
|
||||||
|
$r3$.ɵr(2, 1);
|
||||||
|
$r3$.ɵcr();
|
||||||
|
|
||||||
|
function MyComponent_ForOfDirective_Template_1(ctx0: IDENT, cm: IDENT) {
|
||||||
|
if (cm) {
|
||||||
|
$r3$.ɵE(0, 'li');
|
||||||
|
$r3$.ɵE(1, 'div');
|
||||||
|
$r3$.ɵT(2);
|
||||||
|
$r3$.ɵe();
|
||||||
|
$r3$.ɵE(3, 'ul');
|
||||||
|
$r3$.ɵC(4, $c1$, MyComponent_ForOfDirective_ForOfDirective_Template_4);
|
||||||
|
$r3$.ɵe();
|
||||||
|
$r3$.ɵe();
|
||||||
|
}
|
||||||
|
const $item$ = ctx0.$implicit;
|
||||||
|
$r3$.ɵp(4, 'forOf', $r3$.ɵb(IDENT.infos));
|
||||||
|
$c2$.h(5,4);
|
||||||
|
$r3$.ɵt(2, $r3$.ɵi1('', IDENT.name, ''));
|
||||||
|
$r3$.ɵcR(4);
|
||||||
|
$r3$.ɵr(5, 4);
|
||||||
|
$r3$.ɵcr();
|
||||||
|
|
||||||
|
function MyComponent_ForOfDirective_ForOfDirective_Template_4(
|
||||||
|
ctx1: IDENT, cm: IDENT) {
|
||||||
|
if (cm) {
|
||||||
|
$r3$.ɵE(0, 'li');
|
||||||
|
$r3$.ɵT(1);
|
||||||
|
$r3$.ɵe();
|
||||||
|
}
|
||||||
|
const $info$ = ctx1.$implicit;
|
||||||
|
$r3$.ɵt(1, $r3$.ɵi2(' ', $item$.name, ': ', $info$.description, ' '));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});`;
|
||||||
|
|
||||||
|
const result = compile(files, angularFiles);
|
||||||
|
const source = result.source;
|
||||||
|
expectEmit(source, MyComponentDefinition, 'Invalid component definition');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -122,626 +122,4 @@ describe('r3_view_compiler', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
/* These tests are codified version of the tests in compiler_canonical_spec.ts. Every
|
|
||||||
* test in compiler_canonical_spec.ts should have a corresponding test here.
|
|
||||||
*/
|
|
||||||
describe('compiler conformance', () => {
|
|
||||||
describe('elements', () => {
|
|
||||||
it('should translate DOM structure', () => {
|
|
||||||
const files = {
|
|
||||||
app: {
|
|
||||||
'spec.ts': `
|
|
||||||
import {Component, NgModule} from '@angular/core';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'my-component',
|
|
||||||
template: \`<div class="my-app" title="Hello">Hello <b>World</b>!</div>\`
|
|
||||||
})
|
|
||||||
export class MyComponent {}
|
|
||||||
|
|
||||||
@NgModule({declarations: [MyComponent]})
|
|
||||||
export class MyModule {}
|
|
||||||
`
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// The factory should look like this:
|
|
||||||
const factory = 'factory: function MyComponent_Factory() { return new MyComponent(); }';
|
|
||||||
|
|
||||||
// The template should look like this (where IDENT is a wild card for an identifier):
|
|
||||||
const template = `
|
|
||||||
const $c1$ = ['class', 'my-app', 'title', 'Hello'];
|
|
||||||
…
|
|
||||||
template: function MyComponent_Template(ctx: IDENT, cm: IDENT) {
|
|
||||||
if (cm) {
|
|
||||||
$r3$.ɵE(0, 'div', $c1$);
|
|
||||||
$r3$.ɵT(1, 'Hello ');
|
|
||||||
$r3$.ɵE(2, 'b');
|
|
||||||
$r3$.ɵT(3, 'World');
|
|
||||||
$r3$.ɵe();
|
|
||||||
$r3$.ɵT(4, '!');
|
|
||||||
$r3$.ɵe();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
|
|
||||||
const result = compile(files, angularFiles);
|
|
||||||
|
|
||||||
expectEmit(result.source, factory, 'Incorrect factory');
|
|
||||||
expectEmit(result.source, template, 'Incorrect template');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('components & directives', () => {
|
|
||||||
it('should instantiate directives', () => {
|
|
||||||
const files = {
|
|
||||||
app: {
|
|
||||||
'spec.ts': `
|
|
||||||
import {Component, Directive, NgModule} from '@angular/core';
|
|
||||||
|
|
||||||
@Component({selector: 'child', template: 'child-view'})
|
|
||||||
export class ChildComponent {}
|
|
||||||
|
|
||||||
@Directive({selector: '[some-directive]'})
|
|
||||||
export class SomeDirective {}
|
|
||||||
|
|
||||||
@Component({selector: 'my-component', template: '<child some-directive></child>!'})
|
|
||||||
export class MyComponent {}
|
|
||||||
|
|
||||||
@NgModule({declarations: [ChildComponent, SomeDirective, MyComponent]})
|
|
||||||
export class MyModule{}
|
|
||||||
`
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// ChildComponent definition should be:
|
|
||||||
const ChildComponentDefinition = `
|
|
||||||
static ngComponentDef = $r3$.ɵdefineComponent({
|
|
||||||
type: ChildComponent,
|
|
||||||
tag: 'child',
|
|
||||||
factory: function ChildComponent_Factory() { return new ChildComponent(); },
|
|
||||||
template: function ChildComponent_Template(ctx: IDENT, cm: IDENT) {
|
|
||||||
if (cm) {
|
|
||||||
$r3$.ɵT(0, 'child-view');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});`;
|
|
||||||
|
|
||||||
// SomeDirective definition should be:
|
|
||||||
const SomeDirectiveDefinition = `
|
|
||||||
static ngDirectiveDef = $r3$.ɵdefineDirective({
|
|
||||||
type: SomeDirective,
|
|
||||||
factory: function SomeDirective_Factory() {return new SomeDirective(); }
|
|
||||||
});
|
|
||||||
`;
|
|
||||||
|
|
||||||
// MyComponent definition should be:
|
|
||||||
const MyComponentDefinition = `
|
|
||||||
const $c1$ = ['some-directive', ''];
|
|
||||||
const $c2$ = [SomeDirective];
|
|
||||||
…
|
|
||||||
static ngComponentDef = $r3$.ɵdefineComponent({
|
|
||||||
type: MyComponent,
|
|
||||||
tag: 'my-component',
|
|
||||||
factory: function MyComponent_Factory() { return new MyComponent(); },
|
|
||||||
template: function MyComponent_Template(ctx: IDENT, cm: IDENT) {
|
|
||||||
if (cm) {
|
|
||||||
$r3$.ɵE(0, ChildComponent, IDENT, IDENT);
|
|
||||||
$r3$.ɵe();
|
|
||||||
$r3$.ɵT(3, '!');
|
|
||||||
}
|
|
||||||
ChildComponent.ngComponentDef.h(1, 0);
|
|
||||||
SomeDirective.ngDirectiveDef.h(2, 0);
|
|
||||||
$r3$.ɵr(1, 0);
|
|
||||||
$r3$.ɵr(2, 0);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
`;
|
|
||||||
|
|
||||||
|
|
||||||
const result = compile(files, angularFiles);
|
|
||||||
const source = result.source;
|
|
||||||
|
|
||||||
expectEmit(source, ChildComponentDefinition, 'Incorrect ChildComponent.ngComponentDef');
|
|
||||||
expectEmit(source, SomeDirectiveDefinition, 'Incorrect SomeDirective.ngDirectiveDef');
|
|
||||||
expectEmit(source, MyComponentDefinition, 'Incorrect MyComponentDefinition.ngComponentDef');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should support structural directives', () => {
|
|
||||||
const files = {
|
|
||||||
app: {
|
|
||||||
'spec.ts': `
|
|
||||||
import {Component, Directive, NgModule, TemplateRef} from '@angular/core';
|
|
||||||
|
|
||||||
@Directive({selector: '[if]'})
|
|
||||||
export class IfDirective {
|
|
||||||
constructor(template: TemplateRef<any>) { }
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'my-component',
|
|
||||||
template: '<ul #foo><li *if>{{salutation}} {{foo}}</li></ul>'
|
|
||||||
})
|
|
||||||
export class MyComponent {
|
|
||||||
salutation = 'Hello';
|
|
||||||
}
|
|
||||||
|
|
||||||
@NgModule({declarations: [IfDirective, MyComponent]})
|
|
||||||
export class MyModule {}
|
|
||||||
`
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const IfDirectiveDefinition = `
|
|
||||||
static ngDirectiveDef = $r3$.ɵdefineDirective({
|
|
||||||
type: IfDirective,
|
|
||||||
factory: function IfDirective_Factory() { return new IfDirective($r3$.ɵinjectTemplateRef()); }
|
|
||||||
});`;
|
|
||||||
const MyComponentDefinition = `
|
|
||||||
const $c1$ = ['foo', ''];
|
|
||||||
const $c2$ = [IfDirective];
|
|
||||||
…
|
|
||||||
static ngComponentDef = $r3$.ɵdefineComponent({
|
|
||||||
type: MyComponent,
|
|
||||||
tag: 'my-component',
|
|
||||||
factory: function MyComponent_Factory() { return new MyComponent(); },
|
|
||||||
template: function MyComponent_Template(ctx: IDENT, cm: IDENT) {
|
|
||||||
if (cm) {
|
|
||||||
$r3$.ɵE(0, 'ul', null, null, $c1$);
|
|
||||||
$r3$.ɵC(2, $c2$, MyComponent_IfDirective_Template_2);
|
|
||||||
$r3$.ɵe();
|
|
||||||
}
|
|
||||||
const $foo$ = $r3$.ɵld(1);
|
|
||||||
IfDirective.ngDirectiveDef.h(3,2);
|
|
||||||
$r3$.ɵcR(2);
|
|
||||||
$r3$.ɵr(3,2);
|
|
||||||
$r3$.ɵcr();
|
|
||||||
|
|
||||||
function MyComponent_IfDirective_Template_2(ctx0: IDENT, cm: IDENT) {
|
|
||||||
if (cm) {
|
|
||||||
$r3$.ɵE(0, 'li');
|
|
||||||
$r3$.ɵT(1);
|
|
||||||
$r3$.ɵe();
|
|
||||||
}
|
|
||||||
$r3$.ɵt(1, $r3$.ɵi2('', ctx.salutation, ' ', $foo$, ''));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});`;
|
|
||||||
|
|
||||||
const result = compile(files, angularFiles);
|
|
||||||
const source = result.source;
|
|
||||||
|
|
||||||
expectEmit(source, IfDirectiveDefinition, 'Incorrect IfDirective.ngDirectiveDef');
|
|
||||||
expectEmit(source, MyComponentDefinition, 'Incorrect MyComponent.ngComponentDef');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should support content projection', () => {
|
|
||||||
const files = {
|
|
||||||
app: {
|
|
||||||
'spec.ts': `
|
|
||||||
import {Component, Directive, NgModule, TemplateRef} from '@angular/core';
|
|
||||||
|
|
||||||
@Component({selector: 'simple', template: '<div><ng-content></ng-content></div>'})
|
|
||||||
export class SimpleComponent {}
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'complex',
|
|
||||||
template: \`
|
|
||||||
<div id="first"><ng-content select="span[title=toFirst]"></ng-content></div>
|
|
||||||
<div id="second"><ng-content select="span[title=toSecond]"></ng-content></div>\`
|
|
||||||
})
|
|
||||||
export class ComplexComponent { }
|
|
||||||
|
|
||||||
@NgModule({declarations: [SimpleComponent, ComplexComponent]})
|
|
||||||
export class MyModule {}
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'my-app',
|
|
||||||
template: '<simple>content</simple> <complex></complex>'
|
|
||||||
})
|
|
||||||
export class MyApp {}
|
|
||||||
`
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const SimpleComponentDefinition = `
|
|
||||||
static ngComponentDef = $r3$.ɵdefineComponent({
|
|
||||||
type: SimpleComponent,
|
|
||||||
tag: 'simple',
|
|
||||||
factory: function SimpleComponent_Factory() { return new SimpleComponent(); },
|
|
||||||
template: function SimpleComponent_Template(ctx: IDENT, cm: IDENT) {
|
|
||||||
if (cm) {
|
|
||||||
$r3$.ɵpD(0);
|
|
||||||
$r3$.ɵE(1, 'div');
|
|
||||||
$r3$.ɵP(2, 0);
|
|
||||||
$r3$.ɵe();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});`;
|
|
||||||
|
|
||||||
const ComplexComponentDefinition = `
|
|
||||||
const $c1$ = [[[['span', 'title', 'tofirst'], null]], [[['span', 'title', 'tosecond'], null]]];
|
|
||||||
const $c2$ = ['id','first'];
|
|
||||||
const $c3$ = ['id','second'];
|
|
||||||
…
|
|
||||||
static ngComponentDef = $r3$.ɵdefineComponent({
|
|
||||||
type: ComplexComponent,
|
|
||||||
tag: 'complex',
|
|
||||||
factory: function ComplexComponent_Factory() { return new ComplexComponent(); },
|
|
||||||
template: function ComplexComponent_Template(ctx: IDENT, cm: IDENT) {
|
|
||||||
if (cm) {
|
|
||||||
$r3$.ɵpD(0, $c1$);
|
|
||||||
$r3$.ɵE(1, 'div', $c2$);
|
|
||||||
$r3$.ɵP(2, 0, 1);
|
|
||||||
$r3$.ɵe();
|
|
||||||
$r3$.ɵE(3, 'div', $c3$);
|
|
||||||
$r3$.ɵP(4, 0, 2);
|
|
||||||
$r3$.ɵe();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
`;
|
|
||||||
|
|
||||||
const result = compile(files, angularFiles);
|
|
||||||
const source = result.source;
|
|
||||||
|
|
||||||
expectEmit(result.source, SimpleComponentDefinition, 'Incorrect SimpleComponent definition');
|
|
||||||
expectEmit(
|
|
||||||
result.source, ComplexComponentDefinition, 'Incorrect ComplexComponent definition');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('local reference', () => {
|
|
||||||
const files = {
|
|
||||||
app: {
|
|
||||||
'spec.ts': `
|
|
||||||
import {Component, NgModule} from '@angular/core';
|
|
||||||
|
|
||||||
@Component({selector: 'my-component', template: '<input #user>Hello {{user.value}}!'})
|
|
||||||
export class MyComponent {}
|
|
||||||
|
|
||||||
@NgModule({declarations: [MyComponent]})
|
|
||||||
export class MyModule {}
|
|
||||||
`
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const MyComponentDefinition = `
|
|
||||||
const $c1$ = ['user', ''];
|
|
||||||
…
|
|
||||||
static ngComponentDef = $r3$.ɵdefineComponent({
|
|
||||||
type: MyComponent,
|
|
||||||
tag: 'my-component',
|
|
||||||
factory: function MyComponent_Factory() { return new MyComponent(); },
|
|
||||||
template: function MyComponent_Template(ctx: IDENT, cm: IDENT) {
|
|
||||||
if (cm) {
|
|
||||||
$r3$.ɵE(0, 'input', null, null, $c1$);
|
|
||||||
$r3$.ɵe();
|
|
||||||
$r3$.ɵT(2);
|
|
||||||
}
|
|
||||||
const $user$ = $r3$.ɵld(1);
|
|
||||||
$r3$.ɵt(2, $r3$.ɵi1('Hello ', $user$.value, '!'));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
`;
|
|
||||||
|
|
||||||
const result = compile(files, angularFiles);
|
|
||||||
const source = result.source;
|
|
||||||
|
|
||||||
expectEmit(source, MyComponentDefinition, 'Incorrect MyComponent.ngComponentDef');
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('lifecycle hooks', () => {
|
|
||||||
const files = {
|
|
||||||
app: {
|
|
||||||
'spec.ts': `
|
|
||||||
import {Component, Input, NgModule} from '@angular/core';
|
|
||||||
|
|
||||||
let events: string[] = [];
|
|
||||||
|
|
||||||
@Component({selector: 'lifecycle-comp', template: ''})
|
|
||||||
export class LifecycleComp {
|
|
||||||
@Input('name') nameMin: string;
|
|
||||||
|
|
||||||
ngOnChanges() { events.push('changes' + this.nameMin); }
|
|
||||||
|
|
||||||
ngOnInit() { events.push('init' + this.nameMin); }
|
|
||||||
ngDoCheck() { events.push('check' + this.nameMin); }
|
|
||||||
|
|
||||||
ngAfterContentInit() { events.push('content init' + this.nameMin); }
|
|
||||||
ngAfterContentChecked() { events.push('content check' + this.nameMin); }
|
|
||||||
|
|
||||||
ngAfterViewInit() { events.push('view init' + this.nameMin); }
|
|
||||||
ngAfterViewChecked() { events.push('view check' + this.nameMin); }
|
|
||||||
|
|
||||||
ngOnDestroy() { events.push(this.nameMin); }
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'simple-layout',
|
|
||||||
template: \`
|
|
||||||
<lifecycle-comp [name]="name1"></lifecycle-comp>
|
|
||||||
<lifecycle-comp [name]="name2"></lifecycle-comp>
|
|
||||||
\`
|
|
||||||
})
|
|
||||||
export class SimpleLayout {
|
|
||||||
name1 = '1';
|
|
||||||
name2 = '2';
|
|
||||||
}
|
|
||||||
|
|
||||||
@NgModule({declarations: [LifecycleComp, SimpleLayout]})
|
|
||||||
export class LifecycleModule {}
|
|
||||||
`
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
it('should gen hooks with a few simple components', () => {
|
|
||||||
const LifecycleCompDefinition = `
|
|
||||||
static ngComponentDef = $r3$.ɵdefineComponent({
|
|
||||||
type: LifecycleComp,
|
|
||||||
tag: 'lifecycle-comp',
|
|
||||||
factory: function LifecycleComp_Factory() { return new LifecycleComp(); },
|
|
||||||
template: function LifecycleComp_Template(ctx: IDENT, cm: IDENT) {},
|
|
||||||
inputs: {nameMin: 'name'},
|
|
||||||
features: [$r3$.ɵNgOnChangesFeature(LifecycleComp)]
|
|
||||||
});`;
|
|
||||||
|
|
||||||
const SimpleLayoutDefinition = `
|
|
||||||
const $c1$ = LifecycleComp.ngComponentDef;
|
|
||||||
…
|
|
||||||
static ngComponentDef = $r3$.ɵdefineComponent({
|
|
||||||
type: SimpleLayout,
|
|
||||||
tag: 'simple-layout',
|
|
||||||
factory: function SimpleLayout_Factory() { return new SimpleLayout(); },
|
|
||||||
template: function SimpleLayout_Template(ctx: IDENT, cm: IDENT) {
|
|
||||||
if (cm) {
|
|
||||||
$r3$.ɵE(0, LifecycleComp);
|
|
||||||
$r3$.ɵe();
|
|
||||||
$r3$.ɵE(2, LifecycleComp);
|
|
||||||
$r3$.ɵe();
|
|
||||||
}
|
|
||||||
$r3$.ɵp(0, 'name', $r3$.ɵb(ctx.name1));
|
|
||||||
$r3$.ɵp(2, 'name', $r3$.ɵb(ctx.name2));
|
|
||||||
$c1$.h(1, 0);
|
|
||||||
$c1$.h(3, 2);
|
|
||||||
$r3$.ɵr(1, 0);
|
|
||||||
$r3$.ɵr(3, 2);
|
|
||||||
}
|
|
||||||
});`;
|
|
||||||
|
|
||||||
const result = compile(files, angularFiles);
|
|
||||||
const source = result.source;
|
|
||||||
|
|
||||||
expectEmit(source, LifecycleCompDefinition, 'Invalid LifecycleComp definition');
|
|
||||||
expectEmit(source, SimpleLayoutDefinition, 'Invalid SimpleLayout definition');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('template variables', () => {
|
|
||||||
const shared = {
|
|
||||||
shared: {
|
|
||||||
'for_of.ts': `
|
|
||||||
import {Directive, Input, SimpleChanges, TemplateRef, ViewContainerRef} from '@angular/core';
|
|
||||||
|
|
||||||
export interface ForOfContext {
|
|
||||||
$implicit: any;
|
|
||||||
index: number;
|
|
||||||
even: boolean;
|
|
||||||
odd: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Directive({selector: '[forOf]'})
|
|
||||||
export class ForOfDirective {
|
|
||||||
private previous: any[];
|
|
||||||
|
|
||||||
constructor(private view: ViewContainerRef, private template: TemplateRef<any>) {}
|
|
||||||
|
|
||||||
@Input() forOf: any[];
|
|
||||||
|
|
||||||
ngOnChanges(simpleChanges: SimpleChanges) {
|
|
||||||
if ('forOf' in simpleChanges) {
|
|
||||||
this.update();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ngDoCheck(): void {
|
|
||||||
const previous = this.previous;
|
|
||||||
const current = this.forOf;
|
|
||||||
if (!previous || previous.length != current.length ||
|
|
||||||
previous.some((value: any, index: number) => current[index] !== previous[index])) {
|
|
||||||
this.update();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private update() {
|
|
||||||
// TODO(chuckj): Not implemented yet
|
|
||||||
// this.view.clear();
|
|
||||||
if (this.forOf) {
|
|
||||||
const current = this.forOf;
|
|
||||||
for (let i = 0; i < current.length; i++) {
|
|
||||||
const context = {$implicit: current[i], index: i, even: i % 2 == 0, odd: i % 2 == 1};
|
|
||||||
// TODO(chuckj): Not implemented yet
|
|
||||||
// this.view.createEmbeddedView(this.template, context);
|
|
||||||
}
|
|
||||||
this.previous = [...this.forOf];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
it('should support a let variable and reference', () => {
|
|
||||||
const files = {
|
|
||||||
app: {
|
|
||||||
...shared,
|
|
||||||
'spec.ts': `
|
|
||||||
import {Component, NgModule} from '@angular/core';
|
|
||||||
import {ForOfDirective} from './shared/for_of';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'my-component',
|
|
||||||
template: \`<ul><li *for="let item of items">{{item.name}}</li></ul>\`
|
|
||||||
})
|
|
||||||
export class MyComponent {
|
|
||||||
items = [{name: 'one'}, {name: 'two'}];
|
|
||||||
}
|
|
||||||
|
|
||||||
@NgModule({
|
|
||||||
declarations: [MyComponent, ForOfDirective]
|
|
||||||
})
|
|
||||||
export class MyModule {}
|
|
||||||
`
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO(chuckj): Enforce this when the directives are specified
|
|
||||||
const ForDirectiveDefinition = `
|
|
||||||
static ngDirectiveDef = $r3$.ɵdefineDirective({
|
|
||||||
type: ForOfDirective,
|
|
||||||
factory: function ForOfDirective_Factory() {
|
|
||||||
return new ForOfDirective($r3$.ɵinjectViewContainerRef(), $r3$.ɵinjectTemplateRef());
|
|
||||||
},
|
|
||||||
features: [$r3$.ɵNgOnChangesFeature(NgForOf)],
|
|
||||||
inputs: {forOf: 'forOf'}
|
|
||||||
});
|
|
||||||
`;
|
|
||||||
|
|
||||||
const MyComponentDefinition = `
|
|
||||||
const $c1$ = [ForOfDirective];
|
|
||||||
…
|
|
||||||
static ngComponentDef = $r3$.ɵdefineComponent({
|
|
||||||
type: MyComponent,
|
|
||||||
tag: 'my-component',
|
|
||||||
factory: function MyComponent_Factory() { return new MyComponent(); },
|
|
||||||
template: function MyComponent_Template(ctx: IDENT, cm: IDENT) {
|
|
||||||
if (cm) {
|
|
||||||
$r3$.ɵE(0, 'ul');
|
|
||||||
$r3$.ɵC(1, $c1$, MyComponent_ForOfDirective_Template_1);
|
|
||||||
$r3$.ɵe();
|
|
||||||
}
|
|
||||||
$r3$.ɵp(1, 'forOf', $r3$.ɵb(ctx.items));
|
|
||||||
ForOfDirective.ngDirectiveDef.h(2, 1);
|
|
||||||
$r3$.ɵcR(1);
|
|
||||||
$r3$.ɵr(2, 1);
|
|
||||||
$r3$.ɵcr();
|
|
||||||
|
|
||||||
function MyComponent_ForOfDirective_Template_1(ctx0: IDENT, cm: IDENT) {
|
|
||||||
if (cm) {
|
|
||||||
$r3$.ɵE(0, 'li');
|
|
||||||
$r3$.ɵT(1);
|
|
||||||
$r3$.ɵe();
|
|
||||||
}
|
|
||||||
const $item$ = ctx0.$implicit;
|
|
||||||
$r3$.ɵt(1, $r3$.ɵi1('', $item$.name, ''));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
`;
|
|
||||||
|
|
||||||
const result = compile(files, angularFiles);
|
|
||||||
const source = result.source;
|
|
||||||
|
|
||||||
// TODO(chuckj): Enforce this when the directives are specified
|
|
||||||
// expectEmit(source, ForDirectiveDefinition, 'Invalid directive definition');
|
|
||||||
expectEmit(source, MyComponentDefinition, 'Invalid component definition');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should support accessing parent template variables', () => {
|
|
||||||
const files = {
|
|
||||||
app: {
|
|
||||||
...shared,
|
|
||||||
'spec.ts': `
|
|
||||||
import {Component, NgModule} from '@angular/core';
|
|
||||||
import {ForOfDirective} from './shared/for_of';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'my-component',
|
|
||||||
template: \`
|
|
||||||
<ul>
|
|
||||||
<li *for="let item of items">
|
|
||||||
<div>{{item.name}}</div>
|
|
||||||
<ul>
|
|
||||||
<li *for="let info of item.infos">
|
|
||||||
{{item.name}}: {{info.description}}
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
</ul>\`
|
|
||||||
})
|
|
||||||
export class MyComponent {
|
|
||||||
items = [
|
|
||||||
{name: 'one', infos: [{description: '11'}, {description: '12'}]},
|
|
||||||
{name: 'two', infos: [{description: '21'}, {description: '22'}]}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
@NgModule({
|
|
||||||
declarations: [MyComponent, ForOfDirective]
|
|
||||||
})
|
|
||||||
export class MyModule {}
|
|
||||||
`
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const MyComponentDefinition = `
|
|
||||||
const $c1$ = [ForOfDirective];
|
|
||||||
const $c2$ = ForOfDirective.ngDirectiveDef;
|
|
||||||
…
|
|
||||||
static ngComponentDef = $r3$.ɵdefineComponent({
|
|
||||||
type: MyComponent,
|
|
||||||
tag: 'my-component',
|
|
||||||
factory: function MyComponent_Factory() { return new MyComponent(); },
|
|
||||||
template: function MyComponent_Template(ctx: IDENT, cm: IDENT) {
|
|
||||||
if (cm) {
|
|
||||||
$r3$.ɵE(0, 'ul');
|
|
||||||
$r3$.ɵC(1, $c1$, MyComponent_ForOfDirective_Template_1);
|
|
||||||
$r3$.ɵe();
|
|
||||||
}
|
|
||||||
$r3$.ɵp(1, 'forOf', $r3$.ɵb(ctx.items));
|
|
||||||
$c2$.h(2,1);
|
|
||||||
$r3$.ɵcR(1);
|
|
||||||
$r3$.ɵr(2, 1);
|
|
||||||
$r3$.ɵcr();
|
|
||||||
|
|
||||||
function MyComponent_ForOfDirective_Template_1(ctx0: IDENT, cm: IDENT) {
|
|
||||||
if (cm) {
|
|
||||||
$r3$.ɵE(0, 'li');
|
|
||||||
$r3$.ɵE(1, 'div');
|
|
||||||
$r3$.ɵT(2);
|
|
||||||
$r3$.ɵe();
|
|
||||||
$r3$.ɵE(3, 'ul');
|
|
||||||
$r3$.ɵC(4, $c1$, MyComponent_ForOfDirective_ForOfDirective_Template_4);
|
|
||||||
$r3$.ɵe();
|
|
||||||
$r3$.ɵe();
|
|
||||||
}
|
|
||||||
const $item$ = ctx0.$implicit;
|
|
||||||
$r3$.ɵp(4, 'forOf', $r3$.ɵb(IDENT.infos));
|
|
||||||
$c2$.h(5,4);
|
|
||||||
$r3$.ɵt(2, $r3$.ɵi1('', IDENT.name, ''));
|
|
||||||
$r3$.ɵcR(4);
|
|
||||||
$r3$.ɵr(5, 4);
|
|
||||||
$r3$.ɵcr();
|
|
||||||
|
|
||||||
function MyComponent_ForOfDirective_ForOfDirective_Template_4(
|
|
||||||
ctx1: IDENT, cm: IDENT) {
|
|
||||||
if (cm) {
|
|
||||||
$r3$.ɵE(0, 'li');
|
|
||||||
$r3$.ɵT(1);
|
|
||||||
$r3$.ɵe();
|
|
||||||
}
|
|
||||||
const $info$ = ctx1.$implicit;
|
|
||||||
$r3$.ɵt(1, $r3$.ɵi2(' ', $item$.name, ': ', $info$.description, ' '));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});`;
|
|
||||||
|
|
||||||
const result = compile(files, angularFiles);
|
|
||||||
const source = result.source;
|
|
||||||
expectEmit(source, MyComponentDefinition, 'Invalid component definition');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue