feat(compiler): integrate compiler with view engine - change detection tests work (#14412)
Included refactoring: - make ViewData.parentIndex point to component provider index - split NodeType.Provider into Provider / Directive / Pipe - make purePipe take the real pipe as argument to detect changes - order change detection: 1) directive props 2) renderer props Part of #14013 PR Close #14412
This commit is contained in:
parent
1dc9be4b7d
commit
e4e9dbe33d
|
@ -205,14 +205,14 @@ export class AotCompiler {
|
||||||
const pipes = ngModule.transitiveModule.pipes.map(
|
const pipes = ngModule.transitiveModule.pipes.map(
|
||||||
pipe => this._metadataResolver.getPipeSummary(pipe.reference));
|
pipe => this._metadataResolver.getPipeSummary(pipe.reference));
|
||||||
|
|
||||||
const parsedTemplate = this._templateParser.parse(
|
const {template: parsedTemplate, pipes: usedPipes} = this._templateParser.parse(
|
||||||
compMeta, compMeta.template.template, directives, pipes, ngModule.schemas,
|
compMeta, compMeta.template.template, directives, pipes, ngModule.schemas,
|
||||||
identifierName(compMeta.type));
|
identifierName(compMeta.type));
|
||||||
const stylesExpr = componentStyles ? o.variable(componentStyles.stylesVar) : o.literalArr([]);
|
const stylesExpr = componentStyles ? o.variable(componentStyles.stylesVar) : o.literalArr([]);
|
||||||
const compiledAnimations =
|
const compiledAnimations =
|
||||||
this._animationCompiler.compile(identifierName(compMeta.type), parsedAnimations);
|
this._animationCompiler.compile(identifierName(compMeta.type), parsedAnimations);
|
||||||
const viewResult = this._viewCompiler.compileComponent(
|
const viewResult = this._viewCompiler.compileComponent(
|
||||||
compMeta, parsedTemplate, stylesExpr, pipes, compiledAnimations);
|
compMeta, parsedTemplate, stylesExpr, usedPipes, compiledAnimations);
|
||||||
if (componentStyles) {
|
if (componentStyles) {
|
||||||
targetStatements.push(
|
targetStatements.push(
|
||||||
..._resolveStyleStatements(this._symbolResolver, componentStyles, fileSuffix));
|
..._resolveStyleStatements(this._symbolResolver, componentStyles, fileSuffix));
|
||||||
|
|
|
@ -17,58 +17,9 @@ import {createPureProxy} from './identifier_util';
|
||||||
|
|
||||||
const VAL_UNWRAPPER_VAR = o.variable(`valUnwrapper`);
|
const VAL_UNWRAPPER_VAR = o.variable(`valUnwrapper`);
|
||||||
|
|
||||||
export interface NameResolver {
|
|
||||||
callPipe(name: string, input: o.Expression, args: o.Expression[]): o.Expression;
|
|
||||||
getLocal(name: string): o.Expression;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class EventHandlerVars { static event = o.variable('$event'); }
|
export class EventHandlerVars { static event = o.variable('$event'); }
|
||||||
|
|
||||||
export class ConvertPropertyBindingResult {
|
export interface LocalResolver { getLocal(name: string): o.Expression; }
|
||||||
constructor(
|
|
||||||
public stmts: o.Statement[], public currValExpr: o.Expression,
|
|
||||||
public forceUpdate: o.Expression) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts the given expression AST into an executable output AST, assuming the expression is
|
|
||||||
* used in a property binding.
|
|
||||||
*/
|
|
||||||
export function convertPropertyBinding(
|
|
||||||
builder: ClassBuilder, nameResolver: NameResolver, implicitReceiver: o.Expression,
|
|
||||||
expression: cdAst.AST, bindingId: string): ConvertPropertyBindingResult {
|
|
||||||
const currValExpr = createCurrValueExpr(bindingId);
|
|
||||||
const stmts: o.Statement[] = [];
|
|
||||||
if (!nameResolver) {
|
|
||||||
nameResolver = new DefaultNameResolver();
|
|
||||||
}
|
|
||||||
const visitor = new _AstToIrVisitor(
|
|
||||||
builder, nameResolver, implicitReceiver, VAL_UNWRAPPER_VAR, bindingId, false);
|
|
||||||
const outputExpr: o.Expression = expression.visit(visitor, _Mode.Expression);
|
|
||||||
|
|
||||||
if (!outputExpr) {
|
|
||||||
// e.g. an empty expression was given
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (visitor.temporaryCount) {
|
|
||||||
for (let i = 0; i < visitor.temporaryCount; i++) {
|
|
||||||
stmts.push(temporaryDeclaration(bindingId, i));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (visitor.needsValueUnwrapper) {
|
|
||||||
const initValueUnwrapperStmt = VAL_UNWRAPPER_VAR.callMethod('reset', []).toStmt();
|
|
||||||
stmts.push(initValueUnwrapperStmt);
|
|
||||||
}
|
|
||||||
stmts.push(currValExpr.set(outputExpr).toDeclStmt(null, [o.StmtModifier.Final]));
|
|
||||||
if (visitor.needsValueUnwrapper) {
|
|
||||||
return new ConvertPropertyBindingResult(
|
|
||||||
stmts, currValExpr, VAL_UNWRAPPER_VAR.prop('hasWrappedValue'));
|
|
||||||
} else {
|
|
||||||
return new ConvertPropertyBindingResult(stmts, currValExpr, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ConvertActionBindingResult {
|
export class ConvertActionBindingResult {
|
||||||
constructor(public stmts: o.Statement[], public allowDefault: o.ReadVarExpr) {}
|
constructor(public stmts: o.Statement[], public allowDefault: o.ReadVarExpr) {}
|
||||||
|
@ -79,15 +30,31 @@ export class ConvertActionBindingResult {
|
||||||
* used in an action binding (e.g. an event handler).
|
* used in an action binding (e.g. an event handler).
|
||||||
*/
|
*/
|
||||||
export function convertActionBinding(
|
export function convertActionBinding(
|
||||||
builder: ClassBuilder, nameResolver: NameResolver, implicitReceiver: o.Expression,
|
localResolver: LocalResolver, implicitReceiver: o.Expression, action: cdAst.AST,
|
||||||
action: cdAst.AST, bindingId: string): ConvertActionBindingResult {
|
bindingId: string): ConvertActionBindingResult {
|
||||||
if (!nameResolver) {
|
if (!localResolver) {
|
||||||
nameResolver = new DefaultNameResolver();
|
localResolver = new DefaultLocalResolver();
|
||||||
}
|
}
|
||||||
const visitor =
|
const actionWithoutBuiltins = convertPropertyBindingBuiltins(
|
||||||
new _AstToIrVisitor(builder, nameResolver, implicitReceiver, null, bindingId, true);
|
{
|
||||||
|
createLiteralArrayConverter: (argCount: number) => {
|
||||||
|
// Note: no caching for literal arrays in actions.
|
||||||
|
return (args: o.Expression[]) => o.literalArr(args);
|
||||||
|
},
|
||||||
|
createLiteralMapConverter: (keys: string[]) => {
|
||||||
|
// Note: no caching for literal maps in actions.
|
||||||
|
return (args: o.Expression[]) =>
|
||||||
|
o.literalMap(<[string, o.Expression][]>keys.map((key, i) => [key, args[i]]));
|
||||||
|
},
|
||||||
|
createPipeConverter: (name: string) => {
|
||||||
|
throw new Error(`Illegal State: Actions are not allowed to contain pipes. Pipe: ${name}`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
action);
|
||||||
|
|
||||||
|
const visitor = new _AstToIrVisitor(localResolver, implicitReceiver, bindingId);
|
||||||
const actionStmts: o.Statement[] = [];
|
const actionStmts: o.Statement[] = [];
|
||||||
flattenStatements(action.visit(visitor, _Mode.Statement), actionStmts);
|
flattenStatements(actionWithoutBuiltins.visit(visitor, _Mode.Statement), actionStmts);
|
||||||
prependTemporaryDecls(visitor.temporaryCount, bindingId, actionStmts);
|
prependTemporaryDecls(visitor.temporaryCount, bindingId, actionStmts);
|
||||||
const lastIndex = actionStmts.length - 1;
|
const lastIndex = actionStmts.length - 1;
|
||||||
let preventDefaultVar: o.ReadVarExpr = null;
|
let preventDefaultVar: o.ReadVarExpr = null;
|
||||||
|
@ -106,11 +73,105 @@ export function convertActionBinding(
|
||||||
return new ConvertActionBindingResult(actionStmts, preventDefaultVar);
|
return new ConvertActionBindingResult(actionStmts, preventDefaultVar);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface BuiltinConverter { (args: o.Expression[]): o.Expression; }
|
||||||
|
|
||||||
|
export interface BuiltinConverterFactory {
|
||||||
|
createLiteralArrayConverter(argCount: number): BuiltinConverter;
|
||||||
|
createLiteralMapConverter(keys: string[]): BuiltinConverter;
|
||||||
|
createPipeConverter(name: string, argCount: number): BuiltinConverter;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function convertPropertyBindingBuiltins(
|
||||||
|
converterFactory: BuiltinConverterFactory, ast: cdAst.AST): cdAst.AST {
|
||||||
|
return convertBuiltins(converterFactory, ast);
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ConvertPropertyBindingResult {
|
||||||
|
constructor(public stmts: o.Statement[], public currValExpr: o.Expression) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts the given expression AST into an executable output AST, assuming the expression
|
||||||
|
* is used in property binding. The expression has to be preprocessed via
|
||||||
|
* `convertPropertyBindingBuiltins`.
|
||||||
|
*/
|
||||||
|
export function convertPropertyBinding(
|
||||||
|
localResolver: LocalResolver, implicitReceiver: o.Expression,
|
||||||
|
expressionWithoutBuiltins: cdAst.AST, bindingId: string): ConvertPropertyBindingResult {
|
||||||
|
if (!localResolver) {
|
||||||
|
localResolver = new DefaultLocalResolver();
|
||||||
|
}
|
||||||
|
const currValExpr = createCurrValueExpr(bindingId);
|
||||||
|
const stmts: o.Statement[] = [];
|
||||||
|
const visitor = new _AstToIrVisitor(localResolver, implicitReceiver, bindingId);
|
||||||
|
const outputExpr: o.Expression = expressionWithoutBuiltins.visit(visitor, _Mode.Expression);
|
||||||
|
|
||||||
|
if (visitor.temporaryCount) {
|
||||||
|
for (let i = 0; i < visitor.temporaryCount; i++) {
|
||||||
|
stmts.push(temporaryDeclaration(bindingId, i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stmts.push(currValExpr.set(outputExpr).toDeclStmt(null, [o.StmtModifier.Final]));
|
||||||
|
return new ConvertPropertyBindingResult(stmts, currValExpr);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export class LegacyConvertPropertyBindingResult implements ConvertPropertyBindingResult {
|
||||||
|
constructor(
|
||||||
|
public stmts: o.Statement[], public currValExpr: o.Expression,
|
||||||
|
public forceUpdate: o.Expression) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LegacyNameResolver {
|
||||||
|
callPipe(name: string, input: o.Expression, args: o.Expression[]): o.Expression;
|
||||||
|
getLocal(name: string): o.Expression;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts the given expression AST into an executable output AST, assuming the expression is
|
||||||
|
* used in a property binding.
|
||||||
|
*/
|
||||||
|
export function legacyConvertPropertyBinding(
|
||||||
|
builder: ClassBuilder, nameResolver: LegacyNameResolver, implicitReceiver: o.Expression,
|
||||||
|
expression: cdAst.AST, bindingId: string): LegacyConvertPropertyBindingResult {
|
||||||
|
if (!nameResolver) {
|
||||||
|
nameResolver = new LegacyDefaultNameResolver();
|
||||||
|
}
|
||||||
|
let needsValueUnwrapper = false;
|
||||||
|
const expressionWithoutBuiltins = convertBuiltins(
|
||||||
|
{
|
||||||
|
createLiteralArrayConverter: (argCount: number) => {
|
||||||
|
return (args: o.Expression[]) => legacyCreateCachedLiteralArray(builder, args);
|
||||||
|
},
|
||||||
|
createLiteralMapConverter: (keys: string[]) => {
|
||||||
|
return (args: o.Expression[]) => legacyCreateCachedLiteralMap(
|
||||||
|
builder, <[string, o.Expression][]>keys.map((key, i) => [key, args[i]]));
|
||||||
|
},
|
||||||
|
createPipeConverter: (name: string) => {
|
||||||
|
needsValueUnwrapper = true;
|
||||||
|
return (args: o.Expression[]) => VAL_UNWRAPPER_VAR.callMethod(
|
||||||
|
'unwrap', [nameResolver.callPipe(name, args[0], args.slice(1))]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
expression);
|
||||||
|
|
||||||
|
const {stmts, currValExpr} =
|
||||||
|
convertPropertyBinding(nameResolver, implicitReceiver, expressionWithoutBuiltins, bindingId);
|
||||||
|
let forceUpdate: o.Expression = null;
|
||||||
|
if (needsValueUnwrapper) {
|
||||||
|
const initValueUnwrapperStmt = VAL_UNWRAPPER_VAR.callMethod('reset', []).toStmt();
|
||||||
|
stmts.unshift(initValueUnwrapperStmt);
|
||||||
|
forceUpdate = VAL_UNWRAPPER_VAR.prop('hasWrappedValue');
|
||||||
|
}
|
||||||
|
return new LegacyConvertPropertyBindingResult(stmts, currValExpr, forceUpdate);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates variables that are shared by multiple calls to `convertActionBinding` /
|
* Creates variables that are shared by multiple calls to `convertActionBinding` /
|
||||||
* `convertPropertyBinding`
|
* `convertPropertyBinding`
|
||||||
*/
|
*/
|
||||||
export function createSharedBindingVariablesIfNeeded(stmts: o.Statement[]): o.Statement[] {
|
export function legacyCreateSharedBindingVariablesIfNeeded(stmts: o.Statement[]): o.Statement[] {
|
||||||
const unwrapperStmts: o.Statement[] = [];
|
const unwrapperStmts: o.Statement[] = [];
|
||||||
const readVars = o.findReadVarNames(stmts);
|
const readVars = o.findReadVarNames(stmts);
|
||||||
if (readVars.has(VAL_UNWRAPPER_VAR.name)) {
|
if (readVars.has(VAL_UNWRAPPER_VAR.name)) {
|
||||||
|
@ -122,6 +183,11 @@ export function createSharedBindingVariablesIfNeeded(stmts: o.Statement[]): o.St
|
||||||
return unwrapperStmts;
|
return unwrapperStmts;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function convertBuiltins(converterFactory: BuiltinConverterFactory, ast: cdAst.AST): cdAst.AST {
|
||||||
|
const visitor = new _BuiltinAstConverter(converterFactory);
|
||||||
|
return ast.visit(visitor);
|
||||||
|
}
|
||||||
|
|
||||||
function temporaryName(bindingId: string, temporaryNumber: number): string {
|
function temporaryName(bindingId: string, temporaryNumber: number): string {
|
||||||
return `tmp_${bindingId}_${temporaryNumber}`;
|
return `tmp_${bindingId}_${temporaryNumber}`;
|
||||||
}
|
}
|
||||||
|
@ -162,17 +228,34 @@ function convertToStatementIfNeeded(mode: _Mode, expr: o.Expression): o.Expressi
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _BuiltinAstConverter extends cdAst.AstTransformer {
|
||||||
|
constructor(private _converterFactory: BuiltinConverterFactory) { super(); }
|
||||||
|
visitPipe(ast: cdAst.BindingPipe, context: any): any {
|
||||||
|
const args = [ast.exp, ...ast.args].map(ast => ast.visit(this, context));
|
||||||
|
return new BuiltinFunctionCall(
|
||||||
|
ast.span, args, this._converterFactory.createPipeConverter(ast.name, args.length));
|
||||||
|
}
|
||||||
|
visitLiteralArray(ast: cdAst.LiteralArray, context: any): any {
|
||||||
|
const args = ast.expressions.map(ast => ast.visit(this, context));
|
||||||
|
return new BuiltinFunctionCall(
|
||||||
|
ast.span, args, this._converterFactory.createLiteralArrayConverter(ast.expressions.length));
|
||||||
|
}
|
||||||
|
visitLiteralMap(ast: cdAst.LiteralMap, context: any): any {
|
||||||
|
const args = ast.values.map(ast => ast.visit(this, context));
|
||||||
|
return new BuiltinFunctionCall(
|
||||||
|
ast.span, args, this._converterFactory.createLiteralMapConverter(ast.keys));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class _AstToIrVisitor implements cdAst.AstVisitor {
|
class _AstToIrVisitor implements cdAst.AstVisitor {
|
||||||
private _nodeMap = new Map<cdAst.AST, cdAst.AST>();
|
private _nodeMap = new Map<cdAst.AST, cdAst.AST>();
|
||||||
private _resultMap = new Map<cdAst.AST, o.Expression>();
|
private _resultMap = new Map<cdAst.AST, o.Expression>();
|
||||||
private _currentTemporary: number = 0;
|
private _currentTemporary: number = 0;
|
||||||
public needsValueUnwrapper: boolean = false;
|
|
||||||
public temporaryCount: number = 0;
|
public temporaryCount: number = 0;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private _builder: ClassBuilder, private _nameResolver: NameResolver,
|
private _localResolver: LocalResolver, private _implicitReceiver: o.Expression,
|
||||||
private _implicitReceiver: o.Expression, private _valueUnwrapper: o.ReadVarExpr,
|
private bindingId: string) {}
|
||||||
private bindingId: string, private isAction: boolean) {}
|
|
||||||
|
|
||||||
visitBinary(ast: cdAst.Binary, mode: _Mode): any {
|
visitBinary(ast: cdAst.Binary, mode: _Mode): any {
|
||||||
let op: o.BinaryOperator;
|
let op: o.BinaryOperator;
|
||||||
|
@ -246,20 +329,19 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
|
||||||
}
|
}
|
||||||
|
|
||||||
visitPipe(ast: cdAst.BindingPipe, mode: _Mode): any {
|
visitPipe(ast: cdAst.BindingPipe, mode: _Mode): any {
|
||||||
const input = this.visit(ast.exp, _Mode.Expression);
|
throw new Error(
|
||||||
const args = this.visitAll(ast.args, _Mode.Expression);
|
`Illegal state: Pipes should have been converted into functions. Pipe: ${ast.name}`);
|
||||||
const value = this._nameResolver.callPipe(ast.name, input, args);
|
|
||||||
if (!value) {
|
|
||||||
throw new Error(`Illegal state: Pipe ${ast.name} is not allowed here!`);
|
|
||||||
}
|
|
||||||
this.needsValueUnwrapper = true;
|
|
||||||
return convertToStatementIfNeeded(mode, this._valueUnwrapper.callMethod('unwrap', [value]));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
visitFunctionCall(ast: cdAst.FunctionCall, mode: _Mode): any {
|
visitFunctionCall(ast: cdAst.FunctionCall, mode: _Mode): any {
|
||||||
return convertToStatementIfNeeded(
|
const convertedArgs = this.visitAll(ast.args, _Mode.Expression);
|
||||||
mode,
|
let fnResult: o.Expression;
|
||||||
this.visit(ast.target, _Mode.Expression).callFn(this.visitAll(ast.args, _Mode.Expression)));
|
if (ast instanceof BuiltinFunctionCall) {
|
||||||
|
fnResult = ast.converter(convertedArgs);
|
||||||
|
} else {
|
||||||
|
fnResult = this.visit(ast.target, _Mode.Expression).callFn(convertedArgs);
|
||||||
|
}
|
||||||
|
return convertToStatementIfNeeded(mode, fnResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
visitImplicitReceiver(ast: cdAst.ImplicitReceiver, mode: _Mode): any {
|
visitImplicitReceiver(ast: cdAst.ImplicitReceiver, mode: _Mode): any {
|
||||||
|
@ -301,32 +383,18 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
|
||||||
}
|
}
|
||||||
|
|
||||||
visitLiteralArray(ast: cdAst.LiteralArray, mode: _Mode): any {
|
visitLiteralArray(ast: cdAst.LiteralArray, mode: _Mode): any {
|
||||||
const parts = this.visitAll(ast.expressions, mode);
|
throw new Error(`Illegal State: literal arrays should have been converted into functions`);
|
||||||
const literalArr =
|
|
||||||
this.isAction ? o.literalArr(parts) : createCachedLiteralArray(this._builder, parts);
|
|
||||||
return convertToStatementIfNeeded(mode, literalArr);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
visitLiteralMap(ast: cdAst.LiteralMap, mode: _Mode): any {
|
visitLiteralMap(ast: cdAst.LiteralMap, mode: _Mode): any {
|
||||||
const parts: any[] = [];
|
throw new Error(`Illegal State: literal maps should have been converted into functions`);
|
||||||
for (let i = 0; i < ast.keys.length; i++) {
|
|
||||||
parts.push([ast.keys[i], this.visit(ast.values[i], _Mode.Expression)]);
|
|
||||||
}
|
|
||||||
const literalMap =
|
|
||||||
this.isAction ? o.literalMap(parts) : createCachedLiteralMap(this._builder, parts);
|
|
||||||
return convertToStatementIfNeeded(mode, literalMap);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
visitLiteralPrimitive(ast: cdAst.LiteralPrimitive, mode: _Mode): any {
|
visitLiteralPrimitive(ast: cdAst.LiteralPrimitive, mode: _Mode): any {
|
||||||
return convertToStatementIfNeeded(mode, o.literal(ast.value));
|
return convertToStatementIfNeeded(mode, o.literal(ast.value));
|
||||||
}
|
}
|
||||||
|
|
||||||
private _getLocal(name: string): o.Expression {
|
private _getLocal(name: string): o.Expression { return this._localResolver.getLocal(name); }
|
||||||
if (this.isAction && name == EventHandlerVars.event.name) {
|
|
||||||
return EventHandlerVars.event;
|
|
||||||
}
|
|
||||||
return this._nameResolver.getLocal(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
visitMethodCall(ast: cdAst.MethodCall, mode: _Mode): any {
|
visitMethodCall(ast: cdAst.MethodCall, mode: _Mode): any {
|
||||||
const leftMostSafe = this.leftMostSafeNode(ast);
|
const leftMostSafe = this.leftMostSafeNode(ast);
|
||||||
|
@ -581,7 +649,8 @@ function flattenStatements(arg: any, output: o.Statement[]) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function createCachedLiteralArray(builder: ClassBuilder, values: o.Expression[]): o.Expression {
|
function legacyCreateCachedLiteralArray(
|
||||||
|
builder: ClassBuilder, values: o.Expression[]): o.Expression {
|
||||||
if (values.length === 0) {
|
if (values.length === 0) {
|
||||||
return o.importExpr(createIdentifier(Identifiers.EMPTY_ARRAY));
|
return o.importExpr(createIdentifier(Identifiers.EMPTY_ARRAY));
|
||||||
}
|
}
|
||||||
|
@ -601,7 +670,7 @@ function createCachedLiteralArray(builder: ClassBuilder, values: o.Expression[])
|
||||||
return proxyExpr.callFn(values);
|
return proxyExpr.callFn(values);
|
||||||
}
|
}
|
||||||
|
|
||||||
function createCachedLiteralMap(
|
function legacyCreateCachedLiteralMap(
|
||||||
builder: ClassBuilder, entries: [string, o.Expression][]): o.Expression {
|
builder: ClassBuilder, entries: [string, o.Expression][]): o.Expression {
|
||||||
if (entries.length === 0) {
|
if (entries.length === 0) {
|
||||||
return o.importExpr(createIdentifier(Identifiers.EMPTY_MAP));
|
return o.importExpr(createIdentifier(Identifiers.EMPTY_MAP));
|
||||||
|
@ -624,10 +693,23 @@ function createCachedLiteralMap(
|
||||||
return proxyExpr.callFn(values);
|
return proxyExpr.callFn(values);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class DefaultLocalResolver implements LocalResolver {
|
||||||
|
getLocal(name: string): o.Expression {
|
||||||
|
if (name === EventHandlerVars.event.name) {
|
||||||
|
return EventHandlerVars.event;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class DefaultNameResolver implements NameResolver {
|
class LegacyDefaultNameResolver implements LegacyNameResolver {
|
||||||
callPipe(name: string, input: o.Expression, args: o.Expression[]): o.Expression { return null; }
|
callPipe(name: string, input: o.Expression, args: o.Expression[]): o.Expression { return null; }
|
||||||
getLocal(name: string): o.Expression { return null; }
|
getLocal(name: string): o.Expression {
|
||||||
|
if (name === EventHandlerVars.event.name) {
|
||||||
|
return EventHandlerVars.event;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function createCurrValueExpr(bindingId: string): o.ReadVarExpr {
|
function createCurrValueExpr(bindingId: string): o.ReadVarExpr {
|
||||||
|
@ -646,3 +728,9 @@ function convertStmtIntoExpression(stmt: o.Statement): o.Expression {
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class BuiltinFunctionCall extends cdAst.FunctionCall {
|
||||||
|
constructor(span: cdAst.ParseSpan, public args: cdAst.AST[], public converter: BuiltinConverter) {
|
||||||
|
super(span, null, args);
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,12 +13,12 @@ import {EMPTY_STATE as EMPTY_ANIMATION_STATE} from '../private_import_core';
|
||||||
import {BoundElementPropertyAst, BoundEventAst, PropertyBindingType} from '../template_parser/template_ast';
|
import {BoundElementPropertyAst, BoundEventAst, PropertyBindingType} from '../template_parser/template_ast';
|
||||||
|
|
||||||
import {isFirstViewCheck} from './binding_util';
|
import {isFirstViewCheck} from './binding_util';
|
||||||
import {ConvertPropertyBindingResult} from './expression_converter';
|
import {LegacyConvertPropertyBindingResult} from './expression_converter';
|
||||||
import {createEnumExpression} from './identifier_util';
|
import {createEnumExpression} from './identifier_util';
|
||||||
|
|
||||||
export function createCheckRenderBindingStmt(
|
export function createCheckRenderBindingStmt(
|
||||||
view: o.Expression, renderElement: o.Expression, boundProp: BoundElementPropertyAst,
|
view: o.Expression, renderElement: o.Expression, boundProp: BoundElementPropertyAst,
|
||||||
oldValue: o.ReadPropExpr, evalResult: ConvertPropertyBindingResult,
|
oldValue: o.ReadPropExpr, evalResult: LegacyConvertPropertyBindingResult,
|
||||||
securityContextExpression?: o.Expression): o.Statement[] {
|
securityContextExpression?: o.Expression): o.Statement[] {
|
||||||
const checkStmts: o.Statement[] = [...evalResult.stmts];
|
const checkStmts: o.Statement[] = [...evalResult.stmts];
|
||||||
const securityContext = calcSecurityContext(boundProp, securityContextExpression);
|
const securityContext = calcSecurityContext(boundProp, securityContextExpression);
|
||||||
|
@ -84,7 +84,7 @@ function calcSecurityContext(
|
||||||
export function createCheckAnimationBindingStmts(
|
export function createCheckAnimationBindingStmts(
|
||||||
view: o.Expression, componentView: o.Expression, boundProp: BoundElementPropertyAst,
|
view: o.Expression, componentView: o.Expression, boundProp: BoundElementPropertyAst,
|
||||||
boundOutputs: BoundEventAst[], eventListener: o.Expression, renderElement: o.Expression,
|
boundOutputs: BoundEventAst[], eventListener: o.Expression, renderElement: o.Expression,
|
||||||
oldValue: o.ReadPropExpr, evalResult: ConvertPropertyBindingResult) {
|
oldValue: o.ReadPropExpr, evalResult: LegacyConvertPropertyBindingResult) {
|
||||||
const detachStmts: o.Statement[] = [];
|
const detachStmts: o.Statement[] = [];
|
||||||
const updateStmts: o.Statement[] = [];
|
const updateStmts: o.Statement[] = [];
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
|
|
||||||
import {CompileDirectiveMetadata, CompileDirectiveSummary, CompileIdentifierMetadata, dirWrapperClassName, identifierModuleUrl, identifierName} from './compile_metadata';
|
import {CompileDirectiveMetadata, CompileDirectiveSummary, CompileIdentifierMetadata, dirWrapperClassName, identifierModuleUrl, identifierName} from './compile_metadata';
|
||||||
import {createCheckBindingField, isFirstViewCheck} from './compiler_util/binding_util';
|
import {createCheckBindingField, isFirstViewCheck} from './compiler_util/binding_util';
|
||||||
import {EventHandlerVars, convertActionBinding, convertPropertyBinding} from './compiler_util/expression_converter';
|
import {EventHandlerVars, convertActionBinding, legacyConvertPropertyBinding} from './compiler_util/expression_converter';
|
||||||
import {createCheckAnimationBindingStmts, createCheckRenderBindingStmt} from './compiler_util/render_util';
|
import {createCheckAnimationBindingStmts, createCheckRenderBindingStmt} from './compiler_util/render_util';
|
||||||
import {CompilerConfig} from './config';
|
import {CompilerConfig} from './config';
|
||||||
import {Parser} from './expression_parser/parser';
|
import {Parser} from './expression_parser/parser';
|
||||||
|
@ -253,7 +253,7 @@ function addCheckHostMethod(
|
||||||
];
|
];
|
||||||
hostProps.forEach((hostProp, hostPropIdx) => {
|
hostProps.forEach((hostProp, hostPropIdx) => {
|
||||||
const field = createCheckBindingField(builder);
|
const field = createCheckBindingField(builder);
|
||||||
const evalResult = convertPropertyBinding(
|
const evalResult = legacyConvertPropertyBinding(
|
||||||
builder, null, o.THIS_EXPR.prop(CONTEXT_FIELD_NAME), hostProp.value, field.bindingId);
|
builder, null, o.THIS_EXPR.prop(CONTEXT_FIELD_NAME), hostProp.value, field.bindingId);
|
||||||
if (!evalResult) {
|
if (!evalResult) {
|
||||||
return;
|
return;
|
||||||
|
@ -285,8 +285,7 @@ function addHandleEventMethod(hostListeners: BoundEventAst[], builder: Directive
|
||||||
const actionStmts: o.Statement[] = [resultVar.set(o.literal(true)).toDeclStmt(o.BOOL_TYPE)];
|
const actionStmts: o.Statement[] = [resultVar.set(o.literal(true)).toDeclStmt(o.BOOL_TYPE)];
|
||||||
hostListeners.forEach((hostListener, eventIdx) => {
|
hostListeners.forEach((hostListener, eventIdx) => {
|
||||||
const evalResult = convertActionBinding(
|
const evalResult = convertActionBinding(
|
||||||
builder, null, o.THIS_EXPR.prop(CONTEXT_FIELD_NAME), hostListener.handler,
|
null, o.THIS_EXPR.prop(CONTEXT_FIELD_NAME), hostListener.handler, `sub_${eventIdx}`);
|
||||||
`sub_${eventIdx}`);
|
|
||||||
const trueStmts = evalResult.stmts;
|
const trueStmts = evalResult.stmts;
|
||||||
if (evalResult.allowDefault) {
|
if (evalResult.allowDefault) {
|
||||||
trueStmts.push(resultVar.set(evalResult.allowDefault.and(resultVar)).toStmt());
|
trueStmts.push(resultVar.set(evalResult.allowDefault.and(resultVar)).toStmt());
|
||||||
|
|
|
@ -400,11 +400,36 @@ export class Identifiers {
|
||||||
moduleUrl: VIEW_ENGINE_MODULE_URL,
|
moduleUrl: VIEW_ENGINE_MODULE_URL,
|
||||||
runtime: viewEngine.queryDef
|
runtime: viewEngine.queryDef
|
||||||
};
|
};
|
||||||
|
static pureArrayDef: IdentifierSpec = {
|
||||||
|
name: 'pureArrayDef',
|
||||||
|
moduleUrl: VIEW_ENGINE_MODULE_URL,
|
||||||
|
runtime: viewEngine.pureArrayDef
|
||||||
|
};
|
||||||
|
static pureObjectDef: IdentifierSpec = {
|
||||||
|
name: 'pureObjectDef',
|
||||||
|
moduleUrl: VIEW_ENGINE_MODULE_URL,
|
||||||
|
runtime: viewEngine.pureObjectDef
|
||||||
|
};
|
||||||
|
static purePipeDef: IdentifierSpec = {
|
||||||
|
name: 'purePipeDef',
|
||||||
|
moduleUrl: VIEW_ENGINE_MODULE_URL,
|
||||||
|
runtime: viewEngine.purePipeDef
|
||||||
|
};
|
||||||
|
static pipeDef: IdentifierSpec = {
|
||||||
|
name: 'pipeDef',
|
||||||
|
moduleUrl: VIEW_ENGINE_MODULE_URL,
|
||||||
|
runtime: viewEngine.pipeDef
|
||||||
|
};
|
||||||
static nodeValue: IdentifierSpec = {
|
static nodeValue: IdentifierSpec = {
|
||||||
name: 'nodeValue',
|
name: 'nodeValue',
|
||||||
moduleUrl: VIEW_ENGINE_MODULE_URL,
|
moduleUrl: VIEW_ENGINE_MODULE_URL,
|
||||||
runtime: viewEngine.nodeValue
|
runtime: viewEngine.nodeValue
|
||||||
};
|
};
|
||||||
|
static unwrapValue: IdentifierSpec = {
|
||||||
|
name: 'unwrapValue',
|
||||||
|
moduleUrl: VIEW_ENGINE_MODULE_URL,
|
||||||
|
runtime: viewEngine.unwrapValue
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function assetUrl(pkg: string, path: string = null, type: string = 'src'): string {
|
export function assetUrl(pkg: string, path: string = null, type: string = 'src'): string {
|
||||||
|
|
|
@ -278,14 +278,14 @@ export class JitCompiler implements Compiler {
|
||||||
template.directives.map(dir => this._metadataResolver.getDirectiveSummary(dir.reference));
|
template.directives.map(dir => this._metadataResolver.getDirectiveSummary(dir.reference));
|
||||||
const pipes = template.ngModule.transitiveModule.pipes.map(
|
const pipes = template.ngModule.transitiveModule.pipes.map(
|
||||||
pipe => this._metadataResolver.getPipeSummary(pipe.reference));
|
pipe => this._metadataResolver.getPipeSummary(pipe.reference));
|
||||||
const parsedTemplate = this._templateParser.parse(
|
const {template: parsedTemplate, pipes: usedPipes} = this._templateParser.parse(
|
||||||
compMeta, compMeta.template.template, directives, pipes, template.ngModule.schemas,
|
compMeta, compMeta.template.template, directives, pipes, template.ngModule.schemas,
|
||||||
identifierName(compMeta.type));
|
identifierName(compMeta.type));
|
||||||
const compiledAnimations =
|
const compiledAnimations =
|
||||||
this._animationCompiler.compile(identifierName(compMeta.type), parsedAnimations);
|
this._animationCompiler.compile(identifierName(compMeta.type), parsedAnimations);
|
||||||
const compileResult = this._viewCompiler.compileComponent(
|
const compileResult = this._viewCompiler.compileComponent(
|
||||||
compMeta, parsedTemplate, ir.variable(stylesCompileResult.componentStylesheet.stylesVar),
|
compMeta, parsedTemplate, ir.variable(stylesCompileResult.componentStylesheet.stylesVar),
|
||||||
pipes, compiledAnimations);
|
usedPipes, compiledAnimations);
|
||||||
const statements = stylesCompileResult.componentStylesheet.statements
|
const statements = stylesCompileResult.componentStylesheet.statements
|
||||||
.concat(...compiledAnimations.map(ca => ca.statements))
|
.concat(...compiledAnimations.map(ca => ca.statements))
|
||||||
.concat(compileResult.statements);
|
.concat(compileResult.statements);
|
||||||
|
|
|
@ -51,6 +51,7 @@ export class BoundProperty {
|
||||||
*/
|
*/
|
||||||
export class BindingParser {
|
export class BindingParser {
|
||||||
pipesByName: Map<string, CompilePipeSummary> = new Map();
|
pipesByName: Map<string, CompilePipeSummary> = new Map();
|
||||||
|
private _usedPipes: Map<string, CompilePipeSummary> = new Map();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private _exprParser: Parser, private _interpolationConfig: InterpolationConfig,
|
private _exprParser: Parser, private _interpolationConfig: InterpolationConfig,
|
||||||
|
@ -59,6 +60,8 @@ export class BindingParser {
|
||||||
pipes.forEach(pipe => this.pipesByName.set(pipe.name, pipe));
|
pipes.forEach(pipe => this.pipesByName.set(pipe.name, pipe));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getUsedPipes(): CompilePipeSummary[] { return Array.from(this._usedPipes.values()); }
|
||||||
|
|
||||||
createDirectiveHostPropertyAsts(dirMeta: CompileDirectiveSummary, sourceSpan: ParseSourceSpan):
|
createDirectiveHostPropertyAsts(dirMeta: CompileDirectiveSummary, sourceSpan: ParseSourceSpan):
|
||||||
BoundElementPropertyAst[] {
|
BoundElementPropertyAst[] {
|
||||||
if (dirMeta.hostProperties) {
|
if (dirMeta.hostProperties) {
|
||||||
|
@ -377,11 +380,14 @@ export class BindingParser {
|
||||||
const collector = new PipeCollector();
|
const collector = new PipeCollector();
|
||||||
ast.visit(collector);
|
ast.visit(collector);
|
||||||
collector.pipes.forEach((ast, pipeName) => {
|
collector.pipes.forEach((ast, pipeName) => {
|
||||||
if (!this.pipesByName.has(pipeName)) {
|
const pipeMeta = this.pipesByName.get(pipeName);
|
||||||
|
if (!pipeMeta) {
|
||||||
this._reportError(
|
this._reportError(
|
||||||
`The pipe '${pipeName}' could not be found`,
|
`The pipe '${pipeName}' could not be found`,
|
||||||
new ParseSourceSpan(
|
new ParseSourceSpan(
|
||||||
sourceSpan.start.moveBy(ast.span.start), sourceSpan.start.moveBy(ast.span.end)));
|
sourceSpan.start.moveBy(ast.span.start), sourceSpan.start.moveBy(ast.span.end)));
|
||||||
|
} else {
|
||||||
|
this._usedPipes.set(pipeName, pipeMeta);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,9 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {Inject, InjectionToken, Optional, SchemaMetadata} from '@angular/core';
|
import {Inject, InjectionToken, Optional, SchemaMetadata} from '@angular/core';
|
||||||
|
|
||||||
import {CompileDirectiveMetadata, CompileDirectiveSummary, CompilePipeSummary, CompileTemplateSummary, CompileTokenMetadata, CompileTypeMetadata, identifierName} from '../compile_metadata';
|
import {CompileDirectiveMetadata, CompileDirectiveSummary, CompilePipeSummary, CompileTemplateSummary, CompileTokenMetadata, CompileTypeMetadata, identifierName} from '../compile_metadata';
|
||||||
|
import {AST, ASTWithSource, EmptyExpr} from '../expression_parser/ast';
|
||||||
import {Parser} from '../expression_parser/parser';
|
import {Parser} from '../expression_parser/parser';
|
||||||
import {isPresent} from '../facade/lang';
|
import {isPresent} from '../facade/lang';
|
||||||
import {I18NHtmlParser} from '../i18n/i18n_html_parser';
|
import {I18NHtmlParser} from '../i18n/i18n_html_parser';
|
||||||
|
@ -25,10 +27,12 @@ import {ElementSchemaRegistry} from '../schema/element_schema_registry';
|
||||||
import {CssSelector, SelectorMatcher} from '../selector';
|
import {CssSelector, SelectorMatcher} from '../selector';
|
||||||
import {isStyleUrlResolvable} from '../style_url_resolver';
|
import {isStyleUrlResolvable} from '../style_url_resolver';
|
||||||
import {syntaxError} from '../util';
|
import {syntaxError} from '../util';
|
||||||
|
|
||||||
import {BindingParser, BoundProperty} from './binding_parser';
|
import {BindingParser, BoundProperty} from './binding_parser';
|
||||||
import {AttrAst, BoundDirectivePropertyAst, BoundElementPropertyAst, BoundEventAst, BoundTextAst, DirectiveAst, ElementAst, EmbeddedTemplateAst, NgContentAst, PropertyBindingType, ReferenceAst, TemplateAst, TemplateAstVisitor, TextAst, VariableAst, templateVisitAll} from './template_ast';
|
import {AttrAst, BoundDirectivePropertyAst, BoundElementPropertyAst, BoundEventAst, BoundTextAst, DirectiveAst, ElementAst, EmbeddedTemplateAst, NgContentAst, PropertyBindingType, ReferenceAst, TemplateAst, TemplateAstVisitor, TextAst, VariableAst, templateVisitAll} from './template_ast';
|
||||||
import {PreparsedElementType, preparseElement} from './template_preparser';
|
import {PreparsedElementType, preparseElement} from './template_preparser';
|
||||||
|
|
||||||
|
|
||||||
// Group 1 = "bind-"
|
// Group 1 = "bind-"
|
||||||
// Group 2 = "let-"
|
// Group 2 = "let-"
|
||||||
// Group 3 = "ref-/#"
|
// Group 3 = "ref-/#"
|
||||||
|
@ -76,7 +80,9 @@ export class TemplateParseError extends ParseError {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TemplateParseResult {
|
export class TemplateParseResult {
|
||||||
constructor(public templateAst?: TemplateAst[], public errors?: ParseError[]) {}
|
constructor(
|
||||||
|
public templateAst?: TemplateAst[], public usedPipes?: CompilePipeSummary[],
|
||||||
|
public errors?: ParseError[]) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
@CompilerInjectable()
|
@CompilerInjectable()
|
||||||
|
@ -88,7 +94,8 @@ export class TemplateParser {
|
||||||
|
|
||||||
parse(
|
parse(
|
||||||
component: CompileDirectiveMetadata, template: string, directives: CompileDirectiveSummary[],
|
component: CompileDirectiveMetadata, template: string, directives: CompileDirectiveSummary[],
|
||||||
pipes: CompilePipeSummary[], schemas: SchemaMetadata[], templateUrl: string): TemplateAst[] {
|
pipes: CompilePipeSummary[], schemas: SchemaMetadata[],
|
||||||
|
templateUrl: string): {template: TemplateAst[], pipes: CompilePipeSummary[]} {
|
||||||
const result = this.tryParse(component, template, directives, pipes, schemas, templateUrl);
|
const result = this.tryParse(component, template, directives, pipes, schemas, templateUrl);
|
||||||
const warnings = result.errors.filter(error => error.level === ParseErrorLevel.WARNING);
|
const warnings = result.errors.filter(error => error.level === ParseErrorLevel.WARNING);
|
||||||
const errors = result.errors.filter(error => error.level === ParseErrorLevel.FATAL);
|
const errors = result.errors.filter(error => error.level === ParseErrorLevel.FATAL);
|
||||||
|
@ -102,7 +109,7 @@ export class TemplateParser {
|
||||||
throw syntaxError(`Template parse errors:\n${errorString}`);
|
throw syntaxError(`Template parse errors:\n${errorString}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return result.templateAst;
|
return {template: result.templateAst, pipes: result.usedPipes};
|
||||||
}
|
}
|
||||||
|
|
||||||
tryParse(
|
tryParse(
|
||||||
|
@ -121,6 +128,7 @@ export class TemplateParser {
|
||||||
templateUrl: string): TemplateParseResult {
|
templateUrl: string): TemplateParseResult {
|
||||||
let result: TemplateAst[];
|
let result: TemplateAst[];
|
||||||
const errors = htmlAstWithErrors.errors;
|
const errors = htmlAstWithErrors.errors;
|
||||||
|
const usedPipes: CompilePipeSummary[] = [];
|
||||||
if (htmlAstWithErrors.rootNodes.length > 0) {
|
if (htmlAstWithErrors.rootNodes.length > 0) {
|
||||||
const uniqDirectives = removeSummaryDuplicates(directives);
|
const uniqDirectives = removeSummaryDuplicates(directives);
|
||||||
const uniqPipes = removeSummaryDuplicates(pipes);
|
const uniqPipes = removeSummaryDuplicates(pipes);
|
||||||
|
@ -140,13 +148,14 @@ export class TemplateParser {
|
||||||
errors);
|
errors);
|
||||||
result = html.visitAll(parseVisitor, htmlAstWithErrors.rootNodes, EMPTY_ELEMENT_CONTEXT);
|
result = html.visitAll(parseVisitor, htmlAstWithErrors.rootNodes, EMPTY_ELEMENT_CONTEXT);
|
||||||
errors.push(...providerViewContext.errors);
|
errors.push(...providerViewContext.errors);
|
||||||
|
usedPipes.push(...bindingParser.getUsedPipes());
|
||||||
} else {
|
} else {
|
||||||
result = [];
|
result = [];
|
||||||
}
|
}
|
||||||
this._assertNoReferenceDuplicationOnTemplate(result, errors);
|
this._assertNoReferenceDuplicationOnTemplate(result, errors);
|
||||||
|
|
||||||
if (errors.length > 0) {
|
if (errors.length > 0) {
|
||||||
return new TemplateParseResult(result, errors);
|
return new TemplateParseResult(result, usedPipes, errors);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.transforms) {
|
if (this.transforms) {
|
||||||
|
@ -154,7 +163,7 @@ export class TemplateParser {
|
||||||
(transform: TemplateAstVisitor) => { result = templateVisitAll(transform, result); });
|
(transform: TemplateAstVisitor) => { result = templateVisitAll(transform, result); });
|
||||||
}
|
}
|
||||||
|
|
||||||
return new TemplateParseResult(result, errors);
|
return new TemplateParseResult(result, usedPipes, errors);
|
||||||
}
|
}
|
||||||
|
|
||||||
expandHtml(htmlAstWithErrors: ParseTreeResult, forced: boolean = false): ParseTreeResult {
|
expandHtml(htmlAstWithErrors: ParseTreeResult, forced: boolean = false): ParseTreeResult {
|
||||||
|
@ -303,11 +312,12 @@ class TemplateParseVisitor implements html.Visitor {
|
||||||
const {directives: directiveMetas, matchElement} =
|
const {directives: directiveMetas, matchElement} =
|
||||||
this._parseDirectives(this.selectorMatcher, elementCssSelector);
|
this._parseDirectives(this.selectorMatcher, elementCssSelector);
|
||||||
const references: ReferenceAst[] = [];
|
const references: ReferenceAst[] = [];
|
||||||
|
const boundDirectivePropNames = new Set<string>();
|
||||||
const directiveAsts = this._createDirectiveAsts(
|
const directiveAsts = this._createDirectiveAsts(
|
||||||
isTemplateElement, element.name, directiveMetas, elementOrDirectiveProps,
|
isTemplateElement, element.name, directiveMetas, elementOrDirectiveProps,
|
||||||
elementOrDirectiveRefs, element.sourceSpan, references);
|
elementOrDirectiveRefs, element.sourceSpan, references, boundDirectivePropNames);
|
||||||
const elementProps: BoundElementPropertyAst[] =
|
const elementProps: BoundElementPropertyAst[] = this._createElementPropertyAsts(
|
||||||
this._createElementPropertyAsts(element.name, elementOrDirectiveProps, directiveAsts);
|
element.name, elementOrDirectiveProps, boundDirectivePropNames);
|
||||||
const isViewRoot = parent.isTemplateElement || hasInlineTemplates;
|
const isViewRoot = parent.isTemplateElement || hasInlineTemplates;
|
||||||
|
|
||||||
const providerContext = new ProviderElementContext(
|
const providerContext = new ProviderElementContext(
|
||||||
|
@ -372,11 +382,12 @@ class TemplateParseVisitor implements html.Visitor {
|
||||||
createElementCssSelector(TEMPLATE_ELEMENT, templateMatchableAttrs);
|
createElementCssSelector(TEMPLATE_ELEMENT, templateMatchableAttrs);
|
||||||
const {directives: templateDirectiveMetas} =
|
const {directives: templateDirectiveMetas} =
|
||||||
this._parseDirectives(this.selectorMatcher, templateCssSelector);
|
this._parseDirectives(this.selectorMatcher, templateCssSelector);
|
||||||
|
const templateBoundDirectivePropNames = new Set<string>();
|
||||||
const templateDirectiveAsts = this._createDirectiveAsts(
|
const templateDirectiveAsts = this._createDirectiveAsts(
|
||||||
true, element.name, templateDirectiveMetas, templateElementOrDirectiveProps, [],
|
true, element.name, templateDirectiveMetas, templateElementOrDirectiveProps, [],
|
||||||
element.sourceSpan, []);
|
element.sourceSpan, [], templateBoundDirectivePropNames);
|
||||||
const templateElementProps: BoundElementPropertyAst[] = this._createElementPropertyAsts(
|
const templateElementProps: BoundElementPropertyAst[] = this._createElementPropertyAsts(
|
||||||
element.name, templateElementOrDirectiveProps, templateDirectiveAsts);
|
element.name, templateElementOrDirectiveProps, templateBoundDirectivePropNames);
|
||||||
this._assertNoComponentsNorElementBindingsOnTemplate(
|
this._assertNoComponentsNorElementBindingsOnTemplate(
|
||||||
templateDirectiveAsts, templateElementProps, element.sourceSpan);
|
templateDirectiveAsts, templateElementProps, element.sourceSpan);
|
||||||
const templateProviderContext = new ProviderElementContext(
|
const templateProviderContext = new ProviderElementContext(
|
||||||
|
@ -544,7 +555,8 @@ class TemplateParseVisitor implements html.Visitor {
|
||||||
private _createDirectiveAsts(
|
private _createDirectiveAsts(
|
||||||
isTemplateElement: boolean, elementName: string, directives: CompileDirectiveSummary[],
|
isTemplateElement: boolean, elementName: string, directives: CompileDirectiveSummary[],
|
||||||
props: BoundProperty[], elementOrDirectiveRefs: ElementOrDirectiveRef[],
|
props: BoundProperty[], elementOrDirectiveRefs: ElementOrDirectiveRef[],
|
||||||
elementSourceSpan: ParseSourceSpan, targetReferences: ReferenceAst[]): DirectiveAst[] {
|
elementSourceSpan: ParseSourceSpan, targetReferences: ReferenceAst[],
|
||||||
|
targetBoundDirectivePropNames: Set<string>): DirectiveAst[] {
|
||||||
const matchedReferences = new Set<string>();
|
const matchedReferences = new Set<string>();
|
||||||
let component: CompileDirectiveSummary = null;
|
let component: CompileDirectiveSummary = null;
|
||||||
|
|
||||||
|
@ -557,13 +569,14 @@ class TemplateParseVisitor implements html.Visitor {
|
||||||
component = directive;
|
component = directive;
|
||||||
}
|
}
|
||||||
const directiveProperties: BoundDirectivePropertyAst[] = [];
|
const directiveProperties: BoundDirectivePropertyAst[] = [];
|
||||||
const hostProperties =
|
let hostProperties =
|
||||||
this._bindingParser.createDirectiveHostPropertyAsts(directive, sourceSpan);
|
this._bindingParser.createDirectiveHostPropertyAsts(directive, sourceSpan);
|
||||||
// Note: We need to check the host properties here as well,
|
// Note: We need to check the host properties here as well,
|
||||||
// as we don't know the element name in the DirectiveWrapperCompiler yet.
|
// as we don't know the element name in the DirectiveWrapperCompiler yet.
|
||||||
this._checkPropertiesInSchema(elementName, hostProperties);
|
hostProperties = this._checkPropertiesInSchema(elementName, hostProperties);
|
||||||
const hostEvents = this._bindingParser.createDirectiveHostEventAsts(directive, sourceSpan);
|
const hostEvents = this._bindingParser.createDirectiveHostEventAsts(directive, sourceSpan);
|
||||||
this._createDirectivePropertyAsts(directive.inputs, props, directiveProperties);
|
this._createDirectivePropertyAsts(
|
||||||
|
directive.inputs, props, directiveProperties, targetBoundDirectivePropNames);
|
||||||
elementOrDirectiveRefs.forEach((elOrDirRef) => {
|
elementOrDirectiveRefs.forEach((elOrDirRef) => {
|
||||||
if ((elOrDirRef.value.length === 0 && directive.isComponent) ||
|
if ((elOrDirRef.value.length === 0 && directive.isComponent) ||
|
||||||
(directive.exportAs == elOrDirRef.value)) {
|
(directive.exportAs == elOrDirRef.value)) {
|
||||||
|
@ -596,7 +609,8 @@ class TemplateParseVisitor implements html.Visitor {
|
||||||
|
|
||||||
private _createDirectivePropertyAsts(
|
private _createDirectivePropertyAsts(
|
||||||
directiveProperties: {[key: string]: string}, boundProps: BoundProperty[],
|
directiveProperties: {[key: string]: string}, boundProps: BoundProperty[],
|
||||||
targetBoundDirectiveProps: BoundDirectivePropertyAst[]) {
|
targetBoundDirectiveProps: BoundDirectivePropertyAst[],
|
||||||
|
targetBoundDirectivePropNames: Set<string>) {
|
||||||
if (directiveProperties) {
|
if (directiveProperties) {
|
||||||
const boundPropsByName = new Map<string, BoundProperty>();
|
const boundPropsByName = new Map<string, BoundProperty>();
|
||||||
boundProps.forEach(boundProp => {
|
boundProps.forEach(boundProp => {
|
||||||
|
@ -613,8 +627,11 @@ class TemplateParseVisitor implements html.Visitor {
|
||||||
|
|
||||||
// Bindings are optional, so this binding only needs to be set up if an expression is given.
|
// Bindings are optional, so this binding only needs to be set up if an expression is given.
|
||||||
if (boundProp) {
|
if (boundProp) {
|
||||||
targetBoundDirectiveProps.push(new BoundDirectivePropertyAst(
|
targetBoundDirectivePropNames.add(boundProp.name);
|
||||||
dirProp, boundProp.name, boundProp.expression, boundProp.sourceSpan));
|
if (!isEmptyExpression(boundProp.expression)) {
|
||||||
|
targetBoundDirectiveProps.push(new BoundDirectivePropertyAst(
|
||||||
|
dirProp, boundProp.name, boundProp.expression, boundProp.sourceSpan));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -622,23 +639,15 @@ class TemplateParseVisitor implements html.Visitor {
|
||||||
|
|
||||||
private _createElementPropertyAsts(
|
private _createElementPropertyAsts(
|
||||||
elementName: string, props: BoundProperty[],
|
elementName: string, props: BoundProperty[],
|
||||||
directives: DirectiveAst[]): BoundElementPropertyAst[] {
|
boundDirectivePropNames: Set<string>): BoundElementPropertyAst[] {
|
||||||
const boundElementProps: BoundElementPropertyAst[] = [];
|
const boundElementProps: BoundElementPropertyAst[] = [];
|
||||||
const boundDirectivePropsIndex = new Map<string, BoundDirectivePropertyAst>();
|
|
||||||
|
|
||||||
directives.forEach((directive: DirectiveAst) => {
|
|
||||||
directive.inputs.forEach((prop: BoundDirectivePropertyAst) => {
|
|
||||||
boundDirectivePropsIndex.set(prop.templateName, prop);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
props.forEach((prop: BoundProperty) => {
|
props.forEach((prop: BoundProperty) => {
|
||||||
if (!prop.isLiteral && !boundDirectivePropsIndex.get(prop.name)) {
|
if (!prop.isLiteral && !boundDirectivePropNames.has(prop.name)) {
|
||||||
boundElementProps.push(this._bindingParser.createElementPropertyAst(elementName, prop));
|
boundElementProps.push(this._bindingParser.createElementPropertyAst(elementName, prop));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this._checkPropertiesInSchema(elementName, boundElementProps);
|
return this._checkPropertiesInSchema(elementName, boundElementProps);
|
||||||
return boundElementProps;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _findComponentDirectives(directives: DirectiveAst[]): DirectiveAst[] {
|
private _findComponentDirectives(directives: DirectiveAst[]): DirectiveAst[] {
|
||||||
|
@ -723,8 +732,11 @@ class TemplateParseVisitor implements html.Visitor {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private _checkPropertiesInSchema(elementName: string, boundProps: BoundElementPropertyAst[]) {
|
private _checkPropertiesInSchema(elementName: string, boundProps: BoundElementPropertyAst[]):
|
||||||
boundProps.forEach((boundProp) => {
|
BoundElementPropertyAst[] {
|
||||||
|
// Note: We can't filter out empty expressions before this method,
|
||||||
|
// as we still want to validate them!
|
||||||
|
return boundProps.filter((boundProp) => {
|
||||||
if (boundProp.type === PropertyBindingType.Property &&
|
if (boundProp.type === PropertyBindingType.Property &&
|
||||||
!this._schemaRegistry.hasProperty(elementName, boundProp.name, this._schemas)) {
|
!this._schemaRegistry.hasProperty(elementName, boundProp.name, this._schemas)) {
|
||||||
let errorMsg =
|
let errorMsg =
|
||||||
|
@ -741,6 +753,7 @@ class TemplateParseVisitor implements html.Visitor {
|
||||||
}
|
}
|
||||||
this._reportError(errorMsg, boundProp.sourceSpan);
|
this._reportError(errorMsg, boundProp.sourceSpan);
|
||||||
}
|
}
|
||||||
|
return !isEmptyExpression(boundProp.value);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -870,3 +883,10 @@ export function removeSummaryDuplicates<T extends{type: CompileTypeMetadata}>(it
|
||||||
|
|
||||||
return Array.from(map.values());
|
return Array.from(map.values());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isEmptyExpression(ast: AST): boolean {
|
||||||
|
if (ast instanceof ASTWithSource) {
|
||||||
|
ast = ast.ast;
|
||||||
|
}
|
||||||
|
return ast instanceof EmptyExpr;
|
||||||
|
}
|
|
@ -8,7 +8,7 @@
|
||||||
|
|
||||||
import {AnimationEntryCompileResult} from '../animation/animation_compiler';
|
import {AnimationEntryCompileResult} from '../animation/animation_compiler';
|
||||||
import {CompileDirectiveMetadata, CompilePipeSummary, tokenName, viewClassName} from '../compile_metadata';
|
import {CompileDirectiveMetadata, CompilePipeSummary, tokenName, viewClassName} from '../compile_metadata';
|
||||||
import {EventHandlerVars, NameResolver} from '../compiler_util/expression_converter';
|
import {EventHandlerVars, LegacyNameResolver} from '../compiler_util/expression_converter';
|
||||||
import {CompilerConfig} from '../config';
|
import {CompilerConfig} from '../config';
|
||||||
import {isPresent} from '../facade/lang';
|
import {isPresent} from '../facade/lang';
|
||||||
import * as o from '../output/output_ast';
|
import * as o from '../output/output_ast';
|
||||||
|
@ -33,7 +33,7 @@ export class CompileViewRootNode {
|
||||||
public ngContentIndex?: number) {}
|
public ngContentIndex?: number) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CompileView implements NameResolver {
|
export class CompileView implements LegacyNameResolver {
|
||||||
public viewType: ViewType;
|
public viewType: ViewType;
|
||||||
public viewQueries: Map<any, CompileQuery[]>;
|
public viewQueries: Map<any, CompileQuery[]>;
|
||||||
|
|
||||||
|
|
|
@ -102,8 +102,8 @@ function generateHandleEventMethod(
|
||||||
});
|
});
|
||||||
boundEvents.forEach((renderEvent, renderEventIdx) => {
|
boundEvents.forEach((renderEvent, renderEventIdx) => {
|
||||||
const evalResult = convertActionBinding(
|
const evalResult = convertActionBinding(
|
||||||
compileElement.view, compileElement.view, compileElement.view.componentContext,
|
compileElement.view, compileElement.view.componentContext, renderEvent.handler,
|
||||||
renderEvent.handler, `sub_${renderEventIdx}`);
|
`sub_${renderEventIdx}`);
|
||||||
const trueStmts = evalResult.stmts;
|
const trueStmts = evalResult.stmts;
|
||||||
if (evalResult.allowDefault) {
|
if (evalResult.allowDefault) {
|
||||||
trueStmts.push(resultVar.set(evalResult.allowDefault.and(resultVar)).toStmt());
|
trueStmts.push(resultVar.set(evalResult.allowDefault.and(resultVar)).toStmt());
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
import {SecurityContext} from '@angular/core';
|
import {SecurityContext} from '@angular/core';
|
||||||
|
|
||||||
import {createCheckBindingField} from '../compiler_util/binding_util';
|
import {createCheckBindingField} from '../compiler_util/binding_util';
|
||||||
import {convertPropertyBinding} from '../compiler_util/expression_converter';
|
import {legacyConvertPropertyBinding} from '../compiler_util/expression_converter';
|
||||||
import {createEnumExpression} from '../compiler_util/identifier_util';
|
import {createEnumExpression} from '../compiler_util/identifier_util';
|
||||||
import {createCheckAnimationBindingStmts, createCheckRenderBindingStmt} from '../compiler_util/render_util';
|
import {createCheckAnimationBindingStmts, createCheckRenderBindingStmt} from '../compiler_util/render_util';
|
||||||
import {DirectiveWrapperExpressions} from '../directive_wrapper_compiler';
|
import {DirectiveWrapperExpressions} from '../directive_wrapper_compiler';
|
||||||
|
@ -26,7 +26,7 @@ import {getHandleEventMethodName} from './util';
|
||||||
export function bindRenderText(
|
export function bindRenderText(
|
||||||
boundText: BoundTextAst, compileNode: CompileNode, view: CompileView): void {
|
boundText: BoundTextAst, compileNode: CompileNode, view: CompileView): void {
|
||||||
const valueField = createCheckBindingField(view);
|
const valueField = createCheckBindingField(view);
|
||||||
const evalResult = convertPropertyBinding(
|
const evalResult = legacyConvertPropertyBinding(
|
||||||
view, view, view.componentContext, boundText.value, valueField.bindingId);
|
view, view, view.componentContext, boundText.value, valueField.bindingId);
|
||||||
if (!evalResult) {
|
if (!evalResult) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -53,7 +53,7 @@ export function bindRenderInputs(
|
||||||
boundProps.forEach((boundProp) => {
|
boundProps.forEach((boundProp) => {
|
||||||
const bindingField = createCheckBindingField(view);
|
const bindingField = createCheckBindingField(view);
|
||||||
view.detectChangesRenderPropertiesMethod.resetDebugInfo(compileElement.nodeIndex, boundProp);
|
view.detectChangesRenderPropertiesMethod.resetDebugInfo(compileElement.nodeIndex, boundProp);
|
||||||
const evalResult = convertPropertyBinding(
|
const evalResult = legacyConvertPropertyBinding(
|
||||||
view, view, compileElement.view.componentContext, boundProp.value, bindingField.bindingId);
|
view, view, compileElement.view.componentContext, boundProp.value, bindingField.bindingId);
|
||||||
if (!evalResult) {
|
if (!evalResult) {
|
||||||
return;
|
return;
|
||||||
|
@ -123,7 +123,7 @@ export function bindDirectiveInputs(
|
||||||
const bindingId = `${compileElement.nodeIndex}_${dirIndex}_${inputIdx}`;
|
const bindingId = `${compileElement.nodeIndex}_${dirIndex}_${inputIdx}`;
|
||||||
detectChangesInInputsMethod.resetDebugInfo(compileElement.nodeIndex, input);
|
detectChangesInInputsMethod.resetDebugInfo(compileElement.nodeIndex, input);
|
||||||
const evalResult =
|
const evalResult =
|
||||||
convertPropertyBinding(view, view, view.componentContext, input.value, bindingId);
|
legacyConvertPropertyBinding(view, view, view.componentContext, input.value, bindingId);
|
||||||
if (!evalResult) {
|
if (!evalResult) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
import {ViewEncapsulation} from '@angular/core';
|
import {ViewEncapsulation} from '@angular/core';
|
||||||
|
|
||||||
import {CompileDirectiveSummary, identifierModuleUrl, identifierName} from '../compile_metadata';
|
import {CompileDirectiveSummary, identifierModuleUrl, identifierName} from '../compile_metadata';
|
||||||
import {createSharedBindingVariablesIfNeeded} from '../compiler_util/expression_converter';
|
import {legacyCreateSharedBindingVariablesIfNeeded} from '../compiler_util/expression_converter';
|
||||||
import {createDiTokenExpression, createInlineArray} from '../compiler_util/identifier_util';
|
import {createDiTokenExpression, createInlineArray} from '../compiler_util/identifier_util';
|
||||||
import {isPresent} from '../facade/lang';
|
import {isPresent} from '../facade/lang';
|
||||||
import {Identifiers, createIdentifier, identifierToken} from '../identifiers';
|
import {Identifiers, createIdentifier, identifierToken} from '../identifiers';
|
||||||
|
@ -586,7 +586,7 @@ function generateDetectChangesMethod(view: CompileView): o.Statement[] {
|
||||||
stmts.push(new o.IfStmt(o.not(ViewProperties.throwOnChange), afterViewStmts));
|
stmts.push(new o.IfStmt(o.not(ViewProperties.throwOnChange), afterViewStmts));
|
||||||
}
|
}
|
||||||
|
|
||||||
const varStmts = createSharedBindingVariablesIfNeeded(stmts);
|
const varStmts = legacyCreateSharedBindingVariablesIfNeeded(stmts);
|
||||||
return varStmts.concat(stmts);
|
return varStmts.concat(stmts);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ import {ChangeDetectionStrategy} from '@angular/core';
|
||||||
|
|
||||||
import {AnimationEntryCompileResult} from '../animation/animation_compiler';
|
import {AnimationEntryCompileResult} from '../animation/animation_compiler';
|
||||||
import {CompileDiDependencyMetadata, CompileDirectiveMetadata, CompileDirectiveSummary, CompilePipeSummary, CompileProviderMetadata, CompileTokenMetadata, identifierModuleUrl, identifierName, tokenReference} from '../compile_metadata';
|
import {CompileDiDependencyMetadata, CompileDirectiveMetadata, CompileDirectiveSummary, CompilePipeSummary, CompileProviderMetadata, CompileTokenMetadata, identifierModuleUrl, identifierName, tokenReference} from '../compile_metadata';
|
||||||
import {EventHandlerVars, NameResolver, convertActionBinding, convertPropertyBinding} from '../compiler_util/expression_converter';
|
import {BuiltinConverter, BuiltinConverterFactory, EventHandlerVars, LocalResolver, convertActionBinding, convertPropertyBinding, convertPropertyBindingBuiltins} from '../compiler_util/expression_converter';
|
||||||
import {CompilerConfig} from '../config';
|
import {CompilerConfig} from '../config';
|
||||||
import {AST, ASTWithSource, Interpolation} from '../expression_parser/ast';
|
import {AST, ASTWithSource, Interpolation} from '../expression_parser/ast';
|
||||||
import {Identifiers, createIdentifier, resolveIdentifier} from '../identifiers';
|
import {Identifiers, createIdentifier, resolveIdentifier} from '../identifiers';
|
||||||
|
@ -25,6 +25,7 @@ import {ComponentFactoryDependency, ComponentViewDependency, DirectiveWrapperDep
|
||||||
|
|
||||||
const CLASS_ATTR = 'class';
|
const CLASS_ATTR = 'class';
|
||||||
const STYLE_ATTR = 'style';
|
const STYLE_ATTR = 'style';
|
||||||
|
const IMPLICIT_TEMPLATE_VAR = '\$implicit';
|
||||||
|
|
||||||
@CompilerInjectable()
|
@CompilerInjectable()
|
||||||
export class ViewCompilerNext extends ViewCompiler {
|
export class ViewCompilerNext extends ViewCompiler {
|
||||||
|
@ -35,7 +36,7 @@ export class ViewCompilerNext extends ViewCompiler {
|
||||||
|
|
||||||
compileComponent(
|
compileComponent(
|
||||||
component: CompileDirectiveMetadata, template: TemplateAst[], styles: o.Expression,
|
component: CompileDirectiveMetadata, template: TemplateAst[], styles: o.Expression,
|
||||||
pipes: CompilePipeSummary[],
|
usedPipes: CompilePipeSummary[],
|
||||||
compiledAnimations: AnimationEntryCompileResult[]): ViewCompileResult {
|
compiledAnimations: AnimationEntryCompileResult[]): ViewCompileResult {
|
||||||
const compName = identifierName(component.type) + (component.isHost ? `_Host` : '');
|
const compName = identifierName(component.type) + (component.isHost ? `_Host` : '');
|
||||||
|
|
||||||
|
@ -44,7 +45,7 @@ export class ViewCompilerNext extends ViewCompiler {
|
||||||
const viewBuilderFactory = (parent: ViewBuilder): ViewBuilder => {
|
const viewBuilderFactory = (parent: ViewBuilder): ViewBuilder => {
|
||||||
const embeddedViewIndex = embeddedViewCount++;
|
const embeddedViewIndex = embeddedViewCount++;
|
||||||
const viewName = `view_${compName}_${embeddedViewIndex}`;
|
const viewName = `view_${compName}_${embeddedViewIndex}`;
|
||||||
return new ViewBuilder(parent, viewName, viewBuilderFactory);
|
return new ViewBuilder(parent, viewName, usedPipes, viewBuilderFactory);
|
||||||
};
|
};
|
||||||
|
|
||||||
const visitor = viewBuilderFactory(null);
|
const visitor = viewBuilderFactory(null);
|
||||||
|
@ -80,20 +81,31 @@ const NODE_INDEX_VAR = o.variable('nodeIndex');
|
||||||
const EVENT_NAME_VAR = o.variable('eventName');
|
const EVENT_NAME_VAR = o.variable('eventName');
|
||||||
const ALLOW_DEFAULT_VAR = o.variable(`allowDefault`);
|
const ALLOW_DEFAULT_VAR = o.variable(`allowDefault`);
|
||||||
|
|
||||||
class ViewBuilder implements TemplateAstVisitor, NameResolver {
|
class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverterFactory {
|
||||||
private nodeDefs: o.Expression[] = [];
|
private nodeDefs: o.Expression[] = [];
|
||||||
|
private purePipeNodeIndices: {[pipeName: string]: number} = {};
|
||||||
private refNodeIndices: {[refName: string]: number} = {};
|
private refNodeIndices: {[refName: string]: number} = {};
|
||||||
private variables: VariableAst[] = [];
|
private variables: VariableAst[] = [];
|
||||||
private children: ViewBuilder[] = [];
|
private children: ViewBuilder[] = [];
|
||||||
private updateExpressions: UpdateExpression[] = [];
|
private updateDirectivesExpressions: UpdateExpression[] = [];
|
||||||
|
private updateRendererExpressions: UpdateExpression[] = [];
|
||||||
private handleEventExpressions: HandleEventExpression[] = [];
|
private handleEventExpressions: HandleEventExpression[] = [];
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private parent: ViewBuilder, public viewName: string,
|
private parent: ViewBuilder, public viewName: string, private usedPipes: CompilePipeSummary[],
|
||||||
private viewBuilderFactory: ViewBuilderFactory) {}
|
private viewBuilderFactory: ViewBuilderFactory) {}
|
||||||
|
|
||||||
visitAll(variables: VariableAst[], astNodes: TemplateAst[], elementDepth: number) {
|
visitAll(variables: VariableAst[], astNodes: TemplateAst[], elementDepth: number) {
|
||||||
this.variables = variables;
|
this.variables = variables;
|
||||||
|
// create the pipes for the pure pipes immediately, so that we know their indices.
|
||||||
|
if (!this.parent) {
|
||||||
|
this.usedPipes.forEach((pipe) => {
|
||||||
|
if (pipe.pure) {
|
||||||
|
this.purePipeNodeIndices[pipe.name] = this._createPipe(pipe);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
templateVisitAll(this, astNodes, {elementDepth});
|
templateVisitAll(this, astNodes, {elementDepth});
|
||||||
if (astNodes.length === 0 || (this.parent && hasViewContainer(astNodes[astNodes.length - 1]))) {
|
if (astNodes.length === 0 || (this.parent && hasViewContainer(astNodes[astNodes.length - 1]))) {
|
||||||
// if the view is empty, or an embedded view has a view container as last root nde,
|
// if the view is empty, or an embedded view has a view container as last root nde,
|
||||||
|
@ -108,46 +120,16 @@ class ViewBuilder implements TemplateAstVisitor, NameResolver {
|
||||||
const compType = o.importType(component.type);
|
const compType = o.importType(component.type);
|
||||||
this.children.forEach((child) => { child.build(component, targetStatements); });
|
this.children.forEach((child) => { child.build(component, targetStatements); });
|
||||||
|
|
||||||
const updateStmts: o.Statement[] = [];
|
const updateDirectivesFn = this._createUpdateFn(this.updateDirectivesExpressions, compType);
|
||||||
let updateBindingCount = 0;
|
const updateRendererFn = this._createUpdateFn(this.updateRendererExpressions, compType);
|
||||||
this.updateExpressions
|
|
||||||
.forEach(
|
|
||||||
({expressions, nodeIndex}) => {
|
|
||||||
const exprs = expressions.map(({context, value}) => {
|
|
||||||
const bindingId = `${updateBindingCount++}`;
|
|
||||||
const {stmts, currValExpr} =
|
|
||||||
convertPropertyBinding(null, this, context, value, bindingId);
|
|
||||||
updateStmts.push(...stmts);
|
|
||||||
return currValExpr;
|
|
||||||
});
|
|
||||||
if (exprs.length > 10) {
|
|
||||||
updateStmts.push(
|
|
||||||
CHECK_VAR
|
|
||||||
.callFn([
|
|
||||||
VIEW_VAR, o.literal(nodeIndex),
|
|
||||||
o.literal(viewEngine.ArgumentType.Dynamic), o.literalArr(exprs)
|
|
||||||
])
|
|
||||||
.toStmt());
|
|
||||||
} else {
|
|
||||||
updateStmts.push(
|
|
||||||
CHECK_VAR.callFn((<o.Expression[]>[VIEW_VAR, o.literal(nodeIndex), o.literal(viewEngine.ArgumentType.Inline)]).concat(exprs)).toStmt());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
let updateFn: o.Expression;
|
|
||||||
if (updateStmts.length > 0) {
|
|
||||||
updateFn = o.fn(
|
|
||||||
[new o.FnParam(CHECK_VAR.name), new o.FnParam(VIEW_VAR.name)],
|
|
||||||
[COMP_VAR.set(VIEW_VAR.prop('component')).toDeclStmt(compType), ...updateStmts]);
|
|
||||||
} else {
|
|
||||||
updateFn = o.NULL_EXPR;
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleEventStmts: o.Statement[] = [];
|
const handleEventStmts: o.Statement[] = [];
|
||||||
let handleEventBindingCount = 0;
|
let handleEventBindingCount = 0;
|
||||||
this.handleEventExpressions.forEach(({expression, context, nodeIndex, eventName}) => {
|
this.handleEventExpressions.forEach(({expression, context, nodeIndex, eventName}) => {
|
||||||
const bindingId = `${handleEventBindingCount++}`;
|
const bindingId = `${handleEventBindingCount++}`;
|
||||||
|
const nameResolver = context === COMP_VAR ? this : null;
|
||||||
const {stmts, allowDefault} =
|
const {stmts, allowDefault} =
|
||||||
convertActionBinding(null, this, context, expression, bindingId);
|
convertActionBinding(nameResolver, context, expression, bindingId);
|
||||||
const trueStmts = stmts;
|
const trueStmts = stmts;
|
||||||
if (allowDefault) {
|
if (allowDefault) {
|
||||||
trueStmts.push(ALLOW_DEFAULT_VAR.set(allowDefault.and(ALLOW_DEFAULT_VAR)).toStmt());
|
trueStmts.push(ALLOW_DEFAULT_VAR.set(allowDefault.and(ALLOW_DEFAULT_VAR)).toStmt());
|
||||||
|
@ -181,13 +163,39 @@ class ViewBuilder implements TemplateAstVisitor, NameResolver {
|
||||||
const viewFactory = new o.DeclareFunctionStmt(
|
const viewFactory = new o.DeclareFunctionStmt(
|
||||||
this.viewName, [],
|
this.viewName, [],
|
||||||
[new o.ReturnStatement(o.importExpr(createIdentifier(Identifiers.viewDef)).callFn([
|
[new o.ReturnStatement(o.importExpr(createIdentifier(Identifiers.viewDef)).callFn([
|
||||||
o.literal(viewFlags), o.literalArr(this.nodeDefs), updateFn, handleEventFn
|
o.literal(viewFlags), o.literalArr(this.nodeDefs), updateDirectivesFn, updateRendererFn,
|
||||||
|
handleEventFn
|
||||||
]))]);
|
]))]);
|
||||||
|
|
||||||
targetStatements.push(viewFactory);
|
targetStatements.push(viewFactory);
|
||||||
return targetStatements;
|
return targetStatements;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _createUpdateFn(expressions: UpdateExpression[], compType: o.Type): o.Expression {
|
||||||
|
const updateStmts: o.Statement[] = [];
|
||||||
|
let updateBindingCount = 0;
|
||||||
|
expressions.forEach(({expressions, nodeIndex}) => {
|
||||||
|
const exprs = expressions.map(({context, value}) => {
|
||||||
|
const bindingId = `${updateBindingCount++}`;
|
||||||
|
const nameResolver = context === COMP_VAR ? this : null;
|
||||||
|
const {stmts, currValExpr} =
|
||||||
|
convertPropertyBinding(nameResolver, context, value, bindingId);
|
||||||
|
updateStmts.push(...stmts);
|
||||||
|
return currValExpr;
|
||||||
|
});
|
||||||
|
updateStmts.push(callCheckStmt(nodeIndex, exprs).toStmt());
|
||||||
|
});
|
||||||
|
let updateFn: o.Expression;
|
||||||
|
if (updateStmts.length > 0) {
|
||||||
|
updateFn = o.fn(
|
||||||
|
[new o.FnParam(CHECK_VAR.name), new o.FnParam(VIEW_VAR.name)],
|
||||||
|
[COMP_VAR.set(VIEW_VAR.prop('component')).toDeclStmt(compType), ...updateStmts]);
|
||||||
|
} else {
|
||||||
|
updateFn = o.NULL_EXPR;
|
||||||
|
}
|
||||||
|
return updateFn;
|
||||||
|
}
|
||||||
|
|
||||||
visitNgContent(ast: NgContentAst, context: any): any {}
|
visitNgContent(ast: NgContentAst, context: any): any {}
|
||||||
|
|
||||||
visitText(ast: TextAst, context: any): any {
|
visitText(ast: TextAst, context: any): any {
|
||||||
|
@ -199,17 +207,20 @@ class ViewBuilder implements TemplateAstVisitor, NameResolver {
|
||||||
|
|
||||||
visitBoundText(ast: BoundTextAst, context: any): any {
|
visitBoundText(ast: BoundTextAst, context: any): any {
|
||||||
const nodeIndex = this.nodeDefs.length;
|
const nodeIndex = this.nodeDefs.length;
|
||||||
|
// reserve the space in the nodeDefs array
|
||||||
|
this.nodeDefs.push(null);
|
||||||
|
|
||||||
const astWithSource = <ASTWithSource>ast.value;
|
const astWithSource = <ASTWithSource>ast.value;
|
||||||
const inter = <Interpolation>astWithSource.ast;
|
const inter = <Interpolation>astWithSource.ast;
|
||||||
this.updateExpressions.push({
|
|
||||||
nodeIndex,
|
this._addUpdateExpressions(
|
||||||
expressions: inter.expressions.map((expr) => { return {context: COMP_VAR, value: expr}; })
|
nodeIndex, inter.expressions.map((expr) => { return {context: COMP_VAR, value: expr}; }),
|
||||||
});
|
this.updateRendererExpressions);
|
||||||
|
|
||||||
// textDef(ngContentIndex: number, constants: string[]): NodeDef;
|
// textDef(ngContentIndex: number, constants: string[]): NodeDef;
|
||||||
this.nodeDefs.push(o.importExpr(createIdentifier(Identifiers.textDef)).callFn([
|
this.nodeDefs[nodeIndex] = o.importExpr(createIdentifier(Identifiers.textDef)).callFn([
|
||||||
o.NULL_EXPR, o.literalArr(inter.strings.map(s => o.literal(s)))
|
o.NULL_EXPR, o.literalArr(inter.strings.map(s => o.literal(s)))
|
||||||
]));
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
visitEmbeddedTemplate(ast: EmbeddedTemplateAst, context: {elementDepth: number}): any {
|
visitEmbeddedTemplate(ast: EmbeddedTemplateAst, context: {elementDepth: number}): any {
|
||||||
|
@ -219,11 +230,12 @@ class ViewBuilder implements TemplateAstVisitor, NameResolver {
|
||||||
|
|
||||||
const {flags, queryMatchesExpr} = this._visitElementOrTemplate(nodeIndex, ast, context);
|
const {flags, queryMatchesExpr} = this._visitElementOrTemplate(nodeIndex, ast, context);
|
||||||
|
|
||||||
const childCount = this.nodeDefs.length - nodeIndex - 1;
|
|
||||||
const childVisitor = this.viewBuilderFactory(this);
|
const childVisitor = this.viewBuilderFactory(this);
|
||||||
this.children.push(childVisitor);
|
this.children.push(childVisitor);
|
||||||
childVisitor.visitAll(ast.variables, ast.children, context.elementDepth + 1);
|
childVisitor.visitAll(ast.variables, ast.children, context.elementDepth + 1);
|
||||||
|
|
||||||
|
const childCount = this.nodeDefs.length - nodeIndex - 1;
|
||||||
|
|
||||||
// anchorDef(
|
// anchorDef(
|
||||||
// flags: NodeFlags, matchedQueries: [string, QueryValueType][], ngContentIndex: number,
|
// flags: NodeFlags, matchedQueries: [string, QueryValueType][], ngContentIndex: number,
|
||||||
// childCount: number, templateFactory?: ViewDefinitionFactory): NodeDef;
|
// childCount: number, templateFactory?: ViewDefinitionFactory): NodeDef;
|
||||||
|
@ -243,12 +255,9 @@ class ViewBuilder implements TemplateAstVisitor, NameResolver {
|
||||||
|
|
||||||
templateVisitAll(this, ast.children, {elementDepth: context.elementDepth + 1});
|
templateVisitAll(this, ast.children, {elementDepth: context.elementDepth + 1});
|
||||||
|
|
||||||
const childCount = this.nodeDefs.length - nodeIndex - 1;
|
ast.inputs.forEach(
|
||||||
|
(inputAst) => { hostBindings.push({context: COMP_VAR, value: inputAst.value}); });
|
||||||
ast.inputs.forEach((inputAst) => {
|
this._addUpdateExpressions(nodeIndex, hostBindings, this.updateRendererExpressions);
|
||||||
hostBindings.push({context: COMP_VAR, value: (<ASTWithSource>inputAst.value).ast});
|
|
||||||
});
|
|
||||||
this.updateExpressions.push({nodeIndex, expressions: hostBindings});
|
|
||||||
|
|
||||||
const inputDefs = elementBindingDefs(ast.inputs);
|
const inputDefs = elementBindingDefs(ast.inputs);
|
||||||
ast.directives.forEach(
|
ast.directives.forEach(
|
||||||
|
@ -258,6 +267,8 @@ class ViewBuilder implements TemplateAstVisitor, NameResolver {
|
||||||
o.literal(eventName);
|
o.literal(eventName);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const childCount = this.nodeDefs.length - nodeIndex - 1;
|
||||||
|
|
||||||
// elementDef(
|
// elementDef(
|
||||||
// flags: NodeFlags, matchedQueries: [string, QueryValueType][], ngContentIndex: number,
|
// flags: NodeFlags, matchedQueries: [string, QueryValueType][], ngContentIndex: number,
|
||||||
// childCount: number, name: string, fixedAttrs: {[name: string]: string} = {},
|
// childCount: number, name: string, fixedAttrs: {[name: string]: string} = {},
|
||||||
|
@ -355,13 +366,10 @@ class ViewBuilder implements TemplateAstVisitor, NameResolver {
|
||||||
ast.outputs.forEach(
|
ast.outputs.forEach(
|
||||||
(outputAst) => { hostEvents.push({context: COMP_VAR, eventAst: outputAst}); });
|
(outputAst) => { hostEvents.push({context: COMP_VAR, eventAst: outputAst}); });
|
||||||
hostEvents.forEach((hostEvent) => {
|
hostEvents.forEach((hostEvent) => {
|
||||||
this.handleEventExpressions.push({
|
this._addHandleEventExpression(
|
||||||
nodeIndex,
|
nodeIndex, hostEvent.context,
|
||||||
context: hostEvent.context,
|
viewEngine.elementEventFullName(hostEvent.eventAst.target, hostEvent.eventAst.name),
|
||||||
eventName:
|
hostEvent.eventAst.handler);
|
||||||
viewEngine.elementEventFullName(hostEvent.eventAst.target, hostEvent.eventAst.name),
|
|
||||||
expression: (<ASTWithSource>hostEvent.eventAst.handler).ast
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -383,6 +391,22 @@ class ViewBuilder implements TemplateAstVisitor, NameResolver {
|
||||||
// reserve the space in the nodeDefs array so we can add children
|
// reserve the space in the nodeDefs array so we can add children
|
||||||
this.nodeDefs.push(null);
|
this.nodeDefs.push(null);
|
||||||
|
|
||||||
|
directiveAst.directive.queries.forEach((query, queryIndex) => {
|
||||||
|
const queryId: QueryId = {elementDepth, directiveIndex, queryIndex};
|
||||||
|
const bindingType =
|
||||||
|
query.first ? viewEngine.QueryBindingType.First : viewEngine.QueryBindingType.All;
|
||||||
|
this.nodeDefs.push(o.importExpr(createIdentifier(Identifiers.queryDef)).callFn([
|
||||||
|
o.literal(viewEngine.NodeFlags.HasContentQuery), o.literal(calcQueryId(queryId)),
|
||||||
|
new o.LiteralMapExpr([new o.LiteralMapEntry(query.propertyName, o.literal(bindingType))])
|
||||||
|
]));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Note: the operation below might also create new nodeDefs,
|
||||||
|
// but we don't want them to be a child of a directive,
|
||||||
|
// as they might be a provider/pipe on their own.
|
||||||
|
// I.e. we only allow queries as children of directives nodes.
|
||||||
|
const childCount = this.nodeDefs.length - nodeIndex - 1;
|
||||||
|
|
||||||
const {flags, queryMatchExprs, providerExpr, providerType, depsExpr} =
|
const {flags, queryMatchExprs, providerExpr, providerType, depsExpr} =
|
||||||
this._visitProviderOrDirective(providerAst, queryMatches);
|
this._visitProviderOrDirective(providerAst, queryMatches);
|
||||||
|
|
||||||
|
@ -415,11 +439,10 @@ class ViewBuilder implements TemplateAstVisitor, NameResolver {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (directiveAst.inputs.length) {
|
if (directiveAst.inputs.length) {
|
||||||
this.updateExpressions.push({
|
this._addUpdateExpressions(
|
||||||
nodeIndex,
|
nodeIndex,
|
||||||
expressions: directiveAst.inputs.map(
|
directiveAst.inputs.map((input) => { return {context: COMP_VAR, value: input.value}; }),
|
||||||
input => { return {context: COMP_VAR, value: (<ASTWithSource>input.value).ast}; })
|
this.updateDirectivesExpressions);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const dirContextExpr = o.importExpr(createIdentifier(Identifiers.nodeValue)).callFn([
|
const dirContextExpr = o.importExpr(createIdentifier(Identifiers.nodeValue)).callFn([
|
||||||
|
@ -434,19 +457,6 @@ class ViewBuilder implements TemplateAstVisitor, NameResolver {
|
||||||
const hostEvents = directiveAst.hostEvents.map(
|
const hostEvents = directiveAst.hostEvents.map(
|
||||||
(hostEventAst) => { return {context: dirContextExpr, eventAst: hostEventAst}; });
|
(hostEventAst) => { return {context: dirContextExpr, eventAst: hostEventAst}; });
|
||||||
|
|
||||||
const childCount = directiveAst.directive.queries.length;
|
|
||||||
directiveAst.directive.queries.forEach((query, queryIndex) => {
|
|
||||||
const queryId: QueryId = {elementDepth, directiveIndex, queryIndex};
|
|
||||||
const bindingType =
|
|
||||||
query.first ? viewEngine.QueryBindingType.First : viewEngine.QueryBindingType.All;
|
|
||||||
// queryDef(
|
|
||||||
// flags: NodeFlags, id: string, bindings: {[propName: string]: QueryBindingType}): NodeDef
|
|
||||||
// {
|
|
||||||
this.nodeDefs.push(o.importExpr(createIdentifier(Identifiers.queryDef)).callFn([
|
|
||||||
o.literal(viewEngine.NodeFlags.HasContentQuery), o.literal(calcQueryId(queryId)),
|
|
||||||
new o.LiteralMapExpr([new o.LiteralMapEntry(query.propertyName, o.literal(bindingType))])
|
|
||||||
]));
|
|
||||||
});
|
|
||||||
|
|
||||||
// directiveDef(
|
// directiveDef(
|
||||||
// flags: NodeFlags, matchedQueries: [string, QueryValueType][], childCount: number, ctor:
|
// flags: NodeFlags, matchedQueries: [string, QueryValueType][], childCount: number, ctor:
|
||||||
|
@ -514,10 +524,6 @@ class ViewBuilder implements TemplateAstVisitor, NameResolver {
|
||||||
return {flags, queryMatchExprs, providerExpr, providerType, depsExpr};
|
return {flags, queryMatchExprs, providerExpr, providerType, depsExpr};
|
||||||
}
|
}
|
||||||
|
|
||||||
callPipe(name: string, input: o.Expression, args: o.Expression[]): o.Expression {
|
|
||||||
throw new Error('Pipes are not yet supported!');
|
|
||||||
}
|
|
||||||
|
|
||||||
getLocal(name: string): o.Expression {
|
getLocal(name: string): o.Expression {
|
||||||
if (name == EventHandlerVars.event.name) {
|
if (name == EventHandlerVars.event.name) {
|
||||||
return EventHandlerVars.event;
|
return EventHandlerVars.event;
|
||||||
|
@ -536,12 +542,123 @@ class ViewBuilder implements TemplateAstVisitor, NameResolver {
|
||||||
// check variables
|
// check variables
|
||||||
const varAst = currBuilder.variables.find((varAst) => varAst.name === name);
|
const varAst = currBuilder.variables.find((varAst) => varAst.name === name);
|
||||||
if (varAst) {
|
if (varAst) {
|
||||||
return currViewExpr.prop('context').prop(varAst.value);
|
const varValue = varAst.value || IMPLICIT_TEMPLATE_VAR;
|
||||||
|
return currViewExpr.prop('context').prop(varValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
createLiteralArrayConverter(argCount: number): BuiltinConverter {
|
||||||
|
if (argCount === 0) {
|
||||||
|
const valueExpr = o.importExpr(createIdentifier(Identifiers.EMPTY_ARRAY));
|
||||||
|
return () => valueExpr;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nodeIndex = this.nodeDefs.length;
|
||||||
|
// pureArrayDef(argCount: number): NodeDef;
|
||||||
|
const nodeDef =
|
||||||
|
o.importExpr(createIdentifier(Identifiers.pureArrayDef)).callFn([o.literal(argCount)]);
|
||||||
|
this.nodeDefs.push(nodeDef);
|
||||||
|
|
||||||
|
return (args: o.Expression[]) => callCheckStmt(nodeIndex, args);
|
||||||
|
}
|
||||||
|
createLiteralMapConverter(keys: string[]): BuiltinConverter {
|
||||||
|
if (keys.length === 0) {
|
||||||
|
const valueExpr = o.importExpr(createIdentifier(Identifiers.EMPTY_MAP));
|
||||||
|
return () => valueExpr;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nodeIndex = this.nodeDefs.length;
|
||||||
|
// function pureObjectDef(propertyNames: string[]): NodeDef
|
||||||
|
const nodeDef = o.importExpr(createIdentifier(Identifiers.pureObjectDef)).callFn([o.literalArr(
|
||||||
|
keys.map(key => o.literal(key)))]);
|
||||||
|
this.nodeDefs.push(nodeDef);
|
||||||
|
|
||||||
|
return (args: o.Expression[]) => callCheckStmt(nodeIndex, args);
|
||||||
|
}
|
||||||
|
createPipeConverter(name: string, argCount: number): BuiltinConverter {
|
||||||
|
const pipe = this._findPipe(name);
|
||||||
|
if (pipe.pure) {
|
||||||
|
const nodeIndex = this.nodeDefs.length;
|
||||||
|
// function purePipeDef(argCount: number): NodeDef;
|
||||||
|
const nodeDef =
|
||||||
|
o.importExpr(createIdentifier(Identifiers.purePipeDef)).callFn([o.literal(argCount)]);
|
||||||
|
this.nodeDefs.push(nodeDef);
|
||||||
|
|
||||||
|
// find underlying pipe in the component view
|
||||||
|
let compViewExpr: o.Expression = VIEW_VAR;
|
||||||
|
let compBuilder: ViewBuilder = this;
|
||||||
|
while (compBuilder.parent) {
|
||||||
|
compBuilder = compBuilder.parent;
|
||||||
|
compViewExpr = compViewExpr.prop('parent');
|
||||||
|
}
|
||||||
|
const pipeNodeIndex = compBuilder.purePipeNodeIndices[name];
|
||||||
|
const pipeValueExpr: o.Expression =
|
||||||
|
o.importExpr(createIdentifier(Identifiers.nodeValue)).callFn([
|
||||||
|
compViewExpr, o.literal(pipeNodeIndex)
|
||||||
|
]);
|
||||||
|
|
||||||
|
return (args: o.Expression[]) =>
|
||||||
|
callUnwrapValue(callCheckStmt(nodeIndex, [pipeValueExpr].concat(args)));
|
||||||
|
} else {
|
||||||
|
const nodeIndex = this._createPipe(pipe);
|
||||||
|
const nodeValueExpr = o.importExpr(createIdentifier(Identifiers.nodeValue)).callFn([
|
||||||
|
VIEW_VAR, o.literal(nodeIndex)
|
||||||
|
]);
|
||||||
|
|
||||||
|
return (args: o.Expression[]) => callUnwrapValue(nodeValueExpr.callMethod('transform', args));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _findPipe(name: string): CompilePipeSummary {
|
||||||
|
return this.usedPipes.find((pipeSummary) => pipeSummary.name === name);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _createPipe(pipe: CompilePipeSummary): number {
|
||||||
|
const nodeIndex = this.nodeDefs.length;
|
||||||
|
let flags = viewEngine.NodeFlags.None;
|
||||||
|
pipe.type.lifecycleHooks.forEach((lifecycleHook) => {
|
||||||
|
// for pipes, we only support ngOnDestroy
|
||||||
|
if (lifecycleHook === LifecycleHooks.OnDestroy) {
|
||||||
|
flags |= lifecycleHookToNodeFlag(lifecycleHook);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const depExprs = pipe.type.diDeps.map(depDef);
|
||||||
|
// function pipeDef(
|
||||||
|
// flags: NodeFlags, ctor: any, deps: ([DepFlags, any] | any)[]): NodeDef
|
||||||
|
const nodeDef = o.importExpr(createIdentifier(Identifiers.pipeDef)).callFn([
|
||||||
|
o.literal(flags), o.importExpr(pipe.type), o.literalArr(depExprs)
|
||||||
|
]);
|
||||||
|
this.nodeDefs.push(nodeDef);
|
||||||
|
return nodeIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attention: This might create new nodeDefs (for pipes and literal arrays and literal maps)!
|
||||||
|
private _addUpdateExpressions(
|
||||||
|
nodeIndex: number, expressions: {context: o.Expression, value: AST}[],
|
||||||
|
target: UpdateExpression[]) {
|
||||||
|
if (expressions.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const transformedExpressions = expressions.map(({context, value}) => {
|
||||||
|
if (value instanceof ASTWithSource) {
|
||||||
|
value = value.ast;
|
||||||
|
}
|
||||||
|
return {context, value: convertPropertyBindingBuiltins(this, value)};
|
||||||
|
});
|
||||||
|
target.push({nodeIndex, expressions: transformedExpressions});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _addHandleEventExpression(
|
||||||
|
nodeIndex: number, context: o.Expression, eventName: string, expression: AST) {
|
||||||
|
if (expression instanceof ASTWithSource) {
|
||||||
|
expression = expression.ast;
|
||||||
|
}
|
||||||
|
this.handleEventExpressions.push({nodeIndex, context, eventName, expression});
|
||||||
|
}
|
||||||
|
|
||||||
visitDirective(ast: DirectiveAst, context: {usedEvents: Set<string>}): any {}
|
visitDirective(ast: DirectiveAst, context: {usedEvents: Set<string>}): any {}
|
||||||
visitDirectiveProperty(ast: BoundDirectivePropertyAst, context: any): any {}
|
visitDirectiveProperty(ast: BoundDirectivePropertyAst, context: any): any {}
|
||||||
visitReference(ast: ReferenceAst, context: any): any {}
|
visitReference(ast: ReferenceAst, context: any): any {}
|
||||||
|
@ -736,3 +853,19 @@ function mergeAttributeValue(attrName: string, attrValue1: string, attrValue2: s
|
||||||
return attrValue2;
|
return attrValue2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function callCheckStmt(nodeIndex: number, exprs: o.Expression[]): o.Expression {
|
||||||
|
if (exprs.length > 10) {
|
||||||
|
return CHECK_VAR.callFn([
|
||||||
|
VIEW_VAR, o.literal(nodeIndex), o.literal(viewEngine.ArgumentType.Dynamic),
|
||||||
|
o.literalArr(exprs)
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
return CHECK_VAR.callFn(
|
||||||
|
[VIEW_VAR, o.literal(nodeIndex), o.literal(viewEngine.ArgumentType.Inline), ...exprs]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function callUnwrapValue(expr: o.Expression): o.Expression {
|
||||||
|
return o.importExpr(createIdentifier(Identifiers.unwrapValue)).callFn([expr]);
|
||||||
|
}
|
|
@ -70,7 +70,8 @@ export function main() {
|
||||||
if (pipes === null) {
|
if (pipes === null) {
|
||||||
pipes = [];
|
pipes = [];
|
||||||
}
|
}
|
||||||
return parser.parse(component, template, directives, pipes, schemas, 'TestComp');
|
return parser.parse(component, template, directives, pipes, schemas, 'TestComp')
|
||||||
|
.template;
|
||||||
};
|
};
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
@ -306,10 +307,10 @@ export function main() {
|
||||||
isComponent: true,
|
isComponent: true,
|
||||||
template: new CompileTemplateMetadata({interpolation: ['{%', '%}']})
|
template: new CompileTemplateMetadata({interpolation: ['{%', '%}']})
|
||||||
});
|
});
|
||||||
expect(humanizeTplAst(parser.parse(component, '{%a%}', [], [], [], 'TestComp'), {
|
expect(humanizeTplAst(
|
||||||
start: '{%',
|
parser.parse(component, '{%a%}', [], [], [], 'TestComp').template,
|
||||||
end: '%}'
|
{start: '{%', end: '%}'}))
|
||||||
})).toEqual([[BoundTextAst, '{% a %}']]);
|
.toEqual([[BoundTextAst, '{% a %}']]);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
describe('bound properties', () => {
|
describe('bound properties', () => {
|
||||||
|
|
|
@ -10,7 +10,7 @@ import {isDevMode} from '../application_ref';
|
||||||
import {SecurityContext} from '../security';
|
import {SecurityContext} from '../security';
|
||||||
|
|
||||||
import {BindingDef, BindingType, DebugContext, DisposableFn, ElementData, ElementOutputDef, NodeData, NodeDef, NodeFlags, NodeType, QueryValueType, Services, ViewData, ViewDefinition, ViewDefinitionFactory, ViewFlags, asElementData} from './types';
|
import {BindingDef, BindingType, DebugContext, DisposableFn, ElementData, ElementOutputDef, NodeData, NodeDef, NodeFlags, NodeType, QueryValueType, Services, ViewData, ViewDefinition, ViewDefinitionFactory, ViewFlags, asElementData} from './types';
|
||||||
import {checkAndUpdateBinding, dispatchEvent, elementEventFullName, resolveViewDefinition, sliceErrorStack, unwrapValue} from './util';
|
import {checkAndUpdateBinding, dispatchEvent, elementEventFullName, resolveViewDefinition, sliceErrorStack} from './util';
|
||||||
|
|
||||||
export function anchorDef(
|
export function anchorDef(
|
||||||
flags: NodeFlags, matchedQueries: [string, QueryValueType][], ngContentIndex: number,
|
flags: NodeFlags, matchedQueries: [string, QueryValueType][], ngContentIndex: number,
|
||||||
|
@ -209,8 +209,6 @@ function checkAndUpdateElementValue(view: ViewData, def: NodeDef, bindingIdx: nu
|
||||||
if (!checkAndUpdateBinding(view, def, bindingIdx, value)) {
|
if (!checkAndUpdateBinding(view, def, bindingIdx, value)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
value = unwrapValue(value);
|
|
||||||
|
|
||||||
const binding = def.bindings[bindingIdx];
|
const binding = def.bindings[bindingIdx];
|
||||||
const name = binding.name;
|
const name = binding.name;
|
||||||
const renderNode = asElementData(view, def.index).renderElement;
|
const renderNode = asElementData(view, def.index).renderElement;
|
||||||
|
|
|
@ -31,7 +31,6 @@ export function viewDebugError(msg: string, context: DebugContext): Error {
|
||||||
const err = new Error(msg);
|
const err = new Error(msg);
|
||||||
(err as any)[ERROR_DEBUG_CONTEXT] = context;
|
(err as any)[ERROR_DEBUG_CONTEXT] = context;
|
||||||
err.stack = context.source;
|
err.stack = context.source;
|
||||||
context.view.state |= ViewState.Errored;
|
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,13 +8,13 @@
|
||||||
|
|
||||||
export {anchorDef, elementDef} from './element';
|
export {anchorDef, elementDef} from './element';
|
||||||
export {ngContentDef} from './ng_content';
|
export {ngContentDef} from './ng_content';
|
||||||
export {directiveDef, providerDef} from './provider';
|
export {directiveDef, pipeDef, providerDef} from './provider';
|
||||||
export {pureArrayDef, pureObjectDef, purePipeDef} from './pure_expression';
|
export {pureArrayDef, pureObjectDef, purePipeDef} from './pure_expression';
|
||||||
export {queryDef} from './query';
|
export {queryDef} from './query';
|
||||||
export {createComponentFactory} from './refs';
|
export {createComponentFactory} from './refs';
|
||||||
export {initServicesIfNeeded} from './services';
|
export {initServicesIfNeeded} from './services';
|
||||||
export {textDef} from './text';
|
export {textDef} from './text';
|
||||||
export {elementEventFullName, nodeValue, rootRenderNodes} from './util';
|
export {elementEventFullName, nodeValue, rootRenderNodes, unwrapValue} from './util';
|
||||||
export {viewDef} from './view';
|
export {viewDef} from './view';
|
||||||
export {attachEmbeddedView, detachEmbeddedView, moveEmbeddedView} from './view_attach';
|
export {attachEmbeddedView, detachEmbeddedView, moveEmbeddedView} from './view_attach';
|
||||||
|
|
||||||
|
|
|
@ -15,8 +15,8 @@ import * as v1renderer from '../render/api';
|
||||||
import {Type} from '../type';
|
import {Type} from '../type';
|
||||||
|
|
||||||
import {createChangeDetectorRef, createInjector, createTemplateRef, createViewContainerRef} from './refs';
|
import {createChangeDetectorRef, createInjector, createTemplateRef, createViewContainerRef} from './refs';
|
||||||
import {BindingDef, BindingType, DepDef, DepFlags, DisposableFn, NodeData, NodeDef, NodeFlags, NodeType, ProviderData, ProviderOutputDef, ProviderType, QueryBindingType, QueryDef, QueryValueType, RootData, Services, ViewData, ViewDefinition, ViewFlags, ViewState, asElementData, asProviderData} from './types';
|
import {BindingDef, BindingType, DepDef, DepFlags, DirectiveOutputDef, DisposableFn, NodeData, NodeDef, NodeFlags, NodeType, ProviderData, ProviderType, QueryBindingType, QueryDef, QueryValueType, RootData, Services, ViewData, ViewDefinition, ViewFlags, ViewState, asElementData, asProviderData} from './types';
|
||||||
import {checkAndUpdateBinding, dispatchEvent, isComponentView, tokenKey, unwrapValue, viewParentDiIndex} from './util';
|
import {checkAndUpdateBinding, dispatchEvent, isComponentView, tokenKey, viewParentElIndex} from './util';
|
||||||
|
|
||||||
const RendererV1TokenKey = tokenKey(v1renderer.Renderer);
|
const RendererV1TokenKey = tokenKey(v1renderer.Renderer);
|
||||||
const ElementRefTokenKey = tokenKey(ElementRef);
|
const ElementRefTokenKey = tokenKey(ElementRef);
|
||||||
|
@ -31,45 +31,55 @@ export function directiveDef(
|
||||||
flags: NodeFlags, matchedQueries: [string, QueryValueType][], childCount: number, ctor: any,
|
flags: NodeFlags, matchedQueries: [string, QueryValueType][], childCount: number, ctor: any,
|
||||||
deps: ([DepFlags, any] | any)[], props?: {[name: string]: [number, string]},
|
deps: ([DepFlags, any] | any)[], props?: {[name: string]: [number, string]},
|
||||||
outputs?: {[name: string]: string}, component?: () => ViewDefinition): NodeDef {
|
outputs?: {[name: string]: string}, component?: () => ViewDefinition): NodeDef {
|
||||||
return _providerDef(
|
|
||||||
flags, matchedQueries, childCount, ProviderType.Class, ctor, ctor, deps, props, outputs,
|
|
||||||
component);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function providerDef(
|
|
||||||
flags: NodeFlags, matchedQueries: [string, QueryValueType][], type: ProviderType, token: any,
|
|
||||||
value: any, deps: ([DepFlags, any] | any)[]): NodeDef {
|
|
||||||
return _providerDef(flags, matchedQueries, 0, type, token, value, deps);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function _providerDef(
|
|
||||||
flags: NodeFlags, matchedQueries: [string, QueryValueType][], childCount: number,
|
|
||||||
type: ProviderType, token: any, value: any, deps: ([DepFlags, any] | any)[],
|
|
||||||
props?: {[name: string]: [number, string]}, outputs?: {[name: string]: string},
|
|
||||||
component?: () => ViewDefinition): NodeDef {
|
|
||||||
const matchedQueryDefs: {[queryId: string]: QueryValueType} = {};
|
|
||||||
if (matchedQueries) {
|
|
||||||
matchedQueries.forEach(([queryId, valueType]) => { matchedQueryDefs[queryId] = valueType; });
|
|
||||||
}
|
|
||||||
|
|
||||||
const bindings: BindingDef[] = [];
|
const bindings: BindingDef[] = [];
|
||||||
if (props) {
|
if (props) {
|
||||||
for (let prop in props) {
|
for (let prop in props) {
|
||||||
const [bindingIndex, nonMinifiedName] = props[prop];
|
const [bindingIndex, nonMinifiedName] = props[prop];
|
||||||
bindings[bindingIndex] = {
|
bindings[bindingIndex] = {
|
||||||
type: BindingType.ProviderProperty,
|
type: BindingType.DirectiveProperty,
|
||||||
name: prop, nonMinifiedName,
|
name: prop, nonMinifiedName,
|
||||||
securityContext: undefined,
|
securityContext: undefined,
|
||||||
suffix: undefined
|
suffix: undefined
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const outputDefs: ProviderOutputDef[] = [];
|
const outputDefs: DirectiveOutputDef[] = [];
|
||||||
if (outputs) {
|
if (outputs) {
|
||||||
for (let propName in outputs) {
|
for (let propName in outputs) {
|
||||||
outputDefs.push({propName, eventName: outputs[propName]});
|
outputDefs.push({propName, eventName: outputs[propName]});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return _def(
|
||||||
|
NodeType.Directive, flags, matchedQueries, childCount, ProviderType.Class, ctor, ctor, deps,
|
||||||
|
bindings, outputDefs, component);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function pipeDef(flags: NodeFlags, ctor: any, deps: ([DepFlags, any] | any)[]): NodeDef {
|
||||||
|
return _def(NodeType.Pipe, flags, null, 0, ProviderType.Class, ctor, ctor, deps);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function providerDef(
|
||||||
|
flags: NodeFlags, matchedQueries: [string, QueryValueType][], type: ProviderType, token: any,
|
||||||
|
value: any, deps: ([DepFlags, any] | any)[]): NodeDef {
|
||||||
|
return _def(NodeType.Provider, flags, matchedQueries, 0, type, token, value, deps);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function _def(
|
||||||
|
type: NodeType, flags: NodeFlags, matchedQueries: [string, QueryValueType][],
|
||||||
|
childCount: number, providerType: ProviderType, token: any, value: any,
|
||||||
|
deps: ([DepFlags, any] | any)[], bindings?: BindingDef[], outputs?: DirectiveOutputDef[],
|
||||||
|
component?: () => ViewDefinition): NodeDef {
|
||||||
|
const matchedQueryDefs: {[queryId: string]: QueryValueType} = {};
|
||||||
|
if (matchedQueries) {
|
||||||
|
matchedQueries.forEach(([queryId, valueType]) => { matchedQueryDefs[queryId] = valueType; });
|
||||||
|
}
|
||||||
|
if (!outputs) {
|
||||||
|
outputs = [];
|
||||||
|
}
|
||||||
|
if (!bindings) {
|
||||||
|
bindings = [];
|
||||||
|
}
|
||||||
|
|
||||||
const depDefs: DepDef[] = deps.map(value => {
|
const depDefs: DepDef[] = deps.map(value => {
|
||||||
let token: any;
|
let token: any;
|
||||||
let flags: DepFlags;
|
let flags: DepFlags;
|
||||||
|
@ -86,7 +96,7 @@ export function _providerDef(
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: NodeType.Provider,
|
type,
|
||||||
// will bet set by the view definition
|
// will bet set by the view definition
|
||||||
index: undefined,
|
index: undefined,
|
||||||
reverseChildIndex: undefined,
|
reverseChildIndex: undefined,
|
||||||
|
@ -99,14 +109,13 @@ export function _providerDef(
|
||||||
flags,
|
flags,
|
||||||
matchedQueries: matchedQueryDefs,
|
matchedQueries: matchedQueryDefs,
|
||||||
ngContentIndex: undefined, childCount, bindings,
|
ngContentIndex: undefined, childCount, bindings,
|
||||||
disposableCount: outputDefs.length,
|
disposableCount: outputs.length,
|
||||||
element: undefined,
|
element: undefined,
|
||||||
provider: {
|
provider: {
|
||||||
type,
|
type: providerType,
|
||||||
token,
|
token,
|
||||||
tokenKey: tokenKey(token), value,
|
tokenKey: tokenKey(token), value,
|
||||||
deps: depDefs,
|
deps: depDefs, outputs, component
|
||||||
outputs: outputDefs, component
|
|
||||||
},
|
},
|
||||||
text: undefined,
|
text: undefined,
|
||||||
pureExpression: undefined,
|
pureExpression: undefined,
|
||||||
|
@ -116,97 +125,115 @@ export function _providerDef(
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createProviderInstance(view: ViewData, def: NodeDef): any {
|
export function createProviderInstance(view: ViewData, def: NodeDef): any {
|
||||||
|
return def.flags & NodeFlags.LazyProvider ? NOT_CREATED : _createProviderInstance(view, def);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createPipeInstance(view: ViewData, def: NodeDef): any {
|
||||||
|
// deps are looked up from component.
|
||||||
|
let compView = view;
|
||||||
|
while (compView.parent && !isComponentView(compView)) {
|
||||||
|
compView = compView.parent;
|
||||||
|
}
|
||||||
|
// pipes are always eager and classes!
|
||||||
|
return createClass(
|
||||||
|
compView.parent, compView.parentIndex, viewParentElIndex(compView), def.provider.value,
|
||||||
|
def.provider.deps);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createDirectiveInstance(view: ViewData, def: NodeDef): any {
|
||||||
const providerDef = def.provider;
|
const providerDef = def.provider;
|
||||||
return def.flags & NodeFlags.LazyProvider ? NOT_CREATED : createInstance(view, def);
|
// directives are always eager and classes!
|
||||||
|
const instance = createClass(view, def.index, def.parent, def.provider.value, def.provider.deps);
|
||||||
|
if (providerDef.outputs.length) {
|
||||||
|
for (let i = 0; i < providerDef.outputs.length; i++) {
|
||||||
|
const output = providerDef.outputs[i];
|
||||||
|
const subscription = instance[output.propName].subscribe(
|
||||||
|
eventHandlerClosure(view, def.parent, output.eventName));
|
||||||
|
view.disposables[def.disposableIndex + i] = subscription.unsubscribe.bind(subscription);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
function eventHandlerClosure(view: ViewData, index: number, eventName: string) {
|
function eventHandlerClosure(view: ViewData, index: number, eventName: string) {
|
||||||
return (event: any) => dispatchEvent(view, index, eventName, event);
|
return (event: any) => dispatchEvent(view, index, eventName, event);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function checkAndUpdateProviderInline(
|
export function checkAndUpdateDirectiveInline(
|
||||||
view: ViewData, def: NodeDef, v0: any, v1: any, v2: any, v3: any, v4: any, v5: any, v6: any,
|
view: ViewData, def: NodeDef, v0: any, v1: any, v2: any, v3: any, v4: any, v5: any, v6: any,
|
||||||
v7: any, v8: any, v9: any) {
|
v7: any, v8: any, v9: any) {
|
||||||
const provider = asProviderData(view, def.index).instance;
|
const providerData = asProviderData(view, def.index);
|
||||||
|
const directive = providerData.instance;
|
||||||
let changes: SimpleChanges;
|
let changes: SimpleChanges;
|
||||||
// Note: fallthrough is intended!
|
// Note: fallthrough is intended!
|
||||||
switch (def.bindings.length) {
|
switch (def.bindings.length) {
|
||||||
case 10:
|
case 10:
|
||||||
changes = checkAndUpdateProp(view, provider, def, 9, v9, changes);
|
changes = checkAndUpdateProp(view, providerData, def, 9, v9, changes);
|
||||||
case 9:
|
case 9:
|
||||||
changes = checkAndUpdateProp(view, provider, def, 8, v8, changes);
|
changes = checkAndUpdateProp(view, providerData, def, 8, v8, changes);
|
||||||
case 8:
|
case 8:
|
||||||
changes = checkAndUpdateProp(view, provider, def, 7, v7, changes);
|
changes = checkAndUpdateProp(view, providerData, def, 7, v7, changes);
|
||||||
case 7:
|
case 7:
|
||||||
changes = checkAndUpdateProp(view, provider, def, 6, v6, changes);
|
changes = checkAndUpdateProp(view, providerData, def, 6, v6, changes);
|
||||||
case 6:
|
case 6:
|
||||||
changes = checkAndUpdateProp(view, provider, def, 5, v5, changes);
|
changes = checkAndUpdateProp(view, providerData, def, 5, v5, changes);
|
||||||
case 5:
|
case 5:
|
||||||
changes = checkAndUpdateProp(view, provider, def, 4, v4, changes);
|
changes = checkAndUpdateProp(view, providerData, def, 4, v4, changes);
|
||||||
case 4:
|
case 4:
|
||||||
changes = checkAndUpdateProp(view, provider, def, 3, v3, changes);
|
changes = checkAndUpdateProp(view, providerData, def, 3, v3, changes);
|
||||||
case 3:
|
case 3:
|
||||||
changes = checkAndUpdateProp(view, provider, def, 2, v2, changes);
|
changes = checkAndUpdateProp(view, providerData, def, 2, v2, changes);
|
||||||
case 2:
|
case 2:
|
||||||
changes = checkAndUpdateProp(view, provider, def, 1, v1, changes);
|
changes = checkAndUpdateProp(view, providerData, def, 1, v1, changes);
|
||||||
case 1:
|
case 1:
|
||||||
changes = checkAndUpdateProp(view, provider, def, 0, v0, changes);
|
changes = checkAndUpdateProp(view, providerData, def, 0, v0, changes);
|
||||||
}
|
}
|
||||||
if (changes) {
|
if (changes) {
|
||||||
provider.ngOnChanges(changes);
|
directive.ngOnChanges(changes);
|
||||||
}
|
}
|
||||||
if ((view.state & ViewState.FirstCheck) && (def.flags & NodeFlags.OnInit)) {
|
if ((view.state & ViewState.FirstCheck) && (def.flags & NodeFlags.OnInit)) {
|
||||||
provider.ngOnInit();
|
directive.ngOnInit();
|
||||||
}
|
}
|
||||||
if (def.flags & NodeFlags.DoCheck) {
|
if (def.flags & NodeFlags.DoCheck) {
|
||||||
provider.ngDoCheck();
|
directive.ngDoCheck();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function checkAndUpdateProviderDynamic(view: ViewData, def: NodeDef, values: any[]) {
|
export function checkAndUpdateDirectiveDynamic(view: ViewData, def: NodeDef, values: any[]) {
|
||||||
const provider = asProviderData(view, def.index).instance;
|
const providerData = asProviderData(view, def.index);
|
||||||
|
const directive = providerData.instance;
|
||||||
let changes: SimpleChanges;
|
let changes: SimpleChanges;
|
||||||
for (let i = 0; i < values.length; i++) {
|
for (let i = 0; i < values.length; i++) {
|
||||||
changes = checkAndUpdateProp(view, provider, def, i, values[i], changes);
|
changes = checkAndUpdateProp(view, providerData, def, i, values[i], changes);
|
||||||
}
|
}
|
||||||
if (changes) {
|
if (changes) {
|
||||||
provider.ngOnChanges(changes);
|
directive.ngOnChanges(changes);
|
||||||
}
|
}
|
||||||
if ((view.state & ViewState.FirstCheck) && (def.flags & NodeFlags.OnInit)) {
|
if ((view.state & ViewState.FirstCheck) && (def.flags & NodeFlags.OnInit)) {
|
||||||
provider.ngOnInit();
|
directive.ngOnInit();
|
||||||
}
|
}
|
||||||
if (def.flags & NodeFlags.DoCheck) {
|
if (def.flags & NodeFlags.DoCheck) {
|
||||||
provider.ngDoCheck();
|
directive.ngDoCheck();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function createInstance(view: ViewData, nodeDef: NodeDef): any {
|
function _createProviderInstance(view: ViewData, def: NodeDef): any {
|
||||||
const providerDef = nodeDef.provider;
|
const providerDef = def.provider;
|
||||||
let injectable: any;
|
let injectable: any;
|
||||||
switch (providerDef.type) {
|
switch (providerDef.type) {
|
||||||
case ProviderType.Class:
|
case ProviderType.Class:
|
||||||
injectable =
|
injectable = createClass(view, def.index, def.parent, providerDef.value, providerDef.deps);
|
||||||
createClass(view, nodeDef.index, nodeDef.parent, providerDef.value, providerDef.deps);
|
|
||||||
break;
|
break;
|
||||||
case ProviderType.Factory:
|
case ProviderType.Factory:
|
||||||
injectable =
|
injectable = callFactory(view, def.index, def.parent, providerDef.value, providerDef.deps);
|
||||||
callFactory(view, nodeDef.index, nodeDef.parent, providerDef.value, providerDef.deps);
|
|
||||||
break;
|
break;
|
||||||
case ProviderType.UseExisting:
|
case ProviderType.UseExisting:
|
||||||
injectable = resolveDep(view, nodeDef.index, nodeDef.parent, providerDef.deps[0]);
|
injectable = resolveDep(view, def.index, def.parent, providerDef.deps[0]);
|
||||||
break;
|
break;
|
||||||
case ProviderType.Value:
|
case ProviderType.Value:
|
||||||
injectable = providerDef.value;
|
injectable = providerDef.value;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (providerDef.outputs.length) {
|
|
||||||
for (let i = 0; i < providerDef.outputs.length; i++) {
|
|
||||||
const output = providerDef.outputs[i];
|
|
||||||
const subscription = injectable[output.propName].subscribe(
|
|
||||||
eventHandlerClosure(view, nodeDef.parent, output.eventName));
|
|
||||||
view.disposables[nodeDef.disposableIndex + i] = subscription.unsubscribe.bind(subscription);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return injectable;
|
return injectable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -291,7 +318,7 @@ export function resolveDep(
|
||||||
requestNodeIndex = null;
|
requestNodeIndex = null;
|
||||||
elIndex = view.def.nodes[elIndex].parent;
|
elIndex = view.def.nodes[elIndex].parent;
|
||||||
while (elIndex == null && view) {
|
while (elIndex == null && view) {
|
||||||
elIndex = viewParentDiIndex(view);
|
elIndex = viewParentElIndex(view);
|
||||||
view = view.parent;
|
view = view.parent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -334,20 +361,20 @@ export function resolveDep(
|
||||||
if (providerIndex != null) {
|
if (providerIndex != null) {
|
||||||
const providerData = asProviderData(view, providerIndex);
|
const providerData = asProviderData(view, providerIndex);
|
||||||
if (providerData.instance === NOT_CREATED) {
|
if (providerData.instance === NOT_CREATED) {
|
||||||
providerData.instance = createInstance(view, view.def.nodes[providerIndex]);
|
providerData.instance = _createProviderInstance(view, view.def.nodes[providerIndex]);
|
||||||
}
|
}
|
||||||
return providerData.instance;
|
return providerData.instance;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
requestNodeIndex = null;
|
requestNodeIndex = null;
|
||||||
elIndex = viewParentDiIndex(view);
|
elIndex = viewParentElIndex(view);
|
||||||
view = view.parent;
|
view = view.parent;
|
||||||
}
|
}
|
||||||
return startView.root.injector.get(depDef.token, notFoundValue);
|
return startView.root.injector.get(depDef.token, notFoundValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkAndUpdateProp(
|
function checkAndUpdateProp(
|
||||||
view: ViewData, provider: any, def: NodeDef, bindingIdx: number, value: any,
|
view: ViewData, providerData: ProviderData, def: NodeDef, bindingIdx: number, value: any,
|
||||||
changes: SimpleChanges): SimpleChanges {
|
changes: SimpleChanges): SimpleChanges {
|
||||||
let change: SimpleChange;
|
let change: SimpleChange;
|
||||||
let changed: boolean;
|
let changed: boolean;
|
||||||
|
@ -361,13 +388,18 @@ function checkAndUpdateProp(
|
||||||
changed = checkAndUpdateBinding(view, def, bindingIdx, value);
|
changed = checkAndUpdateBinding(view, def, bindingIdx, value);
|
||||||
}
|
}
|
||||||
if (changed) {
|
if (changed) {
|
||||||
value = unwrapValue(value);
|
if (def.flags & NodeFlags.HasComponent) {
|
||||||
|
const compView = providerData.componentView;
|
||||||
|
if (compView.def.flags & ViewFlags.OnPush) {
|
||||||
|
compView.state |= ViewState.ChecksEnabled;
|
||||||
|
}
|
||||||
|
}
|
||||||
const binding = def.bindings[bindingIdx];
|
const binding = def.bindings[bindingIdx];
|
||||||
const propName = binding.name;
|
const propName = binding.name;
|
||||||
// Note: This is still safe with Closure Compiler as
|
// Note: This is still safe with Closure Compiler as
|
||||||
// the user passed in the property name as an object has to `providerDef`,
|
// the user passed in the property name as an object has to `providerDef`,
|
||||||
// so Closure Compiler will have renamed the property correctly already.
|
// so Closure Compiler will have renamed the property correctly already.
|
||||||
provider[propName] = value;
|
providerData.instance[propName] = value;
|
||||||
if (change) {
|
if (change) {
|
||||||
changes = changes || {};
|
changes = changes || {};
|
||||||
changes[binding.nonMinifiedName] = change;
|
changes[binding.nonMinifiedName] = change;
|
||||||
|
@ -382,7 +414,7 @@ export function callLifecycleHooksChildrenFirst(view: ViewData, lifecycles: Node
|
||||||
}
|
}
|
||||||
const len = view.def.nodes.length;
|
const len = view.def.nodes.length;
|
||||||
for (let i = 0; i < len; i++) {
|
for (let i = 0; i < len; i++) {
|
||||||
// We use the provider post order to call providers of children first.
|
// We use the reverse child oreder to call providers of children first.
|
||||||
const nodeDef = view.def.reverseChildNodes[i];
|
const nodeDef = view.def.reverseChildNodes[i];
|
||||||
const nodeIndex = nodeDef.index;
|
const nodeIndex = nodeDef.index;
|
||||||
if (nodeDef.flags & lifecycles) {
|
if (nodeDef.flags & lifecycles) {
|
||||||
|
|
|
@ -7,24 +7,22 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {BindingDef, BindingType, DepDef, DepFlags, NodeData, NodeDef, NodeType, ProviderData, PureExpressionData, PureExpressionType, Services, ViewData, asPureExpressionData} from './types';
|
import {BindingDef, BindingType, DepDef, DepFlags, NodeData, NodeDef, NodeType, ProviderData, PureExpressionData, PureExpressionType, Services, ViewData, asPureExpressionData} from './types';
|
||||||
import {checkAndUpdateBinding, tokenKey, unwrapValue} from './util';
|
import {checkAndUpdateBinding, tokenKey} from './util';
|
||||||
|
|
||||||
export function purePipeDef(pipeToken: any, argCount: number): NodeDef {
|
export function purePipeDef(argCount: number): NodeDef {
|
||||||
return _pureExpressionDef(
|
// argCount + 1 to include the pipe as first arg
|
||||||
PureExpressionType.Pipe, new Array(argCount),
|
return _pureExpressionDef(PureExpressionType.Pipe, new Array(argCount + 1));
|
||||||
{token: pipeToken, tokenKey: tokenKey(pipeToken), flags: DepFlags.None});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function pureArrayDef(argCount: number): NodeDef {
|
export function pureArrayDef(argCount: number): NodeDef {
|
||||||
return _pureExpressionDef(PureExpressionType.Array, new Array(argCount), undefined);
|
return _pureExpressionDef(PureExpressionType.Array, new Array(argCount));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function pureObjectDef(propertyNames: string[]): NodeDef {
|
export function pureObjectDef(propertyNames: string[]): NodeDef {
|
||||||
return _pureExpressionDef(PureExpressionType.Object, propertyNames, undefined);
|
return _pureExpressionDef(PureExpressionType.Object, propertyNames);
|
||||||
}
|
}
|
||||||
|
|
||||||
function _pureExpressionDef(
|
function _pureExpressionDef(type: PureExpressionType, propertyNames: string[]): NodeDef {
|
||||||
type: PureExpressionType, propertyNames: string[], pipeDep: DepDef): NodeDef {
|
|
||||||
const bindings: BindingDef[] = new Array(propertyNames.length);
|
const bindings: BindingDef[] = new Array(propertyNames.length);
|
||||||
for (let i = 0; i < propertyNames.length; i++) {
|
for (let i = 0; i < propertyNames.length; i++) {
|
||||||
const prop = propertyNames[i];
|
const prop = propertyNames[i];
|
||||||
|
@ -55,17 +53,14 @@ function _pureExpressionDef(
|
||||||
element: undefined,
|
element: undefined,
|
||||||
provider: undefined,
|
provider: undefined,
|
||||||
text: undefined,
|
text: undefined,
|
||||||
pureExpression: {type, pipeDep},
|
pureExpression: {type},
|
||||||
query: undefined,
|
query: undefined,
|
||||||
ngContent: undefined
|
ngContent: undefined
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createPureExpression(view: ViewData, def: NodeDef): PureExpressionData {
|
export function createPureExpression(view: ViewData, def: NodeDef): PureExpressionData {
|
||||||
const pipe = def.pureExpression.pipeDep ?
|
return {value: undefined};
|
||||||
Services.resolveDep(view, def.index, def.parent, def.pureExpression.pipeDep) :
|
|
||||||
undefined;
|
|
||||||
return {value: undefined, pipe};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function checkAndUpdatePureExpressionInline(
|
export function checkAndUpdatePureExpressionInline(
|
||||||
|
@ -99,17 +94,6 @@ export function checkAndUpdatePureExpressionInline(
|
||||||
|
|
||||||
const data = asPureExpressionData(view, def.index);
|
const data = asPureExpressionData(view, def.index);
|
||||||
if (changed) {
|
if (changed) {
|
||||||
v0 = unwrapValue(v0);
|
|
||||||
v1 = unwrapValue(v1);
|
|
||||||
v2 = unwrapValue(v2);
|
|
||||||
v3 = unwrapValue(v3);
|
|
||||||
v4 = unwrapValue(v4);
|
|
||||||
v5 = unwrapValue(v5);
|
|
||||||
v6 = unwrapValue(v6);
|
|
||||||
v7 = unwrapValue(v7);
|
|
||||||
v8 = unwrapValue(v8);
|
|
||||||
v9 = unwrapValue(v9);
|
|
||||||
|
|
||||||
let value: any;
|
let value: any;
|
||||||
switch (def.pureExpression.type) {
|
switch (def.pureExpression.type) {
|
||||||
case PureExpressionType.Array:
|
case PureExpressionType.Array:
|
||||||
|
@ -165,36 +149,37 @@ export function checkAndUpdatePureExpressionInline(
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case PureExpressionType.Pipe:
|
case PureExpressionType.Pipe:
|
||||||
|
const pipe = v0;
|
||||||
switch (bindings.length) {
|
switch (bindings.length) {
|
||||||
case 10:
|
case 10:
|
||||||
value = data.pipe.transform(v0, v1, v2, v3, v4, v5, v6, v7, v8, v9);
|
value = pipe.transform(v1, v2, v3, v4, v5, v6, v7, v8, v9);
|
||||||
break;
|
break;
|
||||||
case 9:
|
case 9:
|
||||||
value = data.pipe.transform(v0, v1, v2, v3, v4, v5, v6, v7, v8);
|
value = pipe.transform(v1, v2, v3, v4, v5, v6, v7, v8);
|
||||||
break;
|
break;
|
||||||
case 8:
|
case 8:
|
||||||
value = data.pipe.transform(v0, v1, v2, v3, v4, v5, v6, v7);
|
value = pipe.transform(v1, v2, v3, v4, v5, v6, v7);
|
||||||
break;
|
break;
|
||||||
case 7:
|
case 7:
|
||||||
value = data.pipe.transform(v0, v1, v2, v3, v4, v5, v6);
|
value = pipe.transform(v1, v2, v3, v4, v5, v6);
|
||||||
break;
|
break;
|
||||||
case 6:
|
case 6:
|
||||||
value = data.pipe.transform(v0, v1, v2, v3, v4, v5);
|
value = pipe.transform(v1, v2, v3, v4, v5);
|
||||||
break;
|
break;
|
||||||
case 5:
|
case 5:
|
||||||
value = data.pipe.transform(v0, v1, v2, v3, v4);
|
value = pipe.transform(v1, v2, v3, v4);
|
||||||
break;
|
break;
|
||||||
case 4:
|
case 4:
|
||||||
value = data.pipe.transform(v0, v1, v2, v3);
|
value = pipe.transform(v1, v2, v3);
|
||||||
break;
|
break;
|
||||||
case 3:
|
case 3:
|
||||||
value = data.pipe.transform(v0, v1, v2);
|
value = pipe.transform(v1, v2);
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
value = data.pipe.transform(v0, v1);
|
value = pipe.transform(v1);
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
value = data.pipe.transform(v0);
|
value = pipe.transform(v0);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -219,23 +204,18 @@ export function checkAndUpdatePureExpressionDynamic(view: ViewData, def: NodeDef
|
||||||
let value: any;
|
let value: any;
|
||||||
switch (def.pureExpression.type) {
|
switch (def.pureExpression.type) {
|
||||||
case PureExpressionType.Array:
|
case PureExpressionType.Array:
|
||||||
value = new Array(values.length);
|
value = values;
|
||||||
for (let i = 0; i < values.length; i++) {
|
|
||||||
value[i] = unwrapValue(values[i]);
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case PureExpressionType.Object:
|
case PureExpressionType.Object:
|
||||||
value = {};
|
value = {};
|
||||||
for (let i = 0; i < values.length; i++) {
|
for (let i = 0; i < values.length; i++) {
|
||||||
value[bindings[i].name] = unwrapValue(values[i]);
|
value[bindings[i].name] = values[i];
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case PureExpressionType.Pipe:
|
case PureExpressionType.Pipe:
|
||||||
const params = new Array(values.length);
|
const pipe = values[0];
|
||||||
for (let i = 0; i < values.length; i++) {
|
const params = values.slice(1);
|
||||||
params[i] = unwrapValue(values[i]);
|
value = (<any>pipe.transform)(...params);
|
||||||
}
|
|
||||||
value = (<any>data.pipe.transform)(...params);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
data.value = value;
|
data.value = value;
|
||||||
|
|
|
@ -13,7 +13,7 @@ import {ViewContainerRef} from '../linker/view_container_ref';
|
||||||
|
|
||||||
import {createTemplateRef, createViewContainerRef} from './refs';
|
import {createTemplateRef, createViewContainerRef} from './refs';
|
||||||
import {NodeDef, NodeFlags, NodeType, QueryBindingDef, QueryBindingType, QueryDef, QueryValueType, Services, ViewData, asElementData, asProviderData, asQueryList} from './types';
|
import {NodeDef, NodeFlags, NodeType, QueryBindingDef, QueryBindingType, QueryDef, QueryValueType, Services, ViewData, asElementData, asProviderData, asQueryList} from './types';
|
||||||
import {declaredViewContainer} from './util';
|
import {declaredViewContainer, viewParentElIndex} from './util';
|
||||||
|
|
||||||
export function queryDef(
|
export function queryDef(
|
||||||
flags: NodeFlags, id: string, bindings: {[propName: string]: QueryBindingType}): NodeDef {
|
flags: NodeFlags, id: string, bindings: {[propName: string]: QueryBindingType}): NodeDef {
|
||||||
|
@ -54,16 +54,18 @@ export function createQuery(): QueryList<any> {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function dirtyParentQuery(queryId: string, view: ViewData) {
|
export function dirtyParentQuery(queryId: string, view: ViewData) {
|
||||||
let nodeIndex = view.parentIndex;
|
let elIndex = viewParentElIndex(view);
|
||||||
view = view.parent;
|
view = view.parent;
|
||||||
let queryIdx: number;
|
let queryIdx: number;
|
||||||
while (view) {
|
while (view) {
|
||||||
const elementDef = view.def.nodes[nodeIndex];
|
if (elIndex != null) {
|
||||||
queryIdx = elementDef.element.providerIndices[queryId];
|
const elementDef = view.def.nodes[elIndex];
|
||||||
if (queryIdx != null) {
|
queryIdx = elementDef.element.providerIndices[queryId];
|
||||||
break;
|
if (queryIdx != null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
nodeIndex = view.parentIndex;
|
elIndex = viewParentElIndex(view);
|
||||||
view = view.parent;
|
view = view.parent;
|
||||||
}
|
}
|
||||||
if (!view) {
|
if (!view) {
|
||||||
|
|
|
@ -19,8 +19,8 @@ import {Sanitizer, SecurityContext} from '../security';
|
||||||
import {Type} from '../type';
|
import {Type} from '../type';
|
||||||
|
|
||||||
import {DirectDomRenderer, LegacyRendererAdapter} from './renderer';
|
import {DirectDomRenderer, LegacyRendererAdapter} from './renderer';
|
||||||
import {ArgumentType, BindingType, DebugContext, DepFlags, ElementData, NodeCheckFn, NodeData, NodeDef, NodeType, RendererV2, RootData, Services, ViewData, ViewDefinition, ViewDefinitionFactory, ViewState, asElementData, asProviderData} from './types';
|
import {ArgumentType, BindingType, DebugContext, DepFlags, ElementData, NodeCheckFn, NodeData, NodeDef, NodeFlags, NodeType, RendererV2, RootData, Services, ViewData, ViewDefinition, ViewDefinitionFactory, ViewState, asElementData, asProviderData} from './types';
|
||||||
import {isComponentView, renderNode, resolveViewDefinition, rootRenderNodes, tokenKey, viewParentDiIndex} from './util';
|
import {isComponentView, renderNode, resolveViewDefinition, rootRenderNodes, tokenKey, viewParentElIndex} from './util';
|
||||||
|
|
||||||
const EMPTY_CONTEXT = new Object();
|
const EMPTY_CONTEXT = new Object();
|
||||||
|
|
||||||
|
@ -53,7 +53,7 @@ class ComponentFactory_ implements ComponentFactory<any> {
|
||||||
const len = viewDef.nodes.length;
|
const len = viewDef.nodes.length;
|
||||||
for (let i = 0; i < len; i++) {
|
for (let i = 0; i < len; i++) {
|
||||||
const nodeDef = viewDef.nodes[i];
|
const nodeDef = viewDef.nodes[i];
|
||||||
if (nodeDef.provider && nodeDef.provider.component) {
|
if (nodeDef.flags & NodeFlags.HasComponent) {
|
||||||
componentNodeIndex = i;
|
componentNodeIndex = i;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -99,7 +99,7 @@ class ViewContainerRef_ implements ViewContainerRef {
|
||||||
let view = this._view;
|
let view = this._view;
|
||||||
let elIndex = view.def.nodes[this._elIndex].parent;
|
let elIndex = view.def.nodes[this._elIndex].parent;
|
||||||
while (elIndex == null && view) {
|
while (elIndex == null && view) {
|
||||||
elIndex = viewParentDiIndex(view);
|
elIndex = viewParentElIndex(view);
|
||||||
view = view.parent;
|
view = view.parent;
|
||||||
}
|
}
|
||||||
return view ? new Injector_(view, elIndex) : this._view.root.injector;
|
return view ? new Injector_(view, elIndex) : this._view.root.injector;
|
||||||
|
|
|
@ -19,8 +19,8 @@ import {resolveDep} from './provider';
|
||||||
import {getQueryValue} from './query';
|
import {getQueryValue} from './query';
|
||||||
import {createInjector} from './refs';
|
import {createInjector} from './refs';
|
||||||
import {DirectDomRenderer, LegacyRendererAdapter} from './renderer';
|
import {DirectDomRenderer, LegacyRendererAdapter} from './renderer';
|
||||||
import {ArgumentType, BindingType, DebugContext, DepFlags, ElementData, NodeCheckFn, NodeData, NodeDef, NodeFlags, NodeType, RendererV2, RootData, Services, ViewData, ViewDefinition, ViewDefinitionFactory, ViewState, asElementData, asProviderData} from './types';
|
import {ArgumentType, BindingType, DebugContext, DepFlags, ElementData, NodeCheckFn, NodeData, NodeDef, NodeFlags, NodeType, RendererV2, RootData, Services, ViewData, ViewDefinition, ViewDefinitionFactory, ViewState, ViewUpdateFn, asElementData, asProviderData} from './types';
|
||||||
import {checkBinding, isComponentView, queryIdIsReference, renderNode, resolveViewDefinition, rootRenderNodes, viewParentDiIndex} from './util';
|
import {checkBinding, isComponentView, queryIdIsReference, renderNode, resolveViewDefinition, rootRenderNodes, viewParentElIndex} from './util';
|
||||||
import {checkAndUpdateView, checkNoChangesView, createEmbeddedView, createRootView, destroyView} from './view';
|
import {checkAndUpdateView, checkNoChangesView, createEmbeddedView, createRootView, destroyView} from './view';
|
||||||
import {attachEmbeddedView, detachEmbeddedView, moveEmbeddedView} from './view_attach';
|
import {attachEmbeddedView, detachEmbeddedView, moveEmbeddedView} from './view_attach';
|
||||||
|
|
||||||
|
@ -44,7 +44,8 @@ export function initServicesIfNeeded() {
|
||||||
Services.resolveDep = services.resolveDep;
|
Services.resolveDep = services.resolveDep;
|
||||||
Services.createDebugContext = services.createDebugContext;
|
Services.createDebugContext = services.createDebugContext;
|
||||||
Services.handleEvent = services.handleEvent;
|
Services.handleEvent = services.handleEvent;
|
||||||
Services.updateView = services.updateView;
|
Services.updateDirectives = services.updateDirectives;
|
||||||
|
Services.updateRenderer = services.updateRenderer;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createProdServices() {
|
function createProdServices() {
|
||||||
|
@ -62,7 +63,9 @@ function createProdServices() {
|
||||||
createDebugContext: (view: ViewData, nodeIndex: number) => new DebugContext_(view, nodeIndex),
|
createDebugContext: (view: ViewData, nodeIndex: number) => new DebugContext_(view, nodeIndex),
|
||||||
handleEvent: (view: ViewData, nodeIndex: number, eventName: string, event: any) =>
|
handleEvent: (view: ViewData, nodeIndex: number, eventName: string, event: any) =>
|
||||||
view.def.handleEvent(view, nodeIndex, eventName, event),
|
view.def.handleEvent(view, nodeIndex, eventName, event),
|
||||||
updateView: (check: NodeCheckFn, view: ViewData) => view.def.update(check, view)
|
updateDirectives: (check: NodeCheckFn, view: ViewData) =>
|
||||||
|
view.def.updateDirectives(check, view),
|
||||||
|
updateRenderer: (check: NodeCheckFn, view: ViewData) => view.def.updateRenderer(check, view),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,7 +83,8 @@ function createDebugServices() {
|
||||||
resolveDep: resolveDep,
|
resolveDep: resolveDep,
|
||||||
createDebugContext: (view: ViewData, nodeIndex: number) => new DebugContext_(view, nodeIndex),
|
createDebugContext: (view: ViewData, nodeIndex: number) => new DebugContext_(view, nodeIndex),
|
||||||
handleEvent: debugHandleEvent,
|
handleEvent: debugHandleEvent,
|
||||||
updateView: debugUpdateView
|
updateDirectives: debugUpdateDirectives,
|
||||||
|
updateRenderer: debugUpdateRenderer
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -152,37 +156,56 @@ function debugHandleEvent(view: ViewData, nodeIndex: number, eventName: string,
|
||||||
'handleEvent', view.def.handleEvent, null, [view, nodeIndex, eventName, event]);
|
'handleEvent', view.def.handleEvent, null, [view, nodeIndex, eventName, event]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function debugUpdateView(check: NodeCheckFn, view: ViewData) {
|
function debugUpdateDirectives(check: NodeCheckFn, view: ViewData) {
|
||||||
if (view.state & ViewState.Destroyed) {
|
if (view.state & ViewState.Destroyed) {
|
||||||
throw viewDestroyedError(_currentAction);
|
throw viewDestroyedError(_currentAction);
|
||||||
}
|
}
|
||||||
debugSetCurrentNode(view, nextNodeIndexWithBinding(view, 0));
|
debugSetCurrentNode(view, nextDirectiveWithBinding(view, 0));
|
||||||
return view.def.update(debugCheckFn, view);
|
return view.def.updateDirectives(debugCheckDirectivesFn, view);
|
||||||
|
|
||||||
function debugCheckFn(
|
function debugCheckDirectivesFn(
|
||||||
view: ViewData, nodeIndex: number, argStyle: ArgumentType, v0?: any, v1?: any, v2?: any,
|
view: ViewData, nodeIndex: number, argStyle: ArgumentType, ...values: any[]) {
|
||||||
v3?: any, v4?: any, v5?: any, v6?: any, v7?: any, v8?: any, v9?: any) {
|
const result = debugCheckFn(check, view, nodeIndex, argStyle, values);
|
||||||
const values = argStyle === ArgumentType.Dynamic ? v0 : [].slice.call(arguments, 3);
|
debugSetCurrentNode(view, nextDirectiveWithBinding(view, nodeIndex));
|
||||||
const nodeDef = view.def.nodes[nodeIndex];
|
|
||||||
for (let i = 0; i < nodeDef.bindings.length; i++) {
|
|
||||||
const binding = nodeDef.bindings[i];
|
|
||||||
const value = values[i];
|
|
||||||
if ((binding.type === BindingType.ElementProperty ||
|
|
||||||
binding.type === BindingType.ProviderProperty) &&
|
|
||||||
checkBinding(view, nodeDef, i, value)) {
|
|
||||||
const elIndex = nodeDef.type === NodeType.Provider ? nodeDef.parent : nodeDef.index;
|
|
||||||
setBindingDebugInfo(
|
|
||||||
view.root.renderer, asElementData(view, elIndex).renderElement, binding.nonMinifiedName,
|
|
||||||
value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const result = check(view, nodeIndex, <any>argStyle, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9);
|
|
||||||
|
|
||||||
debugSetCurrentNode(view, nextNodeIndexWithBinding(view, nodeIndex));
|
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function debugUpdateRenderer(check: NodeCheckFn, view: ViewData) {
|
||||||
|
if (view.state & ViewState.Destroyed) {
|
||||||
|
throw viewDestroyedError(_currentAction);
|
||||||
|
}
|
||||||
|
debugSetCurrentNode(view, nextRenderNodeWithBinding(view, 0));
|
||||||
|
return view.def.updateRenderer(debugCheckRenderNodeFn, view);
|
||||||
|
|
||||||
|
function debugCheckRenderNodeFn(
|
||||||
|
view: ViewData, nodeIndex: number, argStyle: ArgumentType, ...values: any[]) {
|
||||||
|
const result = debugCheckFn(check, view, nodeIndex, argStyle, values);
|
||||||
|
debugSetCurrentNode(view, nextRenderNodeWithBinding(view, nodeIndex));
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function debugCheckFn(
|
||||||
|
delegate: NodeCheckFn, view: ViewData, nodeIndex: number, argStyle: ArgumentType,
|
||||||
|
givenValues: any[]) {
|
||||||
|
const values = argStyle === ArgumentType.Dynamic ? givenValues[0] : givenValues;
|
||||||
|
const nodeDef = view.def.nodes[nodeIndex];
|
||||||
|
for (let i = 0; i < nodeDef.bindings.length; i++) {
|
||||||
|
const binding = nodeDef.bindings[i];
|
||||||
|
const value = values[i];
|
||||||
|
if ((binding.type === BindingType.ElementProperty ||
|
||||||
|
binding.type === BindingType.DirectiveProperty) &&
|
||||||
|
checkBinding(view, nodeDef, i, value)) {
|
||||||
|
const elIndex = nodeDef.type === NodeType.Directive ? nodeDef.parent : nodeDef.index;
|
||||||
|
setBindingDebugInfo(
|
||||||
|
view.root.renderer, asElementData(view, elIndex).renderElement, binding.nonMinifiedName,
|
||||||
|
value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (<any>delegate)(view, nodeIndex, argStyle, ...givenValues);
|
||||||
|
};
|
||||||
|
|
||||||
function setBindingDebugInfo(renderer: RendererV2, renderNode: any, propName: string, value: any) {
|
function setBindingDebugInfo(renderer: RendererV2, renderNode: any, propName: string, value: any) {
|
||||||
const renderName = `ng-reflect-${camelCaseToDashCase(propName)}`;
|
const renderName = `ng-reflect-${camelCaseToDashCase(propName)}`;
|
||||||
if (value) {
|
if (value) {
|
||||||
|
@ -203,16 +226,26 @@ function camelCaseToDashCase(input: string): string {
|
||||||
return input.replace(CAMEL_CASE_REGEXP, (...m: any[]) => '-' + m[1].toLowerCase());
|
return input.replace(CAMEL_CASE_REGEXP, (...m: any[]) => '-' + m[1].toLowerCase());
|
||||||
}
|
}
|
||||||
|
|
||||||
function nextNodeIndexWithBinding(view: ViewData, nodeIndex: number): number {
|
function nextDirectiveWithBinding(view: ViewData, nodeIndex: number): number {
|
||||||
for (let i = nodeIndex; i < view.def.nodes.length; i++) {
|
for (let i = nodeIndex; i < view.def.nodes.length; i++) {
|
||||||
const nodeDef = view.def.nodes[i];
|
const nodeDef = view.def.nodes[i];
|
||||||
if (nodeDef.bindings && nodeDef.bindings.length) {
|
if (nodeDef.type === NodeType.Directive && nodeDef.bindings && nodeDef.bindings.length) {
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function nextRenderNodeWithBinding(view: ViewData, nodeIndex: number): number {
|
||||||
|
for (let i = nodeIndex; i < view.def.nodes.length; i++) {
|
||||||
|
const nodeDef = view.def.nodes[i];
|
||||||
|
if ((nodeDef.type === NodeType.Element || nodeDef.type === NodeType.Text) && nodeDef.bindings &&
|
||||||
|
nodeDef.bindings.length) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
class DebugRenderer implements RendererV2 {
|
class DebugRenderer implements RendererV2 {
|
||||||
constructor(private _delegate: RendererV2) {}
|
constructor(private _delegate: RendererV2) {}
|
||||||
|
@ -282,7 +315,7 @@ class DebugContext_ implements DebugContext {
|
||||||
}
|
}
|
||||||
if (elIndex == null) {
|
if (elIndex == null) {
|
||||||
while (elIndex == null && elView) {
|
while (elIndex == null && elView) {
|
||||||
elIndex = viewParentDiIndex(elView);
|
elIndex = viewParentElIndex(elView);
|
||||||
elView = elView.parent;
|
elView = elView.parent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -320,7 +353,7 @@ class DebugContext_ implements DebugContext {
|
||||||
if (this.elDef) {
|
if (this.elDef) {
|
||||||
for (let i = this.elDef.index + 1; i <= this.elDef.index + this.elDef.childCount; i++) {
|
for (let i = this.elDef.index + 1; i <= this.elDef.index + this.elDef.childCount; i++) {
|
||||||
const childDef = this.elView.def.nodes[i];
|
const childDef = this.elView.def.nodes[i];
|
||||||
if (childDef.type === NodeType.Provider) {
|
if (childDef.type === NodeType.Provider || childDef.type === NodeType.Directive) {
|
||||||
tokens.push(childDef.provider.token);
|
tokens.push(childDef.provider.token);
|
||||||
}
|
}
|
||||||
i += childDef.childCount;
|
i += childDef.childCount;
|
||||||
|
@ -335,7 +368,7 @@ class DebugContext_ implements DebugContext {
|
||||||
|
|
||||||
for (let i = this.elDef.index + 1; i <= this.elDef.index + this.elDef.childCount; i++) {
|
for (let i = this.elDef.index + 1; i <= this.elDef.index + this.elDef.childCount; i++) {
|
||||||
const childDef = this.elView.def.nodes[i];
|
const childDef = this.elView.def.nodes[i];
|
||||||
if (childDef.type === NodeType.Provider) {
|
if (childDef.type === NodeType.Provider || childDef.type === NodeType.Directive) {
|
||||||
collectReferences(this.elView, childDef, references);
|
collectReferences(this.elView, childDef, references);
|
||||||
}
|
}
|
||||||
i += childDef.childCount;
|
i += childDef.childCount;
|
||||||
|
@ -368,7 +401,7 @@ function findHostElement(view: ViewData): ElementData {
|
||||||
view = view.parent;
|
view = view.parent;
|
||||||
}
|
}
|
||||||
if (view.parent) {
|
if (view.parent) {
|
||||||
const hostData = asElementData(view.parent, view.parentIndex);
|
const hostData = asElementData(view.parent, viewParentElIndex(view));
|
||||||
return hostData;
|
return hostData;
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
|
@ -397,6 +430,7 @@ function callWithDebugContext(action: string, fn: any, self: any, args: any[]) {
|
||||||
if (isViewDebugError(e) || !_currentView) {
|
if (isViewDebugError(e) || !_currentView) {
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
|
_currentView.state |= ViewState.Errored;
|
||||||
throw viewWrappedDebugError(e, getCurrentDebugContext());
|
throw viewWrappedDebugError(e, getCurrentDebugContext());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ import {isDevMode} from '../application_ref';
|
||||||
import {looseIdentical} from '../facade/lang';
|
import {looseIdentical} from '../facade/lang';
|
||||||
|
|
||||||
import {BindingDef, BindingType, DebugContext, NodeData, NodeDef, NodeFlags, NodeType, RootData, Services, TextData, ViewData, ViewFlags, asElementData, asTextData} from './types';
|
import {BindingDef, BindingType, DebugContext, NodeData, NodeDef, NodeFlags, NodeType, RootData, Services, TextData, ViewData, ViewFlags, asElementData, asTextData} from './types';
|
||||||
import {checkAndUpdateBinding, sliceErrorStack, unwrapValue} from './util';
|
import {checkAndUpdateBinding, sliceErrorStack} from './util';
|
||||||
|
|
||||||
export function textDef(ngContentIndex: number, constants: string[]): NodeDef {
|
export function textDef(ngContentIndex: number, constants: string[]): NodeDef {
|
||||||
// skip the call to sliceErrorStack itself + the call to this function.
|
// skip the call to sliceErrorStack itself + the call to this function.
|
||||||
|
@ -18,7 +18,7 @@ export function textDef(ngContentIndex: number, constants: string[]): NodeDef {
|
||||||
const bindings: BindingDef[] = new Array(constants.length - 1);
|
const bindings: BindingDef[] = new Array(constants.length - 1);
|
||||||
for (let i = 1; i < constants.length; i++) {
|
for (let i = 1; i < constants.length; i++) {
|
||||||
bindings[i - 1] = {
|
bindings[i - 1] = {
|
||||||
type: BindingType.Interpolation,
|
type: BindingType.TextInterpolation,
|
||||||
name: undefined,
|
name: undefined,
|
||||||
nonMinifiedName: undefined,
|
nonMinifiedName: undefined,
|
||||||
securityContext: undefined,
|
securityContext: undefined,
|
||||||
|
@ -143,7 +143,6 @@ export function checkAndUpdateTextDynamic(view: ViewData, def: NodeDef, values:
|
||||||
}
|
}
|
||||||
|
|
||||||
function _addInterpolationPart(value: any, binding: BindingDef): string {
|
function _addInterpolationPart(value: any, binding: BindingDef): string {
|
||||||
value = unwrapValue(value);
|
|
||||||
const valueStr = value != null ? value.toString() : '';
|
const valueStr = value != null ? value.toString() : '';
|
||||||
return valueStr + binding.suffix;
|
return valueStr + binding.suffix;
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,8 @@ import {Sanitizer, SecurityContext} from '../security';
|
||||||
export interface ViewDefinition {
|
export interface ViewDefinition {
|
||||||
flags: ViewFlags;
|
flags: ViewFlags;
|
||||||
component: ComponentDefinition;
|
component: ComponentDefinition;
|
||||||
update: ViewUpdateFn;
|
updateDirectives: ViewUpdateFn;
|
||||||
|
updateRenderer: ViewUpdateFn;
|
||||||
handleEvent: ViewHandleEventFn;
|
handleEvent: ViewHandleEventFn;
|
||||||
/**
|
/**
|
||||||
* Order: Depth first.
|
* Order: Depth first.
|
||||||
|
@ -124,7 +125,9 @@ export interface NodeDef {
|
||||||
export enum NodeType {
|
export enum NodeType {
|
||||||
Element,
|
Element,
|
||||||
Text,
|
Text,
|
||||||
|
Directive,
|
||||||
Provider,
|
Provider,
|
||||||
|
Pipe,
|
||||||
PureExpression,
|
PureExpression,
|
||||||
Query,
|
Query,
|
||||||
NgContent
|
NgContent
|
||||||
|
@ -163,8 +166,8 @@ export enum BindingType {
|
||||||
ElementClass,
|
ElementClass,
|
||||||
ElementStyle,
|
ElementStyle,
|
||||||
ElementProperty,
|
ElementProperty,
|
||||||
ProviderProperty,
|
DirectiveProperty,
|
||||||
Interpolation,
|
TextInterpolation,
|
||||||
PureExpressionProperty
|
PureExpressionProperty
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -200,7 +203,7 @@ export interface ProviderDef {
|
||||||
tokenKey: string;
|
tokenKey: string;
|
||||||
value: any;
|
value: any;
|
||||||
deps: DepDef[];
|
deps: DepDef[];
|
||||||
outputs: ProviderOutputDef[];
|
outputs: DirectiveOutputDef[];
|
||||||
// closure to allow recursive components
|
// closure to allow recursive components
|
||||||
component: ViewDefinitionFactory;
|
component: ViewDefinitionFactory;
|
||||||
}
|
}
|
||||||
|
@ -228,7 +231,7 @@ export enum DepFlags {
|
||||||
Value = 2 << 2
|
Value = 2 << 2
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ProviderOutputDef {
|
export interface DirectiveOutputDef {
|
||||||
propName: string;
|
propName: string;
|
||||||
eventName: string;
|
eventName: string;
|
||||||
}
|
}
|
||||||
|
@ -238,10 +241,7 @@ export interface TextDef {
|
||||||
source: string;
|
source: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PureExpressionDef {
|
export interface PureExpressionDef { type: PureExpressionType; }
|
||||||
type: PureExpressionType;
|
|
||||||
pipeDep: DepDef;
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum PureExpressionType {
|
export enum PureExpressionType {
|
||||||
Array,
|
Array,
|
||||||
|
@ -285,8 +285,7 @@ export interface NgContentDef {
|
||||||
export interface ViewData {
|
export interface ViewData {
|
||||||
def: ViewDefinition;
|
def: ViewDefinition;
|
||||||
root: RootData;
|
root: RootData;
|
||||||
// index of parent element / anchor. Not the index
|
// index of component provider / anchor.
|
||||||
// of the provider with the component view.
|
|
||||||
parentIndex: number;
|
parentIndex: number;
|
||||||
parent: ViewData;
|
parent: ViewData;
|
||||||
component: any;
|
component: any;
|
||||||
|
@ -385,10 +384,7 @@ export function asProviderData(view: ViewData, index: number): ProviderData {
|
||||||
*
|
*
|
||||||
* Attention: Adding fields to this is performance sensitive!
|
* Attention: Adding fields to this is performance sensitive!
|
||||||
*/
|
*/
|
||||||
export interface PureExpressionData {
|
export interface PureExpressionData { value: any; }
|
||||||
value: any;
|
|
||||||
pipe: PipeTransform;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Accessor for view.nodes, enforcing that every usage site stays monomorphic.
|
* Accessor for view.nodes, enforcing that every usage site stays monomorphic.
|
||||||
|
@ -493,7 +489,8 @@ export interface Services {
|
||||||
notFoundValue?: any): any;
|
notFoundValue?: any): any;
|
||||||
createDebugContext(view: ViewData, nodeIndex: number): DebugContext;
|
createDebugContext(view: ViewData, nodeIndex: number): DebugContext;
|
||||||
handleEvent: ViewHandleEventFn;
|
handleEvent: ViewHandleEventFn;
|
||||||
updateView: ViewUpdateFn;
|
updateDirectives: ViewUpdateFn;
|
||||||
|
updateRenderer: ViewUpdateFn;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -513,5 +510,6 @@ export const Services: Services = {
|
||||||
resolveDep: undefined,
|
resolveDep: undefined,
|
||||||
createDebugContext: undefined,
|
createDebugContext: undefined,
|
||||||
handleEvent: undefined,
|
handleEvent: undefined,
|
||||||
updateView: undefined,
|
updateDirectives: undefined,
|
||||||
|
updateRenderer: undefined,
|
||||||
};
|
};
|
||||||
|
|
|
@ -30,16 +30,28 @@ export function tokenKey(token: any): string {
|
||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let unwrapCounter = 0;
|
||||||
|
|
||||||
|
export function unwrapValue(value: any): any {
|
||||||
|
if (value instanceof WrappedValue) {
|
||||||
|
value = value.wrapped;
|
||||||
|
unwrapCounter++;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
export function checkBinding(
|
export function checkBinding(
|
||||||
view: ViewData, def: NodeDef, bindingIdx: number, value: any): boolean {
|
view: ViewData, def: NodeDef, bindingIdx: number, value: any): boolean {
|
||||||
const oldValue = view.oldValues[def.bindingIndex + bindingIdx];
|
const oldValue = view.oldValues[def.bindingIndex + bindingIdx];
|
||||||
return !!(view.state & ViewState.FirstCheck) || !devModeEqual(oldValue, value);
|
return unwrapCounter > 0 || !!(view.state & ViewState.FirstCheck) ||
|
||||||
|
!devModeEqual(oldValue, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function checkBindingNoChanges(
|
export function checkBindingNoChanges(
|
||||||
view: ViewData, def: NodeDef, bindingIdx: number, value: any) {
|
view: ViewData, def: NodeDef, bindingIdx: number, value: any) {
|
||||||
const oldValue = view.oldValues[def.bindingIndex + bindingIdx];
|
const oldValue = view.oldValues[def.bindingIndex + bindingIdx];
|
||||||
if ((view.state & ViewState.FirstCheck) || !devModeEqual(oldValue, value)) {
|
if (unwrapCounter || (view.state & ViewState.FirstCheck) || !devModeEqual(oldValue, value)) {
|
||||||
|
unwrapCounter = 0;
|
||||||
throw expressionChangedAfterItHasBeenCheckedError(
|
throw expressionChangedAfterItHasBeenCheckedError(
|
||||||
Services.createDebugContext(view, def.index), oldValue, value,
|
Services.createDebugContext(view, def.index), oldValue, value,
|
||||||
(view.state & ViewState.FirstCheck) !== 0);
|
(view.state & ViewState.FirstCheck) !== 0);
|
||||||
|
@ -49,15 +61,10 @@ export function checkBindingNoChanges(
|
||||||
export function checkAndUpdateBinding(
|
export function checkAndUpdateBinding(
|
||||||
view: ViewData, def: NodeDef, bindingIdx: number, value: any): boolean {
|
view: ViewData, def: NodeDef, bindingIdx: number, value: any): boolean {
|
||||||
const oldValues = view.oldValues;
|
const oldValues = view.oldValues;
|
||||||
if ((view.state & ViewState.FirstCheck) ||
|
if (unwrapCounter || (view.state & ViewState.FirstCheck) ||
|
||||||
!looseIdentical(oldValues[def.bindingIndex + bindingIdx], value)) {
|
!looseIdentical(oldValues[def.bindingIndex + bindingIdx], value)) {
|
||||||
|
unwrapCounter = 0;
|
||||||
oldValues[def.bindingIndex + bindingIdx] = value;
|
oldValues[def.bindingIndex + bindingIdx] = value;
|
||||||
if (def.flags & NodeFlags.HasComponent) {
|
|
||||||
const compView = asProviderData(view, def.index).componentView;
|
|
||||||
if (compView.def.flags & ViewFlags.OnPush) {
|
|
||||||
compView.state |= ViewState.ChecksEnabled;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
@ -75,13 +82,6 @@ export function dispatchEvent(
|
||||||
return Services.handleEvent(view, nodeIndex, eventName, event);
|
return Services.handleEvent(view, nodeIndex, eventName, event);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function unwrapValue(value: any): any {
|
|
||||||
if (value instanceof WrappedValue) {
|
|
||||||
value = value.wrapped;
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function declaredViewContainer(view: ViewData): ElementData {
|
export function declaredViewContainer(view: ViewData): ElementData {
|
||||||
if (view.parent) {
|
if (view.parent) {
|
||||||
const parentView = view.parent;
|
const parentView = view.parent;
|
||||||
|
@ -91,16 +91,17 @@ export function declaredViewContainer(view: ViewData): ElementData {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* for component views, this is the same as parentIndex.
|
* for component views, this is the host element.
|
||||||
* for embedded views, this is the index of the parent node
|
* for embedded views, this is the index of the parent node
|
||||||
* that contains the view container.
|
* that contains the view container.
|
||||||
*/
|
*/
|
||||||
export function viewParentDiIndex(view: ViewData): number {
|
export function viewParentElIndex(view: ViewData): number {
|
||||||
if (view.parent && view.context !== view.component) {
|
const parentView = view.parent;
|
||||||
const parentNodeDef = view.parent.def.nodes[view.parentIndex];
|
if (parentView) {
|
||||||
return parentNodeDef.parent;
|
return parentView.def.nodes[view.parentIndex].parent;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
return view.parentIndex;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function renderNode(view: ViewData, def: NodeDef): any {
|
export function renderNode(view: ViewData, def: NodeDef): any {
|
||||||
|
@ -119,6 +120,8 @@ export function nodeValue(view: ViewData, index: number): any {
|
||||||
return asElementData(view, def.index).renderElement;
|
return asElementData(view, def.index).renderElement;
|
||||||
case NodeType.Text:
|
case NodeType.Text:
|
||||||
return asTextData(view, def.index).renderText;
|
return asTextData(view, def.index).renderText;
|
||||||
|
case NodeType.Directive:
|
||||||
|
case NodeType.Pipe:
|
||||||
case NodeType.Provider:
|
case NodeType.Provider:
|
||||||
return asProviderData(view, def.index).instance;
|
return asProviderData(view, def.index).instance;
|
||||||
}
|
}
|
||||||
|
@ -183,7 +186,10 @@ export function visitRootRenderNodes(
|
||||||
const len = view.def.nodes.length;
|
const len = view.def.nodes.length;
|
||||||
for (let i = 0; i < len; i++) {
|
for (let i = 0; i < len; i++) {
|
||||||
const nodeDef = view.def.nodes[i];
|
const nodeDef = view.def.nodes[i];
|
||||||
visitRenderNode(view, nodeDef, action, parentNode, nextSibling, target);
|
if (nodeDef.type === NodeType.Element || nodeDef.type === NodeType.Text ||
|
||||||
|
nodeDef.type === NodeType.NgContent) {
|
||||||
|
visitRenderNode(view, nodeDef, action, parentNode, nextSibling, target);
|
||||||
|
}
|
||||||
// jump to next sibling
|
// jump to next sibling
|
||||||
i += nodeDef.childCount;
|
i += nodeDef.childCount;
|
||||||
}
|
}
|
||||||
|
@ -197,7 +203,7 @@ export function visitProjectedRenderNodes(
|
||||||
compView = compView.parent;
|
compView = compView.parent;
|
||||||
}
|
}
|
||||||
const hostView = compView.parent;
|
const hostView = compView.parent;
|
||||||
const hostElDef = hostView.def.nodes[compView.parentIndex];
|
const hostElDef = hostView.def.nodes[viewParentElIndex(compView)];
|
||||||
const startIndex = hostElDef.index + 1;
|
const startIndex = hostElDef.index + 1;
|
||||||
const endIndex = hostElDef.index + hostElDef.childCount;
|
const endIndex = hostElDef.index + hostElDef.childCount;
|
||||||
for (let i = startIndex; i <= endIndex; i++) {
|
for (let i = startIndex; i <= endIndex; i++) {
|
||||||
|
|
|
@ -11,19 +11,19 @@ import {ViewEncapsulation} from '../metadata/view';
|
||||||
import {checkAndUpdateElementDynamic, checkAndUpdateElementInline, createElement} from './element';
|
import {checkAndUpdateElementDynamic, checkAndUpdateElementInline, createElement} from './element';
|
||||||
import {expressionChangedAfterItHasBeenCheckedError} from './errors';
|
import {expressionChangedAfterItHasBeenCheckedError} from './errors';
|
||||||
import {appendNgContent} from './ng_content';
|
import {appendNgContent} from './ng_content';
|
||||||
import {callLifecycleHooksChildrenFirst, checkAndUpdateProviderDynamic, checkAndUpdateProviderInline, createProviderInstance} from './provider';
|
import {callLifecycleHooksChildrenFirst, checkAndUpdateDirectiveDynamic, checkAndUpdateDirectiveInline, createDirectiveInstance, createPipeInstance, createProviderInstance} from './provider';
|
||||||
import {checkAndUpdatePureExpressionDynamic, checkAndUpdatePureExpressionInline, createPureExpression} from './pure_expression';
|
import {checkAndUpdatePureExpressionDynamic, checkAndUpdatePureExpressionInline, createPureExpression} from './pure_expression';
|
||||||
import {checkAndUpdateQuery, createQuery, queryDef} from './query';
|
import {checkAndUpdateQuery, createQuery, queryDef} from './query';
|
||||||
import {checkAndUpdateTextDynamic, checkAndUpdateTextInline, createText} from './text';
|
import {checkAndUpdateTextDynamic, checkAndUpdateTextInline, createText} from './text';
|
||||||
import {ArgumentType, ComponentDefinition, ElementDef, NodeData, NodeDef, NodeFlags, NodeType, ProviderData, ProviderDef, RootData, Services, TextDef, ViewData, ViewDefinition, ViewDefinitionFactory, ViewFlags, ViewHandleEventFn, ViewState, ViewUpdateFn, asElementData, asProviderData, asPureExpressionData, asQueryList} from './types';
|
import {ArgumentType, ComponentDefinition, ElementDef, NodeData, NodeDef, NodeFlags, NodeType, ProviderData, ProviderDef, RootData, Services, TextDef, ViewData, ViewDefinition, ViewDefinitionFactory, ViewFlags, ViewHandleEventFn, ViewState, ViewUpdateFn, asElementData, asProviderData, asPureExpressionData, asQueryList} from './types';
|
||||||
import {checkBindingNoChanges, isComponentView, queryIdIsReference, resolveViewDefinition} from './util';
|
import {checkBindingNoChanges, isComponentView, queryIdIsReference, resolveViewDefinition, viewParentElIndex} from './util';
|
||||||
|
|
||||||
const NOOP = (): any => undefined;
|
const NOOP = (): any => undefined;
|
||||||
|
|
||||||
export function viewDef(
|
export function viewDef(
|
||||||
flags: ViewFlags, nodesWithoutIndices: NodeDef[], update?: ViewUpdateFn,
|
flags: ViewFlags, nodesWithoutIndices: NodeDef[], updateDirectives?: ViewUpdateFn,
|
||||||
handleEvent?: ViewHandleEventFn, compId?: string, encapsulation?: ViewEncapsulation,
|
updateRenderer?: ViewUpdateFn, handleEvent?: ViewHandleEventFn, compId?: string,
|
||||||
styles?: string[]): ViewDefinition {
|
encapsulation?: ViewEncapsulation, styles?: string[]): ViewDefinition {
|
||||||
// clone nodes and set auto calculated values
|
// clone nodes and set auto calculated values
|
||||||
if (nodesWithoutIndices.length === 0) {
|
if (nodesWithoutIndices.length === 0) {
|
||||||
throw new Error(`Illegal State: Views without nodes are not allowed!`);
|
throw new Error(`Illegal State: Views without nodes are not allowed!`);
|
||||||
|
@ -83,7 +83,7 @@ export function viewDef(
|
||||||
if (!currentParent) {
|
if (!currentParent) {
|
||||||
lastRootNode = node;
|
lastRootNode = node;
|
||||||
}
|
}
|
||||||
if (node.provider) {
|
if (node.type === NodeType.Provider || node.type === NodeType.Directive) {
|
||||||
currentParent.element.providerIndices[node.provider.tokenKey] = i;
|
currentParent.element.providerIndices[node.provider.tokenKey] = i;
|
||||||
}
|
}
|
||||||
if (node.query) {
|
if (node.query) {
|
||||||
|
@ -108,7 +108,8 @@ export function viewDef(
|
||||||
nodeFlags: viewNodeFlags,
|
nodeFlags: viewNodeFlags,
|
||||||
nodeMatchedQueries: viewMatchedQueries, flags,
|
nodeMatchedQueries: viewMatchedQueries, flags,
|
||||||
nodes: nodes, reverseChildNodes,
|
nodes: nodes, reverseChildNodes,
|
||||||
update: update || NOOP,
|
updateDirectives: updateDirectives || NOOP,
|
||||||
|
updateRenderer: updateRenderer || NOOP,
|
||||||
handleEvent: handleEvent || NOOP,
|
handleEvent: handleEvent || NOOP,
|
||||||
component: componentDef,
|
component: componentDef,
|
||||||
bindingCount: viewBindingCount,
|
bindingCount: viewBindingCount,
|
||||||
|
@ -172,18 +173,18 @@ function validateNode(parent: NodeDef, node: NodeDef, nodeCount: number) {
|
||||||
`Illegal State: Last root node of a template can't have embedded views, at index ${node.index}!`);
|
`Illegal State: Last root node of a template can't have embedded views, at index ${node.index}!`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (node.provider) {
|
if (node.type === NodeType.Provider || node.type === NodeType.Directive) {
|
||||||
const parentType = parent ? parent.type : null;
|
const parentType = parent ? parent.type : null;
|
||||||
if (parentType !== NodeType.Element) {
|
if (parentType !== NodeType.Element) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Illegal State: Provider nodes need to be children of elements or anchors, at index ${node.index}!`);
|
`Illegal State: Provider/Directive nodes need to be children of elements or anchors, at index ${node.index}!`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (node.query) {
|
if (node.query) {
|
||||||
const parentType = parent ? parent.type : null;
|
const parentType = parent ? parent.type : null;
|
||||||
if (parentType !== NodeType.Provider) {
|
if (parentType !== NodeType.Directive) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Illegal State: Query nodes need to be children of providers, at index ${node.index}!`);
|
`Illegal State: Query nodes need to be children of directives, at index ${node.index}!`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (node.childCount) {
|
if (node.childCount) {
|
||||||
|
@ -282,7 +283,7 @@ function initView(view: ViewData, component: any, context: any) {
|
||||||
function createViewNodes(view: ViewData) {
|
function createViewNodes(view: ViewData) {
|
||||||
let renderHost: any;
|
let renderHost: any;
|
||||||
if (isComponentView(view)) {
|
if (isComponentView(view)) {
|
||||||
renderHost = asElementData(view.parent, view.parentIndex).renderElement;
|
renderHost = asElementData(view.parent, viewParentElIndex(view)).renderElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
const def = view.def;
|
const def = view.def;
|
||||||
|
@ -297,23 +298,36 @@ function createViewNodes(view: ViewData) {
|
||||||
case NodeType.Text:
|
case NodeType.Text:
|
||||||
nodes[i] = createText(view, renderHost, nodeDef) as any;
|
nodes[i] = createText(view, renderHost, nodeDef) as any;
|
||||||
break;
|
break;
|
||||||
case NodeType.Provider:
|
case NodeType.Provider: {
|
||||||
if (nodeDef.provider.component) {
|
const instance = createProviderInstance(view, nodeDef);
|
||||||
|
const providerData = <ProviderData>{componentView: undefined, instance};
|
||||||
|
nodes[i] = providerData as any;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case NodeType.Pipe: {
|
||||||
|
const instance = createPipeInstance(view, nodeDef);
|
||||||
|
const providerData = <ProviderData>{componentView: undefined, instance};
|
||||||
|
nodes[i] = providerData as any;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case NodeType.Directive: {
|
||||||
|
if (nodeDef.flags & NodeFlags.HasComponent) {
|
||||||
// Components can inject a ChangeDetectorRef that needs a references to
|
// Components can inject a ChangeDetectorRef that needs a references to
|
||||||
// the component view. Therefore, we create the component view first
|
// the component view. Therefore, we create the component view first
|
||||||
// and set the ProviderData in ViewData, and then instantiate the provider.
|
// and set the ProviderData in ViewData, and then instantiate the provider.
|
||||||
const componentView = createView(
|
const componentView = createView(
|
||||||
view.root, view, nodeDef.parent, resolveViewDefinition(nodeDef.provider.component));
|
view.root, view, nodeDef.index, resolveViewDefinition(nodeDef.provider.component));
|
||||||
const providerData = <ProviderData>{componentView, instance: undefined};
|
const providerData = <ProviderData>{componentView, instance: undefined};
|
||||||
nodes[i] = providerData as any;
|
nodes[i] = providerData as any;
|
||||||
const instance = providerData.instance = createProviderInstance(view, nodeDef);
|
const instance = providerData.instance = createDirectiveInstance(view, nodeDef);
|
||||||
initView(componentView, instance, instance);
|
initView(componentView, instance, instance);
|
||||||
} else {
|
} else {
|
||||||
const instance = createProviderInstance(view, nodeDef);
|
const instance = createDirectiveInstance(view, nodeDef);
|
||||||
const providerData = <ProviderData>{componentView: undefined, instance};
|
const providerData = <ProviderData>{componentView: undefined, instance};
|
||||||
nodes[i] = providerData as any;
|
nodes[i] = providerData as any;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case NodeType.PureExpression:
|
case NodeType.PureExpression:
|
||||||
nodes[i] = createPureExpression(view, nodeDef) as any;
|
nodes[i] = createPureExpression(view, nodeDef) as any;
|
||||||
break;
|
break;
|
||||||
|
@ -333,21 +347,25 @@ function createViewNodes(view: ViewData) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function checkNoChangesView(view: ViewData) {
|
export function checkNoChangesView(view: ViewData) {
|
||||||
Services.updateView(checkNoChangesNode, view);
|
Services.updateDirectives(checkNoChangesNode, view);
|
||||||
execEmbeddedViewsAction(view, ViewAction.CheckNoChanges);
|
execEmbeddedViewsAction(view, ViewAction.CheckNoChanges);
|
||||||
execQueriesAction(view, NodeFlags.HasContentQuery, QueryAction.CheckNoChanges);
|
execQueriesAction(view, NodeFlags.HasContentQuery, QueryAction.CheckNoChanges);
|
||||||
|
Services.updateRenderer(checkNoChangesNode, view);
|
||||||
execComponentViewsAction(view, ViewAction.CheckNoChanges);
|
execComponentViewsAction(view, ViewAction.CheckNoChanges);
|
||||||
execQueriesAction(view, NodeFlags.HasViewQuery, QueryAction.CheckNoChanges);
|
execQueriesAction(view, NodeFlags.HasViewQuery, QueryAction.CheckNoChanges);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function checkAndUpdateView(view: ViewData) {
|
export function checkAndUpdateView(view: ViewData) {
|
||||||
Services.updateView(checkAndUpdateNode, view);
|
Services.updateDirectives(checkAndUpdateNode, view);
|
||||||
execEmbeddedViewsAction(view, ViewAction.CheckAndUpdate);
|
execEmbeddedViewsAction(view, ViewAction.CheckAndUpdate);
|
||||||
execQueriesAction(view, NodeFlags.HasContentQuery, QueryAction.CheckAndUpdate);
|
execQueriesAction(view, NodeFlags.HasContentQuery, QueryAction.CheckAndUpdate);
|
||||||
|
|
||||||
callLifecycleHooksChildrenFirst(
|
callLifecycleHooksChildrenFirst(
|
||||||
view, NodeFlags.AfterContentChecked |
|
view, NodeFlags.AfterContentChecked |
|
||||||
(view.state & ViewState.FirstCheck ? NodeFlags.AfterContentInit : 0));
|
(view.state & ViewState.FirstCheck ? NodeFlags.AfterContentInit : 0));
|
||||||
|
|
||||||
|
Services.updateRenderer(checkAndUpdateNode, view);
|
||||||
|
|
||||||
execComponentViewsAction(view, ViewAction.CheckAndUpdate);
|
execComponentViewsAction(view, ViewAction.CheckAndUpdate);
|
||||||
execQueriesAction(view, NodeFlags.HasViewQuery, QueryAction.CheckAndUpdate);
|
execQueriesAction(view, NodeFlags.HasViewQuery, QueryAction.CheckAndUpdate);
|
||||||
|
|
||||||
|
@ -380,8 +398,8 @@ function checkAndUpdateNodeInline(
|
||||||
return checkAndUpdateElementInline(view, nodeDef, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9);
|
return checkAndUpdateElementInline(view, nodeDef, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9);
|
||||||
case NodeType.Text:
|
case NodeType.Text:
|
||||||
return checkAndUpdateTextInline(view, nodeDef, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9);
|
return checkAndUpdateTextInline(view, nodeDef, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9);
|
||||||
case NodeType.Provider:
|
case NodeType.Directive:
|
||||||
return checkAndUpdateProviderInline(view, nodeDef, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9);
|
return checkAndUpdateDirectiveInline(view, nodeDef, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9);
|
||||||
case NodeType.PureExpression:
|
case NodeType.PureExpression:
|
||||||
return checkAndUpdatePureExpressionInline(
|
return checkAndUpdatePureExpressionInline(
|
||||||
view, nodeDef, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9);
|
view, nodeDef, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9);
|
||||||
|
@ -395,8 +413,8 @@ function checkAndUpdateNodeDynamic(view: ViewData, nodeIndex: number, values: an
|
||||||
return checkAndUpdateElementDynamic(view, nodeDef, values);
|
return checkAndUpdateElementDynamic(view, nodeDef, values);
|
||||||
case NodeType.Text:
|
case NodeType.Text:
|
||||||
return checkAndUpdateTextDynamic(view, nodeDef, values);
|
return checkAndUpdateTextDynamic(view, nodeDef, values);
|
||||||
case NodeType.Provider:
|
case NodeType.Directive:
|
||||||
return checkAndUpdateProviderDynamic(view, nodeDef, values);
|
return checkAndUpdateDirectiveDynamic(view, nodeDef, values);
|
||||||
case NodeType.PureExpression:
|
case NodeType.PureExpression:
|
||||||
return checkAndUpdatePureExpressionDynamic(view, nodeDef, values);
|
return checkAndUpdatePureExpressionDynamic(view, nodeDef, values);
|
||||||
}
|
}
|
||||||
|
@ -462,14 +480,14 @@ function checkNoChangesQuery(view: ViewData, nodeDef: NodeDef) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function destroyView(view: ViewData) {
|
export function destroyView(view: ViewData) {
|
||||||
|
execEmbeddedViewsAction(view, ViewAction.Destroy);
|
||||||
|
execComponentViewsAction(view, ViewAction.Destroy);
|
||||||
callLifecycleHooksChildrenFirst(view, NodeFlags.OnDestroy);
|
callLifecycleHooksChildrenFirst(view, NodeFlags.OnDestroy);
|
||||||
if (view.disposables) {
|
if (view.disposables) {
|
||||||
for (let i = 0; i < view.disposables.length; i++) {
|
for (let i = 0; i < view.disposables.length; i++) {
|
||||||
view.disposables[i]();
|
view.disposables[i]();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
execComponentViewsAction(view, ViewAction.Destroy);
|
|
||||||
execEmbeddedViewsAction(view, ViewAction.Destroy);
|
|
||||||
view.state |= ViewState.Destroyed;
|
view.state |= ViewState.Destroyed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import {USE_VIEW_ENGINE} from '@angular/compiler/src/config';
|
||||||
import {ElementSchemaRegistry} from '@angular/compiler/src/schema/element_schema_registry';
|
import {ElementSchemaRegistry} from '@angular/compiler/src/schema/element_schema_registry';
|
||||||
import {TEST_COMPILER_PROVIDERS} from '@angular/compiler/testing/test_bindings';
|
import {TEST_COMPILER_PROVIDERS} from '@angular/compiler/testing/test_bindings';
|
||||||
import {AfterContentChecked, AfterContentInit, AfterViewChecked, AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, DebugElement, Directive, DoCheck, Injectable, Input, OnChanges, OnDestroy, OnInit, Output, Pipe, PipeTransform, RenderComponentType, Renderer, RootRenderer, SimpleChange, SimpleChanges, TemplateRef, Type, ViewChild, ViewContainerRef, WrappedValue} from '@angular/core';
|
import {AfterContentChecked, AfterContentInit, AfterViewChecked, AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, DebugElement, Directive, DoCheck, Injectable, Input, OnChanges, OnDestroy, OnInit, Output, Pipe, PipeTransform, RenderComponentType, Renderer, RootRenderer, SimpleChange, SimpleChanges, TemplateRef, Type, ViewChild, ViewContainerRef, WrappedValue} from '@angular/core';
|
||||||
|
@ -19,6 +20,19 @@ import {MockSchemaRegistry} from '../../../compiler/testing/index';
|
||||||
import {EventEmitter} from '../../src/facade/async';
|
import {EventEmitter} from '../../src/facade/async';
|
||||||
|
|
||||||
export function main() {
|
export function main() {
|
||||||
|
describe('Current compiler', () => { createTests({viewEngine: false}); });
|
||||||
|
|
||||||
|
describe('View Engine compiler', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureCompiler(
|
||||||
|
{useJit: true, providers: [{provide: USE_VIEW_ENGINE, useValue: true}]});
|
||||||
|
});
|
||||||
|
|
||||||
|
createTests({viewEngine: true});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function createTests({viewEngine}: {viewEngine: boolean}) {
|
||||||
let elSchema: MockSchemaRegistry;
|
let elSchema: MockSchemaRegistry;
|
||||||
let renderLog: RenderLog;
|
let renderLog: RenderLog;
|
||||||
let directiveLog: DirectiveLog;
|
let directiveLog: DirectiveLog;
|
||||||
|
@ -1080,7 +1094,8 @@ export function main() {
|
||||||
|
|
||||||
ctx.destroy();
|
ctx.destroy();
|
||||||
|
|
||||||
expect(directiveLog.filter(['ngOnDestroy'])).toEqual([
|
// We don't care about the exact order in this test.
|
||||||
|
expect(directiveLog.filter(['ngOnDestroy']).sort()).toEqual([
|
||||||
'dir.ngOnDestroy', 'injectable.ngOnDestroy'
|
'dir.ngOnDestroy', 'injectable.ngOnDestroy'
|
||||||
]);
|
]);
|
||||||
}));
|
}));
|
||||||
|
@ -1092,8 +1107,9 @@ export function main() {
|
||||||
it('should throw when a record gets changed after it has been checked', fakeAsync(() => {
|
it('should throw when a record gets changed after it has been checked', fakeAsync(() => {
|
||||||
const ctx = createCompFixture('<div [someProp]="a"></div>', TestData);
|
const ctx = createCompFixture('<div [someProp]="a"></div>', TestData);
|
||||||
ctx.componentInstance.a = 1;
|
ctx.componentInstance.a = 1;
|
||||||
|
|
||||||
expect(() => ctx.checkNoChanges())
|
expect(() => ctx.checkNoChanges())
|
||||||
.toThrowError(/:0:5[\s\S]*Expression has changed after it was checked./g);
|
.toThrowError(/Expression has changed after it was checked./g);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should warn when the view has been created in a cd hook', fakeAsync(() => {
|
it('should warn when the view has been created in a cd hook', fakeAsync(() => {
|
||||||
|
@ -1216,26 +1232,28 @@ export function main() {
|
||||||
expect(renderLog.loggedValues).toEqual(['Tom']);
|
expect(renderLog.loggedValues).toEqual(['Tom']);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should recurse into nested view containers even if there are no bindings in the component view',
|
// TODO(tbosch): ViewQueries don't work yet with the view engine...
|
||||||
() => {
|
viewEngine ||
|
||||||
@Component({template: '<template #vc>{{name}}</template>'})
|
it('should recurse into nested view containers even if there are no bindings in the component view',
|
||||||
class Comp {
|
() => {
|
||||||
name = 'Tom';
|
@Component({template: '<template #vc>{{name}}</template>'})
|
||||||
@ViewChild('vc', {read: ViewContainerRef}) vc: ViewContainerRef;
|
class Comp {
|
||||||
@ViewChild(TemplateRef) template: TemplateRef<any>;
|
name = 'Tom';
|
||||||
}
|
@ViewChild('vc', {read: ViewContainerRef}) vc: ViewContainerRef;
|
||||||
|
@ViewChild(TemplateRef) template: TemplateRef<any>;
|
||||||
|
}
|
||||||
|
|
||||||
TestBed.configureTestingModule({declarations: [Comp]});
|
TestBed.configureTestingModule({declarations: [Comp]});
|
||||||
initHelpers();
|
initHelpers();
|
||||||
|
|
||||||
const ctx = TestBed.createComponent(Comp);
|
const ctx = TestBed.createComponent(Comp);
|
||||||
ctx.detectChanges();
|
ctx.detectChanges();
|
||||||
expect(renderLog.loggedValues).toEqual([]);
|
expect(renderLog.loggedValues).toEqual([]);
|
||||||
|
|
||||||
ctx.componentInstance.vc.createEmbeddedView(ctx.componentInstance.template);
|
ctx.componentInstance.vc.createEmbeddedView(ctx.componentInstance.template);
|
||||||
ctx.detectChanges();
|
ctx.detectChanges();
|
||||||
expect(renderLog.loggedValues).toEqual(['Tom']);
|
expect(renderLog.loggedValues).toEqual(['Tom']);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -265,7 +265,7 @@ function declareTests({useJit, viewEngine}: {useJit: boolean, viewEngine: boolea
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('pipes', () => {
|
describe('pipes', () => {
|
||||||
viewEngine || it('should support pipes in bindings', () => {
|
it('should support pipes in bindings', () => {
|
||||||
TestBed.configureTestingModule({declarations: [MyComp, MyDir, DoublePipe]});
|
TestBed.configureTestingModule({declarations: [MyComp, MyDir, DoublePipe]});
|
||||||
const template = '<div my-dir #dir="mydir" [elprop]="ctxProp | double"></div>';
|
const template = '<div my-dir #dir="mydir" [elprop]="ctxProp | double"></div>';
|
||||||
TestBed.overrideComponent(MyComp, {set: {template}});
|
TestBed.overrideComponent(MyComp, {set: {template}});
|
||||||
|
@ -545,7 +545,7 @@ function declareTests({useJit, viewEngine}: {useJit: boolean, viewEngine: boolea
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('variables', () => {
|
describe('variables', () => {
|
||||||
viewEngine || it('should allow to use variables in a for loop', () => {
|
it('should allow to use variables in a for loop', () => {
|
||||||
TestBed.configureTestingModule({declarations: [MyComp, ChildCompNoTemplate]});
|
TestBed.configureTestingModule({declarations: [MyComp, ChildCompNoTemplate]});
|
||||||
const template =
|
const template =
|
||||||
'<template ngFor [ngForOf]="[1]" let-i><child-cmp-no-template #cmp></child-cmp-no-template>{{i}}-{{cmp.ctxProp}}</template>';
|
'<template ngFor [ngForOf]="[1]" let-i><child-cmp-no-template #cmp></child-cmp-no-template>{{i}}-{{cmp.ctxProp}}</template>';
|
||||||
|
@ -670,31 +670,29 @@ function declareTests({useJit, viewEngine}: {useJit: boolean, viewEngine: boolea
|
||||||
});
|
});
|
||||||
|
|
||||||
if (getDOM().supportsDOMEvents()) {
|
if (getDOM().supportsDOMEvents()) {
|
||||||
viewEngine ||
|
it('should be checked when an async pipe requests a check', fakeAsync(() => {
|
||||||
it('should be checked when an async pipe requests a check', fakeAsync(() => {
|
TestBed.configureTestingModule(
|
||||||
TestBed.configureTestingModule(
|
{declarations: [MyComp, PushCmpWithAsyncPipe], imports: [CommonModule]});
|
||||||
{declarations: [MyComp, PushCmpWithAsyncPipe], imports: [CommonModule]});
|
const template = '<push-cmp-with-async #cmp></push-cmp-with-async>';
|
||||||
const template = '<push-cmp-with-async #cmp></push-cmp-with-async>';
|
TestBed.overrideComponent(MyComp, {set: {template}});
|
||||||
TestBed.overrideComponent(MyComp, {set: {template}});
|
const fixture = TestBed.createComponent(MyComp);
|
||||||
const fixture = TestBed.createComponent(MyComp);
|
|
||||||
|
|
||||||
tick();
|
tick();
|
||||||
|
|
||||||
const cmp: PushCmpWithAsyncPipe =
|
const cmp: PushCmpWithAsyncPipe = fixture.debugElement.children[0].references['cmp'];
|
||||||
fixture.debugElement.children[0].references['cmp'];
|
fixture.detectChanges();
|
||||||
fixture.detectChanges();
|
expect(cmp.numberOfChecks).toEqual(1);
|
||||||
expect(cmp.numberOfChecks).toEqual(1);
|
|
||||||
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
expect(cmp.numberOfChecks).toEqual(1);
|
expect(cmp.numberOfChecks).toEqual(1);
|
||||||
|
|
||||||
cmp.resolve(2);
|
cmp.resolve(2);
|
||||||
tick();
|
tick();
|
||||||
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
expect(cmp.numberOfChecks).toEqual(2);
|
expect(cmp.numberOfChecks).toEqual(2);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -897,26 +895,24 @@ function declareTests({useJit, viewEngine}: {useJit: boolean, viewEngine: boolea
|
||||||
expect(tc.nativeElement.id).toEqual('newId');
|
expect(tc.nativeElement.id).toEqual('newId');
|
||||||
});
|
});
|
||||||
|
|
||||||
viewEngine ||
|
it('should not use template variables for expressions in hostProperties', () => {
|
||||||
it('should not use template variables for expressions in hostProperties', () => {
|
@Directive({selector: '[host-properties]', host: {'[id]': 'id', '[title]': 'unknownProp'}})
|
||||||
@Directive(
|
class DirectiveWithHostProps {
|
||||||
{selector: '[host-properties]', host: {'[id]': 'id', '[title]': 'unknownProp'}})
|
id = 'one';
|
||||||
class DirectiveWithHostProps {
|
}
|
||||||
id = 'one';
|
|
||||||
}
|
|
||||||
|
|
||||||
const fixture =
|
const fixture =
|
||||||
TestBed.configureTestingModule({declarations: [MyComp, DirectiveWithHostProps]})
|
TestBed.configureTestingModule({declarations: [MyComp, DirectiveWithHostProps]})
|
||||||
.overrideComponent(MyComp, {
|
.overrideComponent(
|
||||||
set: {template: `<div *ngFor="let id of ['forId']" host-properties></div>`}
|
MyComp,
|
||||||
})
|
{set: {template: `<div *ngFor="let id of ['forId']" host-properties></div>`}})
|
||||||
.createComponent(MyComp);
|
.createComponent(MyComp);
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
const tc = fixture.debugElement.children[0];
|
const tc = fixture.debugElement.children[0];
|
||||||
expect(tc.properties['id']).toBe('one');
|
expect(tc.properties['id']).toBe('one');
|
||||||
expect(tc.properties['title']).toBe(undefined);
|
expect(tc.properties['title']).toBe(undefined);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not allow pipes in hostProperties', () => {
|
it('should not allow pipes in hostProperties', () => {
|
||||||
@Directive({selector: '[host-properties]', host: {'[id]': 'id | uppercase'}})
|
@Directive({selector: '[host-properties]', host: {'[id]': 'id | uppercase'}})
|
||||||
|
@ -930,8 +926,7 @@ function declareTests({useJit, viewEngine}: {useJit: boolean, viewEngine: boolea
|
||||||
.toThrowError(/Host binding expression cannot contain pipes/);
|
.toThrowError(/Host binding expression cannot contain pipes/);
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: literal array
|
it('should not use template variables for expressions in hostListeners', () => {
|
||||||
viewEngine || it('should not use template variables for expressions in hostListeners', () => {
|
|
||||||
@Directive({selector: '[host-listener]', host: {'(click)': 'doIt(id, unknownProp)'}})
|
@Directive({selector: '[host-listener]', host: {'(click)': 'doIt(id, unknownProp)'}})
|
||||||
class DirectiveWithHostListener {
|
class DirectiveWithHostListener {
|
||||||
id = 'one';
|
id = 'one';
|
||||||
|
|
|
@ -16,8 +16,9 @@ import {createRootView, isBrowser} from './helper';
|
||||||
export function main() {
|
export function main() {
|
||||||
describe(`View Anchor`, () => {
|
describe(`View Anchor`, () => {
|
||||||
function compViewDef(
|
function compViewDef(
|
||||||
nodes: NodeDef[], update?: ViewUpdateFn, handleEvent?: ViewHandleEventFn): ViewDefinition {
|
nodes: NodeDef[], updateDirectives?: ViewUpdateFn, updateRenderer?: ViewUpdateFn,
|
||||||
return viewDef(ViewFlags.None, nodes, update, handleEvent);
|
handleEvent?: ViewHandleEventFn): ViewDefinition {
|
||||||
|
return viewDef(ViewFlags.None, nodes, updateDirectives, updateRenderer, handleEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
function createAndGetRootNodes(
|
function createAndGetRootNodes(
|
||||||
|
|
|
@ -16,9 +16,9 @@ import {createRootView, isBrowser, removeNodes} from './helper';
|
||||||
export function main() {
|
export function main() {
|
||||||
describe(`Component Views`, () => {
|
describe(`Component Views`, () => {
|
||||||
function compViewDef(
|
function compViewDef(
|
||||||
nodes: NodeDef[], update?: ViewUpdateFn, handleEvent?: ViewHandleEventFn,
|
nodes: NodeDef[], updateDirectives?: ViewUpdateFn, updateRenderer?: ViewUpdateFn,
|
||||||
viewFlags: ViewFlags = ViewFlags.None): ViewDefinition {
|
handleEvent?: ViewHandleEventFn, viewFlags: ViewFlags = ViewFlags.None): ViewDefinition {
|
||||||
return viewDef(viewFlags, nodes, update, handleEvent);
|
return viewDef(viewFlags, nodes, updateDirectives, updateRenderer, handleEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
function createAndGetRootNodes(viewDef: ViewDefinition): {rootNodes: any[], view: ViewData} {
|
function createAndGetRootNodes(viewDef: ViewDefinition): {rootNodes: any[], view: ViewData} {
|
||||||
|
@ -119,7 +119,7 @@ export function main() {
|
||||||
directiveDef(NodeFlags.None, null, 0, AComp, [], null, null, () => compViewDef(
|
directiveDef(NodeFlags.None, null, 0, AComp, [], null, null, () => compViewDef(
|
||||||
[
|
[
|
||||||
elementDef(NodeFlags.None, null, null, 0, 'span', null, [[BindingType.ElementAttribute, 'a', SecurityContext.NONE]]),
|
elementDef(NodeFlags.None, null, null, 0, 'span', null, [[BindingType.ElementAttribute, 'a', SecurityContext.NONE]]),
|
||||||
], update
|
], null, update
|
||||||
)),
|
)),
|
||||||
]));
|
]));
|
||||||
const compView = asProviderData(view, 1).componentView;
|
const compView = asProviderData(view, 1).componentView;
|
||||||
|
@ -194,7 +194,7 @@ export function main() {
|
||||||
[
|
[
|
||||||
elementDef(NodeFlags.None, null, null, 0, 'span', null, null, ['click']),
|
elementDef(NodeFlags.None, null, null, 0, 'span', null, null, ['click']),
|
||||||
],
|
],
|
||||||
update, null, ViewFlags.OnPush)),
|
update, null, null, ViewFlags.OnPush)),
|
||||||
],
|
],
|
||||||
(check, view) => { check(view, 1, ArgumentType.Inline, compInputValue); }));
|
(check, view) => { check(view, 1, ArgumentType.Inline, compInputValue); }));
|
||||||
|
|
||||||
|
@ -246,7 +246,7 @@ export function main() {
|
||||||
[
|
[
|
||||||
elementDef(NodeFlags.None, null, null, 0, 'span', null, [[BindingType.ElementAttribute, 'a', SecurityContext.NONE]]),
|
elementDef(NodeFlags.None, null, null, 0, 'span', null, [[BindingType.ElementAttribute, 'a', SecurityContext.NONE]]),
|
||||||
],
|
],
|
||||||
update)),
|
null, update)),
|
||||||
]));
|
]));
|
||||||
|
|
||||||
const compView = asProviderData(view, 1).componentView;
|
const compView = asProviderData(view, 1).componentView;
|
||||||
|
|
|
@ -17,9 +17,9 @@ import {ARG_TYPE_VALUES, checkNodeInlineOrDynamic, createRootView, isBrowser, re
|
||||||
export function main() {
|
export function main() {
|
||||||
describe(`View Elements`, () => {
|
describe(`View Elements`, () => {
|
||||||
function compViewDef(
|
function compViewDef(
|
||||||
nodes: NodeDef[], update?: ViewUpdateFn, handleEvent?: ViewHandleEventFn,
|
nodes: NodeDef[], updateDirectives?: ViewUpdateFn, updateRenderer?: ViewUpdateFn,
|
||||||
viewFlags: ViewFlags = ViewFlags.None): ViewDefinition {
|
handleEvent?: ViewHandleEventFn, viewFlags: ViewFlags = ViewFlags.None): ViewDefinition {
|
||||||
return viewDef(viewFlags, nodes, update, handleEvent);
|
return viewDef(viewFlags, nodes, updateDirectives, updateRenderer, handleEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
function createAndGetRootNodes(
|
function createAndGetRootNodes(
|
||||||
|
@ -85,7 +85,7 @@ export function main() {
|
||||||
[BindingType.ElementProperty, 'value', SecurityContext.NONE]
|
[BindingType.ElementProperty, 'value', SecurityContext.NONE]
|
||||||
]),
|
]),
|
||||||
],
|
],
|
||||||
(check, view) => {
|
null, (check, view) => {
|
||||||
checkNodeInlineOrDynamic(check, view, 0, inlineDynamic, ['v1', 'v2']);
|
checkNodeInlineOrDynamic(check, view, 0, inlineDynamic, ['v1', 'v2']);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@ -112,7 +112,7 @@ export function main() {
|
||||||
[BindingType.ElementAttribute, 'a2', SecurityContext.NONE]
|
[BindingType.ElementAttribute, 'a2', SecurityContext.NONE]
|
||||||
]),
|
]),
|
||||||
],
|
],
|
||||||
(check, view) => {
|
null, (check, view) => {
|
||||||
checkNodeInlineOrDynamic(check, view, 0, inlineDynamic, ['v1', 'v2']);
|
checkNodeInlineOrDynamic(check, view, 0, inlineDynamic, ['v1', 'v2']);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@ -159,7 +159,7 @@ export function main() {
|
||||||
[BindingType.ElementStyle, 'color', null]
|
[BindingType.ElementStyle, 'color', null]
|
||||||
]),
|
]),
|
||||||
],
|
],
|
||||||
(check, view) => {
|
null, (check, view) => {
|
||||||
checkNodeInlineOrDynamic(check, view, 0, inlineDynamic, [10, 'red']);
|
checkNodeInlineOrDynamic(check, view, 0, inlineDynamic, [10, 'red']);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@ -172,42 +172,6 @@ export function main() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('general binding behavior', () => {
|
|
||||||
ARG_TYPE_VALUES.forEach((inlineDynamic) => {
|
|
||||||
it(`should unwrap values with ${ArgumentType[inlineDynamic]}`, () => {
|
|
||||||
let bindingValue: any;
|
|
||||||
|
|
||||||
const {view, rootNodes} = createAndGetRootNodes(compViewDef(
|
|
||||||
[
|
|
||||||
elementDef(
|
|
||||||
NodeFlags.None, null, null, 0, 'input', null,
|
|
||||||
[
|
|
||||||
[BindingType.ElementProperty, 'someProp', SecurityContext.NONE],
|
|
||||||
]),
|
|
||||||
],
|
|
||||||
(check, view) => {
|
|
||||||
checkNodeInlineOrDynamic(check, view, 0, inlineDynamic, [bindingValue]);
|
|
||||||
}));
|
|
||||||
|
|
||||||
const setterSpy = jasmine.createSpy('set');
|
|
||||||
Object.defineProperty(rootNodes[0], 'someProp', {set: setterSpy});
|
|
||||||
|
|
||||||
bindingValue = 'v1';
|
|
||||||
Services.checkAndUpdateView(view);
|
|
||||||
expect(setterSpy).toHaveBeenCalledWith('v1');
|
|
||||||
|
|
||||||
setterSpy.calls.reset();
|
|
||||||
Services.checkAndUpdateView(view);
|
|
||||||
expect(setterSpy).not.toHaveBeenCalled();
|
|
||||||
|
|
||||||
setterSpy.calls.reset();
|
|
||||||
bindingValue = WrappedValue.wrap('v1');
|
|
||||||
Services.checkAndUpdateView(view);
|
|
||||||
expect(setterSpy).toHaveBeenCalledWith('v1');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
if (isBrowser()) {
|
if (isBrowser()) {
|
||||||
describe('listen to DOM events', () => {
|
describe('listen to DOM events', () => {
|
||||||
function createAndAttachAndGetRootNodes(viewDef: ViewDefinition):
|
function createAndAttachAndGetRootNodes(viewDef: ViewDefinition):
|
||||||
|
@ -228,7 +192,7 @@ export function main() {
|
||||||
spyOn(HTMLElement.prototype, 'removeEventListener').and.callThrough();
|
spyOn(HTMLElement.prototype, 'removeEventListener').and.callThrough();
|
||||||
const {view, rootNodes} = createAndAttachAndGetRootNodes(compViewDef(
|
const {view, rootNodes} = createAndAttachAndGetRootNodes(compViewDef(
|
||||||
[elementDef(NodeFlags.None, null, null, 0, 'button', null, null, ['click'])], null,
|
[elementDef(NodeFlags.None, null, null, 0, 'button', null, null, ['click'])], null,
|
||||||
handleEventSpy));
|
null, handleEventSpy));
|
||||||
|
|
||||||
rootNodes[0].click();
|
rootNodes[0].click();
|
||||||
|
|
||||||
|
@ -252,7 +216,7 @@ export function main() {
|
||||||
[elementDef(
|
[elementDef(
|
||||||
NodeFlags.None, null, null, 0, 'button', null, null,
|
NodeFlags.None, null, null, 0, 'button', null, null,
|
||||||
[['window', 'windowClick']])],
|
[['window', 'windowClick']])],
|
||||||
null, handleEventSpy));
|
null, null, handleEventSpy));
|
||||||
|
|
||||||
expect(addListenerSpy).toHaveBeenCalled();
|
expect(addListenerSpy).toHaveBeenCalled();
|
||||||
expect(addListenerSpy.calls.mostRecent().args[0]).toBe('windowClick');
|
expect(addListenerSpy.calls.mostRecent().args[0]).toBe('windowClick');
|
||||||
|
@ -278,7 +242,7 @@ export function main() {
|
||||||
[elementDef(
|
[elementDef(
|
||||||
NodeFlags.None, null, null, 0, 'button', null, null,
|
NodeFlags.None, null, null, 0, 'button', null, null,
|
||||||
[['document', 'documentClick']])],
|
[['document', 'documentClick']])],
|
||||||
null, handleEventSpy));
|
null, null, handleEventSpy));
|
||||||
|
|
||||||
expect(addListenerSpy).toHaveBeenCalled();
|
expect(addListenerSpy).toHaveBeenCalled();
|
||||||
expect(addListenerSpy.calls.mostRecent().args[0]).toBe('documentClick');
|
expect(addListenerSpy.calls.mostRecent().args[0]).toBe('documentClick');
|
||||||
|
@ -302,7 +266,7 @@ export function main() {
|
||||||
|
|
||||||
const {view, rootNodes} = createAndAttachAndGetRootNodes(compViewDef(
|
const {view, rootNodes} = createAndAttachAndGetRootNodes(compViewDef(
|
||||||
[elementDef(NodeFlags.None, null, null, 0, 'button', null, null, ['click'])], null,
|
[elementDef(NodeFlags.None, null, null, 0, 'button', null, null, ['click'])], null,
|
||||||
(view, index, eventName, event) => {
|
null, (view, index, eventName, event) => {
|
||||||
preventDefaultSpy = spyOn(event, 'preventDefault').and.callThrough();
|
preventDefaultSpy = spyOn(event, 'preventDefault').and.callThrough();
|
||||||
return eventHandlerResult;
|
return eventHandlerResult;
|
||||||
}));
|
}));
|
||||||
|
@ -328,7 +292,7 @@ export function main() {
|
||||||
const addListenerSpy = spyOn(HTMLElement.prototype, 'addEventListener').and.callThrough();
|
const addListenerSpy = spyOn(HTMLElement.prototype, 'addEventListener').and.callThrough();
|
||||||
const {view, rootNodes} = createAndAttachAndGetRootNodes(compViewDef(
|
const {view, rootNodes} = createAndAttachAndGetRootNodes(compViewDef(
|
||||||
[elementDef(NodeFlags.None, null, null, 0, 'button', null, null, ['click'])], null,
|
[elementDef(NodeFlags.None, null, null, 0, 'button', null, null, ['click'])], null,
|
||||||
() => { throw new Error('Test'); }));
|
null, () => { throw new Error('Test'); }));
|
||||||
|
|
||||||
let err: any;
|
let err: any;
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -16,9 +16,9 @@ import {createRootView, isBrowser} from './helper';
|
||||||
export function main() {
|
export function main() {
|
||||||
describe(`Embedded Views`, () => {
|
describe(`Embedded Views`, () => {
|
||||||
function compViewDef(
|
function compViewDef(
|
||||||
nodes: NodeDef[], update?: ViewUpdateFn, handleEvent?: ViewHandleEventFn,
|
nodes: NodeDef[], updateDirectives?: ViewUpdateFn, updateRenderer?: ViewUpdateFn,
|
||||||
viewFlags: ViewFlags = ViewFlags.None): ViewDefinition {
|
handleEvent?: ViewHandleEventFn, viewFlags: ViewFlags = ViewFlags.None): ViewDefinition {
|
||||||
return viewDef(viewFlags, nodes, update, handleEvent);
|
return viewDef(viewFlags, nodes, updateDirectives, updateRenderer, handleEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
function embeddedViewDef(nodes: NodeDef[], update?: ViewUpdateFn): ViewDefinitionFactory {
|
function embeddedViewDef(nodes: NodeDef[], update?: ViewUpdateFn): ViewDefinitionFactory {
|
||||||
|
|
|
@ -16,9 +16,9 @@ import {createRootView, isBrowser} from './helper';
|
||||||
export function main() {
|
export function main() {
|
||||||
describe(`View NgContent`, () => {
|
describe(`View NgContent`, () => {
|
||||||
function compViewDef(
|
function compViewDef(
|
||||||
nodes: NodeDef[], update?: ViewUpdateFn, handleEvent?: ViewHandleEventFn,
|
nodes: NodeDef[], updateDirectives?: ViewUpdateFn, updateRenderer?: ViewUpdateFn,
|
||||||
viewFlags: ViewFlags = ViewFlags.None): ViewDefinition {
|
handleEvent?: ViewHandleEventFn, viewFlags: ViewFlags = ViewFlags.None): ViewDefinition {
|
||||||
return viewDef(viewFlags, nodes, update, handleEvent);
|
return viewDef(viewFlags, nodes, updateDirectives, updateRenderer, handleEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
function embeddedViewDef(nodes: NodeDef[], update?: ViewUpdateFn): ViewDefinitionFactory {
|
function embeddedViewDef(nodes: NodeDef[], update?: ViewUpdateFn): ViewDefinitionFactory {
|
||||||
|
|
|
@ -17,10 +17,11 @@ import {ARG_TYPE_VALUES, checkNodeInlineOrDynamic, createRootView, isBrowser} fr
|
||||||
export function main() {
|
export function main() {
|
||||||
describe(`View Providers`, () => {
|
describe(`View Providers`, () => {
|
||||||
function compViewDef(
|
function compViewDef(
|
||||||
nodes: NodeDef[], update?: ViewUpdateFn, handleEvent?: ViewHandleEventFn,
|
nodes: NodeDef[], updateDirectives?: ViewUpdateFn, updateRenderer?: ViewUpdateFn,
|
||||||
viewFlags: ViewFlags = ViewFlags.None): ViewDefinition {
|
handleEvent?: ViewHandleEventFn, viewFlags: ViewFlags = ViewFlags.None): ViewDefinition {
|
||||||
return viewDef(
|
return viewDef(
|
||||||
viewFlags, nodes, update, handleEvent, 'someCompId', ViewEncapsulation.None, []);
|
viewFlags, nodes, updateDirectives, updateRenderer, handleEvent, 'someCompId',
|
||||||
|
ViewEncapsulation.None, []);
|
||||||
}
|
}
|
||||||
|
|
||||||
function embeddedViewDef(nodes: NodeDef[], update?: ViewUpdateFn): ViewDefinitionFactory {
|
function embeddedViewDef(nodes: NodeDef[], update?: ViewUpdateFn): ViewDefinitionFactory {
|
||||||
|
@ -59,7 +60,8 @@ export function main() {
|
||||||
|
|
||||||
createAndGetRootNodes(compViewDef([
|
createAndGetRootNodes(compViewDef([
|
||||||
elementDef(NodeFlags.None, null, null, 2, 'span'),
|
elementDef(NodeFlags.None, null, null, 2, 'span'),
|
||||||
directiveDef(NodeFlags.LazyProvider, null, 0, LazyService, []),
|
providerDef(
|
||||||
|
NodeFlags.LazyProvider, null, ProviderType.Class, LazyService, LazyService, []),
|
||||||
directiveDef(NodeFlags.None, null, 0, SomeService, [Injector])
|
directiveDef(NodeFlags.None, null, 0, SomeService, [Injector])
|
||||||
]));
|
]));
|
||||||
|
|
||||||
|
@ -330,37 +332,6 @@ export function main() {
|
||||||
expect(getDOM().getAttribute(el, 'ng-reflect-a')).toBe('v1');
|
expect(getDOM().getAttribute(el, 'ng-reflect-a')).toBe('v1');
|
||||||
});
|
});
|
||||||
|
|
||||||
it(`should unwrap values with ${ArgumentType[inlineDynamic]}`, () => {
|
|
||||||
let bindingValue: any;
|
|
||||||
let setterSpy = jasmine.createSpy('set');
|
|
||||||
|
|
||||||
class SomeService {
|
|
||||||
set a(value: any) { setterSpy(value); }
|
|
||||||
}
|
|
||||||
|
|
||||||
const {view, rootNodes} = createAndGetRootNodes(compViewDef(
|
|
||||||
[
|
|
||||||
elementDef(NodeFlags.None, null, null, 1, 'span'),
|
|
||||||
directiveDef(NodeFlags.None, null, 0, SomeService, [], {a: [0, 'a']})
|
|
||||||
],
|
|
||||||
(check, view) => {
|
|
||||||
checkNodeInlineOrDynamic(check, view, 1, inlineDynamic, [bindingValue]);
|
|
||||||
}));
|
|
||||||
|
|
||||||
bindingValue = 'v1';
|
|
||||||
Services.checkAndUpdateView(view);
|
|
||||||
expect(setterSpy).toHaveBeenCalledWith('v1');
|
|
||||||
|
|
||||||
setterSpy.calls.reset();
|
|
||||||
Services.checkAndUpdateView(view);
|
|
||||||
expect(setterSpy).not.toHaveBeenCalled();
|
|
||||||
|
|
||||||
setterSpy.calls.reset();
|
|
||||||
bindingValue = WrappedValue.wrap('v1');
|
|
||||||
Services.checkAndUpdateView(view);
|
|
||||||
expect(setterSpy).toHaveBeenCalledWith('v1');
|
|
||||||
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -388,7 +359,7 @@ export function main() {
|
||||||
directiveDef(
|
directiveDef(
|
||||||
NodeFlags.None, null, 0, SomeService, [], null, {emitter: 'someEventName'})
|
NodeFlags.None, null, 0, SomeService, [], null, {emitter: 'someEventName'})
|
||||||
],
|
],
|
||||||
null, handleEvent));
|
null, null, handleEvent));
|
||||||
|
|
||||||
emitter.emit('someEventInstance');
|
emitter.emit('someEventInstance');
|
||||||
expect(handleEvent).toHaveBeenCalledWith(view, 0, 'someEventName', 'someEventInstance');
|
expect(handleEvent).toHaveBeenCalledWith(view, 0, 'someEventName', 'someEventInstance');
|
||||||
|
@ -410,7 +381,7 @@ export function main() {
|
||||||
directiveDef(
|
directiveDef(
|
||||||
NodeFlags.None, null, 0, SomeService, [], null, {emitter: 'someEventName'})
|
NodeFlags.None, null, 0, SomeService, [], null, {emitter: 'someEventName'})
|
||||||
],
|
],
|
||||||
null, () => { throw new Error('Test'); }));
|
null, null, () => { throw new Error('Test'); }));
|
||||||
|
|
||||||
let err: any;
|
let err: any;
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {Injector, PipeTransform, RenderComponentType, RootRenderer, Sanitizer, SecurityContext, ViewEncapsulation, WrappedValue} from '@angular/core';
|
import {Injector, PipeTransform, RenderComponentType, RootRenderer, Sanitizer, SecurityContext, ViewEncapsulation, WrappedValue} from '@angular/core';
|
||||||
import {ArgumentType, NodeDef, NodeFlags, RootData, Services, ViewData, ViewDefinition, ViewFlags, ViewHandleEventFn, ViewUpdateFn, anchorDef, asProviderData, asPureExpressionData, directiveDef, elementDef, pureArrayDef, pureObjectDef, purePipeDef, rootRenderNodes, textDef, viewDef} from '@angular/core/src/view/index';
|
import {ArgumentType, NodeDef, NodeFlags, RootData, Services, ViewData, ViewDefinition, ViewFlags, ViewHandleEventFn, ViewUpdateFn, anchorDef, asProviderData, asPureExpressionData, directiveDef, elementDef, nodeValue, pipeDef, pureArrayDef, pureObjectDef, purePipeDef, rootRenderNodes, textDef, viewDef} from '@angular/core/src/view/index';
|
||||||
import {inject} from '@angular/core/testing';
|
import {inject} from '@angular/core/testing';
|
||||||
|
|
||||||
import {ARG_TYPE_VALUES, checkNodeInlineOrDynamic, createRootView} from './helper';
|
import {ARG_TYPE_VALUES, checkNodeInlineOrDynamic, createRootView} from './helper';
|
||||||
|
@ -15,9 +15,9 @@ import {ARG_TYPE_VALUES, checkNodeInlineOrDynamic, createRootView} from './helpe
|
||||||
export function main() {
|
export function main() {
|
||||||
describe(`View Pure Expressions`, () => {
|
describe(`View Pure Expressions`, () => {
|
||||||
function compViewDef(
|
function compViewDef(
|
||||||
nodes: NodeDef[], update?: ViewUpdateFn, handleEvent?: ViewHandleEventFn,
|
nodes: NodeDef[], updateDirectives?: ViewUpdateFn, updateRenderer?: ViewUpdateFn,
|
||||||
viewFlags: ViewFlags = ViewFlags.None): ViewDefinition {
|
handleEvent?: ViewHandleEventFn, viewFlags: ViewFlags = ViewFlags.None): ViewDefinition {
|
||||||
return viewDef(viewFlags, nodes, update, handleEvent);
|
return viewDef(viewFlags, nodes, updateDirectives, updateRenderer, handleEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
function createAndGetRootNodes(viewDef: ViewDefinition): {rootNodes: any[], view: ViewData} {
|
function createAndGetRootNodes(viewDef: ViewDefinition): {rootNodes: any[], view: ViewData} {
|
||||||
|
@ -64,32 +64,6 @@ export function main() {
|
||||||
expect(arr1).toEqual([3, 2]);
|
expect(arr1).toEqual([3, 2]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it(`should unwrap values with ${ArgumentType[inlineDynamic]}`, () => {
|
|
||||||
let bindingValue: any;
|
|
||||||
const {view, rootNodes} = createAndGetRootNodes(compViewDef(
|
|
||||||
[
|
|
||||||
elementDef(NodeFlags.None, null, null, 1, 'span'),
|
|
||||||
pureArrayDef(1),
|
|
||||||
],
|
|
||||||
(check, view) => {
|
|
||||||
checkNodeInlineOrDynamic(check, view, 1, inlineDynamic, [bindingValue]);
|
|
||||||
}));
|
|
||||||
|
|
||||||
const exprData = asPureExpressionData(view, 1);
|
|
||||||
|
|
||||||
bindingValue = 'v1';
|
|
||||||
Services.checkAndUpdateView(view);
|
|
||||||
const v1Arr = exprData.value;
|
|
||||||
expect(v1Arr).toEqual(['v1']);
|
|
||||||
|
|
||||||
Services.checkAndUpdateView(view);
|
|
||||||
expect(exprData.value).toBe(v1Arr);
|
|
||||||
|
|
||||||
bindingValue = WrappedValue.wrap('v1');
|
|
||||||
Services.checkAndUpdateView(view);
|
|
||||||
expect(exprData.value).not.toBe(v1Arr);
|
|
||||||
expect(exprData.value).toEqual(['v1']);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -127,32 +101,6 @@ export function main() {
|
||||||
expect(obj1).toEqual({a: 3, b: 2});
|
expect(obj1).toEqual({a: 3, b: 2});
|
||||||
});
|
});
|
||||||
|
|
||||||
it(`should unwrap values with ${ArgumentType[inlineDynamic]}`, () => {
|
|
||||||
let bindingValue: any;
|
|
||||||
const {view, rootNodes} = createAndGetRootNodes(compViewDef(
|
|
||||||
[
|
|
||||||
elementDef(NodeFlags.None, null, null, 1, 'span'),
|
|
||||||
pureObjectDef(['a']),
|
|
||||||
],
|
|
||||||
(check, view) => {
|
|
||||||
checkNodeInlineOrDynamic(check, view, 1, inlineDynamic, [bindingValue]);
|
|
||||||
}));
|
|
||||||
|
|
||||||
const exprData = asPureExpressionData(view, 1);
|
|
||||||
|
|
||||||
bindingValue = 'v1';
|
|
||||||
Services.checkAndUpdateView(view);
|
|
||||||
const v1Obj = exprData.value;
|
|
||||||
expect(v1Obj).toEqual({'a': 'v1'});
|
|
||||||
|
|
||||||
Services.checkAndUpdateView(view);
|
|
||||||
expect(exprData.value).toBe(v1Obj);
|
|
||||||
|
|
||||||
bindingValue = WrappedValue.wrap('v1');
|
|
||||||
Services.checkAndUpdateView(view);
|
|
||||||
expect(exprData.value).not.toBe(v1Obj);
|
|
||||||
expect(exprData.value).toEqual({'a': 'v1'});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -168,11 +116,12 @@ export function main() {
|
||||||
const {view, rootNodes} = createAndGetRootNodes(compViewDef(
|
const {view, rootNodes} = createAndGetRootNodes(compViewDef(
|
||||||
[
|
[
|
||||||
elementDef(NodeFlags.None, null, null, 3, 'span'),
|
elementDef(NodeFlags.None, null, null, 3, 'span'),
|
||||||
directiveDef(NodeFlags.None, null, 0, SomePipe, []), purePipeDef(SomePipe, 2),
|
pipeDef(NodeFlags.None, SomePipe, []), purePipeDef(2),
|
||||||
directiveDef(NodeFlags.None, null, 0, Service, [], {data: [0, 'data']})
|
directiveDef(NodeFlags.None, null, 0, Service, [], {data: [0, 'data']})
|
||||||
],
|
],
|
||||||
(check, view) => {
|
(check, view) => {
|
||||||
const pureValue = checkNodeInlineOrDynamic(check, view, 2, inlineDynamic, values);
|
const pureValue = checkNodeInlineOrDynamic(
|
||||||
|
check, view, 2, inlineDynamic, [nodeValue(view, 1)].concat(values));
|
||||||
checkNodeInlineOrDynamic(check, view, 3, inlineDynamic, [pureValue]);
|
checkNodeInlineOrDynamic(check, view, 3, inlineDynamic, [pureValue]);
|
||||||
}));
|
}));
|
||||||
const service = asProviderData(view, 3).instance;
|
const service = asProviderData(view, 3).instance;
|
||||||
|
@ -194,36 +143,6 @@ export function main() {
|
||||||
expect(obj1).toEqual([13, 22]);
|
expect(obj1).toEqual([13, 22]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it(`should unwrap values with ${ArgumentType[inlineDynamic]}`, () => {
|
|
||||||
let bindingValue: any;
|
|
||||||
let transformSpy = jasmine.createSpy('transform');
|
|
||||||
|
|
||||||
class SomePipe implements PipeTransform {
|
|
||||||
transform = transformSpy;
|
|
||||||
}
|
|
||||||
|
|
||||||
const {view, rootNodes} = createAndGetRootNodes(compViewDef(
|
|
||||||
[
|
|
||||||
elementDef(NodeFlags.None, null, null, 2, 'span'),
|
|
||||||
directiveDef(NodeFlags.None, null, 0, SomePipe, []),
|
|
||||||
purePipeDef(SomePipe, 1),
|
|
||||||
],
|
|
||||||
(check, view) => {
|
|
||||||
checkNodeInlineOrDynamic(check, view, 2, inlineDynamic, [bindingValue]);
|
|
||||||
}));
|
|
||||||
|
|
||||||
bindingValue = 'v1';
|
|
||||||
Services.checkAndUpdateView(view);
|
|
||||||
expect(transformSpy).toHaveBeenCalledWith('v1');
|
|
||||||
|
|
||||||
transformSpy.calls.reset();
|
|
||||||
Services.checkAndUpdateView(view);
|
|
||||||
expect(transformSpy).not.toHaveBeenCalled();
|
|
||||||
|
|
||||||
bindingValue = WrappedValue.wrap('v1');
|
|
||||||
Services.checkAndUpdateView(view);
|
|
||||||
expect(transformSpy).toHaveBeenCalledWith('v1');
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -17,9 +17,9 @@ import {createRootView} from './helper';
|
||||||
export function main() {
|
export function main() {
|
||||||
describe(`Query Views`, () => {
|
describe(`Query Views`, () => {
|
||||||
function compViewDef(
|
function compViewDef(
|
||||||
nodes: NodeDef[], update?: ViewUpdateFn, handleEvent?: ViewHandleEventFn,
|
nodes: NodeDef[], updateDirectives?: ViewUpdateFn, updateRenderer?: ViewUpdateFn,
|
||||||
viewFlags: ViewFlags = ViewFlags.None): ViewDefinition {
|
handleEvent?: ViewHandleEventFn, viewFlags: ViewFlags = ViewFlags.None): ViewDefinition {
|
||||||
return viewDef(viewFlags, nodes, update, handleEvent);
|
return viewDef(viewFlags, nodes, updateDirectives, updateRenderer, handleEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
function embeddedViewDef(nodes: NodeDef[], update?: ViewUpdateFn): ViewDefinitionFactory {
|
function embeddedViewDef(nodes: NodeDef[], update?: ViewUpdateFn): ViewDefinitionFactory {
|
||||||
|
|
|
@ -16,9 +16,9 @@ import {createRootView, isBrowser} from './helper';
|
||||||
export function main() {
|
export function main() {
|
||||||
describe('View Services', () => {
|
describe('View Services', () => {
|
||||||
function compViewDef(
|
function compViewDef(
|
||||||
nodes: NodeDef[], update?: ViewUpdateFn, handleEvent?: ViewHandleEventFn,
|
nodes: NodeDef[], updateDirectives?: ViewUpdateFn, updateRenderer?: ViewUpdateFn,
|
||||||
viewFlags: ViewFlags = ViewFlags.None): ViewDefinition {
|
handleEvent?: ViewHandleEventFn, viewFlags: ViewFlags = ViewFlags.None): ViewDefinition {
|
||||||
return viewDef(viewFlags, nodes, update, handleEvent);
|
return viewDef(viewFlags, nodes, updateDirectives, updateRenderer, handleEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
function createAndGetRootNodes(
|
function createAndGetRootNodes(
|
||||||
|
|
|
@ -16,9 +16,9 @@ import {ARG_TYPE_VALUES, checkNodeInlineOrDynamic, createRootView, isBrowser} fr
|
||||||
export function main() {
|
export function main() {
|
||||||
describe(`View Text`, () => {
|
describe(`View Text`, () => {
|
||||||
function compViewDef(
|
function compViewDef(
|
||||||
nodes: NodeDef[], update?: ViewUpdateFn, handleEvent?: ViewHandleEventFn,
|
nodes: NodeDef[], updateDirectives?: ViewUpdateFn, updateRenderer?: ViewUpdateFn,
|
||||||
viewFlags: ViewFlags = ViewFlags.None): ViewDefinition {
|
handleEvent?: ViewHandleEventFn, viewFlags: ViewFlags = ViewFlags.None): ViewDefinition {
|
||||||
return viewDef(viewFlags, nodes, update, handleEvent);
|
return viewDef(viewFlags, nodes, updateDirectives, updateRenderer, handleEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
function createAndGetRootNodes(
|
function createAndGetRootNodes(
|
||||||
|
@ -67,7 +67,7 @@ export function main() {
|
||||||
[
|
[
|
||||||
textDef(null, ['0', '1', '2']),
|
textDef(null, ['0', '1', '2']),
|
||||||
],
|
],
|
||||||
(check, view) => {
|
null, (check, view) => {
|
||||||
checkNodeInlineOrDynamic(check, view, 0, inlineDynamic, ['a', 'b']);
|
checkNodeInlineOrDynamic(check, view, 0, inlineDynamic, ['a', 'b']);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@ -77,41 +77,6 @@ export function main() {
|
||||||
expect(getDOM().getText(rootNodes[0])).toBe('0a1b2');
|
expect(getDOM().getText(rootNodes[0])).toBe('0a1b2');
|
||||||
});
|
});
|
||||||
|
|
||||||
if (isBrowser()) {
|
|
||||||
it(`should unwrap values with ${ArgumentType[inlineDynamic]}`, () => {
|
|
||||||
let bindingValue: any;
|
|
||||||
const setterSpy = jasmine.createSpy('set');
|
|
||||||
|
|
||||||
class FakeTextNode {
|
|
||||||
set nodeValue(value: any) { setterSpy(value); }
|
|
||||||
}
|
|
||||||
|
|
||||||
spyOn(document, 'createTextNode').and.returnValue(new FakeTextNode());
|
|
||||||
|
|
||||||
const {view, rootNodes} = createAndGetRootNodes(compViewDef(
|
|
||||||
[
|
|
||||||
textDef(null, ['', '']),
|
|
||||||
],
|
|
||||||
(check, view) => {
|
|
||||||
checkNodeInlineOrDynamic(check, view, 0, inlineDynamic, [bindingValue]);
|
|
||||||
}));
|
|
||||||
|
|
||||||
Object.defineProperty(rootNodes[0], 'nodeValue', {set: setterSpy});
|
|
||||||
|
|
||||||
bindingValue = 'v1';
|
|
||||||
Services.checkAndUpdateView(view);
|
|
||||||
expect(setterSpy).toHaveBeenCalledWith('v1');
|
|
||||||
|
|
||||||
setterSpy.calls.reset();
|
|
||||||
Services.checkAndUpdateView(view);
|
|
||||||
expect(setterSpy).not.toHaveBeenCalled();
|
|
||||||
|
|
||||||
setterSpy.calls.reset();
|
|
||||||
bindingValue = WrappedValue.wrap('v1');
|
|
||||||
Services.checkAndUpdateView(view);
|
|
||||||
expect(setterSpy).toHaveBeenCalledWith('v1');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -75,10 +75,13 @@ function TreeComponent_0(): ViewDefinition {
|
||||||
],
|
],
|
||||||
(check, view) => {
|
(check, view) => {
|
||||||
const cmp = view.component;
|
const cmp = view.component;
|
||||||
check(view, 0, ArgumentType.Inline, cmp.bgColor);
|
|
||||||
check(view, 1, ArgumentType.Inline, cmp.data.value);
|
|
||||||
check(view, 3, ArgumentType.Inline, cmp.data.left != null);
|
check(view, 3, ArgumentType.Inline, cmp.data.left != null);
|
||||||
check(view, 5, ArgumentType.Inline, cmp.data.right != null);
|
check(view, 5, ArgumentType.Inline, cmp.data.right != null);
|
||||||
|
},
|
||||||
|
(check, view) => {
|
||||||
|
const cmp = view.component;
|
||||||
|
check(view, 0, ArgumentType.Inline, cmp.bgColor);
|
||||||
|
check(view, 1, ArgumentType.Inline, cmp.data.value);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue