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 * as o from '../output/output_ast';
|
||||
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 {CompiledStylesheet, StyleCompiler} from '../style_compiler';
|
||||
import {SummaryResolver} from '../summary_resolver';
|
||||
|
@ -356,14 +357,22 @@ export class AotCompiler {
|
|||
error(
|
||||
`Cannot determine the module for component '${identifierName(directiveMetadata.type)}'`);
|
||||
|
||||
const {template: parsedTemplate} =
|
||||
const {template: parsedTemplate, pipes: parsedPipes} =
|
||||
this._parseTemplate(directiveMetadata, module, module.transitiveModule.directives);
|
||||
compileIvyComponent(context, directiveMetadata, parsedTemplate, this._reflector);
|
||||
compileIvyComponent(
|
||||
context, directiveMetadata, parsedPipes, parsedTemplate, this._reflector);
|
||||
} else {
|
||||
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));
|
||||
|
||||
if (context.statements && context.statements.length > 0) {
|
||||
|
|
|
@ -11,7 +11,7 @@ import {OutputContext, error} from './util';
|
|||
|
||||
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
|
||||
|
@ -51,6 +51,7 @@ export class ConstantPool {
|
|||
private injectorDefinitions = new Map<any, FixupExpression>();
|
||||
private directiveDefinitions = new Map<any, FixupExpression>();
|
||||
private componentDefinitions = new Map<any, FixupExpression>();
|
||||
private pipeDefinitions = new Map<any, FixupExpression>();
|
||||
|
||||
private nextNameIndex = 0;
|
||||
|
||||
|
@ -75,18 +76,19 @@ export class ConstantPool {
|
|||
return fixup;
|
||||
}
|
||||
|
||||
getDefinition(type: any, kind: DefinitionKind, ctx: OutputContext): o.Expression {
|
||||
const declarations = kind == DefinitionKind.Component ?
|
||||
this.componentDefinitions :
|
||||
kind == DefinitionKind.Directive ? this.directiveDefinitions : this.injectorDefinitions;
|
||||
let fixup = declarations.get(type);
|
||||
getDefinition(type: any, kind: DefinitionKind, ctx: OutputContext, forceShared: boolean = false):
|
||||
o.Expression {
|
||||
const definitions = this.definitionsOf(kind);
|
||||
let fixup = definitions.get(type);
|
||||
let newValue = false;
|
||||
if (!fixup) {
|
||||
const property = kind == DefinitionKind.Component ?
|
||||
'ngComponentDef' :
|
||||
kind == DefinitionKind.Directive ? 'ngDirectiveDef' : 'ngInjectorDef';
|
||||
const property = this.propertyNameOf(kind);
|
||||
fixup = new FixupExpression(ctx.importExpr(type).prop(property));
|
||||
declarations.set(type, fixup);
|
||||
} else if (!fixup.shared) {
|
||||
definitions.set(type, fixup);
|
||||
newValue = true;
|
||||
}
|
||||
|
||||
if ((!newValue && !fixup.shared) || (newValue && forceShared)) {
|
||||
const name = this.freshName();
|
||||
this.statements.push(
|
||||
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++}`; }
|
||||
|
||||
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 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) {
|
||||
function visit(ast: AST) {
|
||||
visitor.visit && visitor.visit(ast, context) || ast.visit(visitor, context);
|
||||
|
|
|
@ -21,6 +21,7 @@ export class Identifiers {
|
|||
/* Methods */
|
||||
static NEW_METHOD = 'n';
|
||||
static HOST_BINDING_METHOD = 'h';
|
||||
static TRANSFORM_METHOD = 'transform';
|
||||
|
||||
/* Instructions */
|
||||
static createElement: o.ExternalReference = {name: 'ɵE', moduleName: CORE};
|
||||
|
@ -63,8 +64,16 @@ export class Identifiers {
|
|||
static interpolation8: o.ExternalReference = {name: 'ɵi8', 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 pipe: o.ExternalReference = {name: 'ɵPp', moduleName: CORE};
|
||||
|
||||
static projection: o.ExternalReference = {name: 'ɵP', moduleName: CORE};
|
||||
static projectionDef: o.ExternalReference = {name: 'ɵpD', moduleName: CORE};
|
||||
|
||||
|
@ -88,5 +97,7 @@ export class Identifiers {
|
|||
moduleName: CORE,
|
||||
};
|
||||
|
||||
static definePipe: o.ExternalReference = {name: 'ɵdefinePipe', 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 {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 {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 {LifecycleHooks} from '../lifecycle_reflector';
|
||||
import * as o from '../output/output_ast';
|
||||
|
@ -21,6 +21,7 @@ import {OutputContext, error} from '../util';
|
|||
|
||||
import {Identifiers as R3} from './r3_identifiers';
|
||||
|
||||
|
||||
/** Name of the context parameter passed into a template function */
|
||||
const CONTEXT_NAME = 'ctx';
|
||||
|
||||
|
@ -62,7 +63,7 @@ export function compileDirective(
|
|||
/* name */ className,
|
||||
/* parent */ null,
|
||||
/* fields */[new o.ClassField(
|
||||
/* name */ 'ngDirectiveDef',
|
||||
/* name */ outputCtx.constantPool.propertyNameOf(DefinitionKind.Directive),
|
||||
/* type */ o.INFERRED_TYPE,
|
||||
/* modifiers */[o.StmtModifier.Static],
|
||||
/* initializer */ o.importExpr(R3.defineDirective).callFn([o.literalMap(
|
||||
|
@ -73,8 +74,8 @@ export function compileDirective(
|
|||
}
|
||||
|
||||
export function compileComponent(
|
||||
outputCtx: OutputContext, component: CompileDirectiveMetadata, template: TemplateAst[],
|
||||
reflector: CompileReflector) {
|
||||
outputCtx: OutputContext, component: CompileDirectiveMetadata, pipes: CompilePipeSummary[],
|
||||
template: TemplateAst[], reflector: CompileReflector) {
|
||||
const definitionMapValues: {key: string, quoted: boolean, value: o.Expression}[] = [];
|
||||
|
||||
// e.g. `type: MyApp`
|
||||
|
@ -112,10 +113,11 @@ export function compileComponent(
|
|||
// e.g. `template: function MyComponent_Template(_ctx, _cm) {...}`
|
||||
const templateTypeName = component.type.reference.name;
|
||||
const templateName = templateTypeName ? `${templateTypeName}_Template` : null;
|
||||
const pipeMap = new Map(pipes.map<[string, CompilePipeSummary]>(pipe => [pipe.name, pipe]));
|
||||
const templateFunctionExpression =
|
||||
new TemplateDefinitionBuilder(
|
||||
outputCtx, outputCtx.constantPool, reflector, CONTEXT_NAME, ROOT_SCOPE.nestedScope(), 0,
|
||||
component.template !.ngContentSelectors, templateTypeName, templateName)
|
||||
component.template !.ngContentSelectors, templateTypeName, templateName, pipeMap)
|
||||
.buildTemplateFunction(template, []);
|
||||
definitionMapValues.push({key: 'template', value: templateFunctionExpression, quoted: false});
|
||||
|
||||
|
@ -143,7 +145,7 @@ export function compileComponent(
|
|||
/* name */ className,
|
||||
/* parent */ null,
|
||||
/* fields */[new o.ClassField(
|
||||
/* name */ 'ngComponentDef',
|
||||
/* name */ outputCtx.constantPool.propertyNameOf(DefinitionKind.Component),
|
||||
/* type */ o.INFERRED_TYPE,
|
||||
/* modifiers */[o.StmtModifier.Static],
|
||||
/* 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)]);
|
||||
}
|
||||
|
||||
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 {
|
||||
private map = new Map<string, o.Expression>();
|
||||
private referenceNameIndex = 0;
|
||||
|
@ -219,10 +237,10 @@ class BindingScope {
|
|||
return null;
|
||||
}
|
||||
|
||||
set(name: string, variableName: string): BindingScope {
|
||||
set(name: string, value: o.Expression): BindingScope {
|
||||
!this.map.has(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;
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
private _dataIndex = 0;
|
||||
|
@ -251,6 +269,7 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
|
|||
private _postfix: o.Statement[] = [];
|
||||
private _contentProjections: Map<NgContentAst, NgContentInfo>;
|
||||
private _projectionDefinitionIndex = 0;
|
||||
private _pipeConverter: PipeConverter;
|
||||
private unsupported = unsupported;
|
||||
private invalid = invalid;
|
||||
|
||||
|
@ -258,7 +277,23 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
|
|||
private outputCtx: OutputContext, private constantPool: ConstantPool,
|
||||
private reflector: CompileReflector, private contextParameter: 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 {
|
||||
// Create variable bindings
|
||||
|
@ -272,7 +307,7 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
|
|||
]);
|
||||
|
||||
// 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
|
||||
this._bindingMode.push(declaration);
|
||||
|
@ -332,8 +367,10 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
|
|||
o.INFERRED_TYPE, null, this.templateName);
|
||||
}
|
||||
|
||||
// LocalResolver
|
||||
getLocal(name: string): o.Expression|null { return this.bindingScope.get(name); }
|
||||
|
||||
// TemplateAstVisitor
|
||||
visitNgContent(ast: NgContentAst) {
|
||||
const info = this._contentProjections.get(ast) !;
|
||||
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) {
|
||||
let bindingCount = 0;
|
||||
const elementIndex = this.allocateDataSlot();
|
||||
|
@ -410,7 +448,7 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
|
|||
this._bindingMode.push(o.variable(variableName, o.INFERRED_TYPE)
|
||||
.set(o.importExpr(R3.load).callFn([o.literal(slot)]))
|
||||
.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];
|
||||
})).map(value => o.literal(value));
|
||||
parameters.push(
|
||||
|
@ -434,18 +472,14 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
|
|||
if (input.isAnimation) {
|
||||
this.unsupported('animations');
|
||||
}
|
||||
// TODO(chuckj): Built-in transform?
|
||||
const convertedBinding = convertPropertyBinding(
|
||||
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 convertedBinding = this.convertPropertyBinding(implicit, input.value);
|
||||
const parameters = [o.literal(elementIndex), o.literal(input.name), convertedBinding];
|
||||
const instruction = BINDING_INSTRUCTION_MAP[input.type];
|
||||
if (instruction) {
|
||||
// TODO(chuckj): runtime: security context?
|
||||
this.instruction(
|
||||
this._bindingMode, input.sourceSpan, instruction, o.literal(elementIndex),
|
||||
o.literal(input.name), convertedBinding.currValExpr);
|
||||
o.literal(input.name), convertedBinding);
|
||||
} else {
|
||||
this.unsupported(`binding ${PropertyBindingType[input.type]}`);
|
||||
}
|
||||
|
@ -479,13 +513,10 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
|
|||
|
||||
// Bindings
|
||||
for (const input of directive.inputs) {
|
||||
const convertedBinding = convertPropertyBinding(
|
||||
this, implicit, input.value, this.bindingContext(), BindingForm.TrySimple, interpolate);
|
||||
this._bindingMode.push(...convertedBinding.stmts);
|
||||
const convertedBinding = this.convertPropertyBinding(implicit, input.value);
|
||||
this.instruction(
|
||||
this._bindingMode, directive.sourceSpan, R3.elementProperty, o.literal(nodeIndex),
|
||||
o.literal(input.templateName),
|
||||
o.importExpr(R3.bind).callFn([convertedBinding.currValExpr]));
|
||||
o.literal(input.templateName), o.importExpr(R3.bind).callFn([convertedBinding]));
|
||||
}
|
||||
|
||||
// e.g. MyDirective.ngDirectiveDef.h(0, 0);
|
||||
|
@ -501,6 +532,7 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
|
|||
}
|
||||
}
|
||||
|
||||
// TemplateAstVisitor
|
||||
visitEmbeddedTemplate(ast: EmbeddedTemplateAst) {
|
||||
const templateIndex = this.allocateDataSlot();
|
||||
|
||||
|
@ -539,7 +571,7 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
|
|||
const templateVisitor = new TemplateDefinitionBuilder(
|
||||
this.outputCtx, this.constantPool, this.reflector, templateContext,
|
||||
this.bindingScope.nestedScope(), this.level + 1, this.ngContentSelectors, contextName,
|
||||
templateName);
|
||||
templateName, this.pipes);
|
||||
const templateFunctionExpr = templateVisitor.buildTemplateFunction(ast.children, ast.variables);
|
||||
this._postfix.push(templateFunctionExpr.toDeclStmt(templateName, null));
|
||||
}
|
||||
|
@ -551,6 +583,7 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
|
|||
readonly visitElementProperty = invalid;
|
||||
readonly visitAttr = invalid;
|
||||
|
||||
// TemplateAstVisitor
|
||||
visitBoundText(ast: BoundTextAst) {
|
||||
const nodeIndex = this.allocateDataSlot();
|
||||
|
||||
|
@ -563,6 +596,7 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
|
|||
this.bind(o.variable(CONTEXT_NAME), ast.value, ast.sourceSpan));
|
||||
}
|
||||
|
||||
// TemplateAstVisitor
|
||||
visitText(ast: TextAst) {
|
||||
// Text is defined in creation mode only.
|
||||
this.instruction(
|
||||
|
@ -600,8 +634,10 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
|
|||
}
|
||||
|
||||
private convertPropertyBinding(implicit: o.Expression, value: AST): o.Expression {
|
||||
const pipesConvertedValue = value.visit(this._pipeConverter);
|
||||
const convertedPropertyBinding = convertPropertyBinding(
|
||||
this, implicit, value, this.bindingContext(), BindingForm.TrySimple, interpolate);
|
||||
this, implicit, pipesConvertedValue, this.bindingContext(), BindingForm.TrySimple,
|
||||
interpolate);
|
||||
this._refreshMode.push(...convertedPropertyBinding.stmts);
|
||||
return convertedPropertyBinding.currValExpr;
|
||||
}
|
||||
|
@ -611,7 +647,7 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
|
|||
}
|
||||
}
|
||||
|
||||
function createFactory(
|
||||
export function createFactory(
|
||||
type: CompileTypeMetadata, outputCtx: OutputContext,
|
||||
reflector: CompileReflector): o.FunctionExpr {
|
||||
let args: o.Expression[] = [];
|
||||
|
@ -652,6 +688,35 @@ function createFactory(
|
|||
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 {
|
||||
throw new Error(
|
||||
`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
|
||||
*/
|
||||
|
||||
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 * as ts from 'typescript';
|
||||
|
||||
import {ConstantPool} from '../../src/constant_pool';
|
||||
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 {OutputContext} from '../../src/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.
|
||||
const analyzedModules = analyzeNgModules(sourceFiles, compilerHost, symbolResolver, resolver);
|
||||
|
||||
const directives = Array.from(analyzedModules.ngModuleByPipeOrDirective.keys());
|
||||
const pipesOrDirectives = Array.from(analyzedModules.ngModuleByPipeOrDirective.keys());
|
||||
|
||||
const fakeOutputContext: OutputContext = {
|
||||
genFilePath: 'fakeFactory.ts',
|
||||
|
@ -193,20 +194,20 @@ export function compile(
|
|||
constantPool: new ConstantPool()
|
||||
};
|
||||
|
||||
// Load All directives
|
||||
for (const directive of directives) {
|
||||
const module = analyzedModules.ngModuleByPipeOrDirective.get(directive) !;
|
||||
// Load all directives and pipes
|
||||
for (const pipeOrDirective of pipesOrDirectives) {
|
||||
const module = analyzedModules.ngModuleByPipeOrDirective.get(pipeOrDirective) !;
|
||||
resolver.loadNgModuleDirectiveAndPipeMetadata(module.type.reference, true);
|
||||
}
|
||||
|
||||
// Compile the directives.
|
||||
for (const directive of directives) {
|
||||
const module = analyzedModules.ngModuleByPipeOrDirective.get(directive);
|
||||
for (const pipeOrDirective of pipesOrDirectives) {
|
||||
const module = analyzedModules.ngModuleByPipeOrDirective.get(pipeOrDirective);
|
||||
if (!module || !module.type.reference.filePath.startsWith('/app')) {
|
||||
continue;
|
||||
}
|
||||
if (resolver.isDirective(directive)) {
|
||||
const metadata = resolver.getDirectiveMetadata(directive);
|
||||
if (resolver.isDirective(pipeOrDirective)) {
|
||||
const metadata = resolver.getDirectiveMetadata(pipeOrDirective);
|
||||
if (metadata.isComponent) {
|
||||
const fakeUrl = 'ng://fake-template-url.html';
|
||||
const htmlAst = htmlParser.parse(metadata.template !.template !, fakeUrl);
|
||||
|
@ -217,11 +218,16 @@ export function compile(
|
|||
module.transitiveModule.pipes.map(pipe => resolver.getPipeSummary(pipe.reference));
|
||||
const parsedTemplate = templateParser.parse(
|
||||
metadata, htmlAst, directives, pipes, module.schemas, fakeUrl, false);
|
||||
|
||||
compileComponent(fakeOutputContext, metadata, parsedTemplate.template, staticReflector);
|
||||
compileComponent(
|
||||
fakeOutputContext, metadata, pipes, parsedTemplate.template, staticReflector);
|
||||
} else {
|
||||
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