parent
08aa54e1d9
commit
bbb8f386f1
|
@ -32,6 +32,9 @@ const TEMPORARY_NAME = '_t';
|
||||||
/** The prefix reference variables */
|
/** The prefix reference variables */
|
||||||
const REFERENCE_PREFIX = '_r';
|
const REFERENCE_PREFIX = '_r';
|
||||||
|
|
||||||
|
/** The name of the implicit context reference */
|
||||||
|
const IMPLICIT_REFERENCE = '$implicit';
|
||||||
|
|
||||||
export function compileDirective(
|
export function compileDirective(
|
||||||
outputCtx: OutputContext, directive: CompileDirectiveMetadata, reflector: CompileReflector) {
|
outputCtx: OutputContext, directive: CompileDirectiveMetadata, reflector: CompileReflector) {
|
||||||
const definitionMapValues: {key: string, quoted: boolean, value: o.Expression}[] = [];
|
const definitionMapValues: {key: string, quoted: boolean, value: o.Expression}[] = [];
|
||||||
|
@ -98,7 +101,7 @@ export function compileComponent(
|
||||||
new TemplateDefinitionBuilder(
|
new TemplateDefinitionBuilder(
|
||||||
outputCtx, outputCtx.constantPool, reflector, CONTEXT_NAME, ROOT_SCOPE.nestedScope(), 0,
|
outputCtx, outputCtx.constantPool, reflector, CONTEXT_NAME, ROOT_SCOPE.nestedScope(), 0,
|
||||||
templateTypeName, templateName)
|
templateTypeName, templateName)
|
||||||
.buildTemplateFunction(template);
|
.buildTemplateFunction(template, []);
|
||||||
definitionMapValues.push({key: 'template', value: templateFunctionExpression, quoted: false});
|
definitionMapValues.push({key: 'template', value: templateFunctionExpression, quoted: false});
|
||||||
|
|
||||||
|
|
||||||
|
@ -223,7 +226,24 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
|
||||||
private bindingScope: BindingScope, private level = 0, private contextName: string|null,
|
private bindingScope: BindingScope, private level = 0, private contextName: string|null,
|
||||||
private templateName: string|null) {}
|
private templateName: string|null) {}
|
||||||
|
|
||||||
buildTemplateFunction(asts: TemplateAst[]): o.FunctionExpr {
|
buildTemplateFunction(asts: TemplateAst[], variables: VariableAst[]): o.FunctionExpr {
|
||||||
|
// Create variable bindings
|
||||||
|
for (const variable of variables) {
|
||||||
|
const variableName = variable.name;
|
||||||
|
const expression =
|
||||||
|
o.variable(this.contextParameter).prop(variable.value || IMPLICIT_REFERENCE);
|
||||||
|
const scopedName = this.bindingScope.freshReferenceName();
|
||||||
|
const declaration = o.variable(scopedName).set(expression).toDeclStmt(o.INFERRED_TYPE, [
|
||||||
|
o.StmtModifier.Final
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Add the reference to the local scope.
|
||||||
|
this.bindingScope.set(variableName, scopedName);
|
||||||
|
|
||||||
|
// Declare the local variable in binding mode
|
||||||
|
this._bindingMode.push(declaration);
|
||||||
|
}
|
||||||
|
|
||||||
templateVisitAll(this, asts);
|
templateVisitAll(this, asts);
|
||||||
|
|
||||||
return o.fn(
|
return o.fn(
|
||||||
|
@ -395,8 +415,9 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
|
||||||
this, implicit, input.value, this.bindingContext(), BindingForm.TrySimple, interpolate);
|
this, implicit, input.value, this.bindingContext(), BindingForm.TrySimple, interpolate);
|
||||||
this._bindingMode.push(...convertedBinding.stmts);
|
this._bindingMode.push(...convertedBinding.stmts);
|
||||||
this.instruction(
|
this.instruction(
|
||||||
this._bindingMode, directive.sourceSpan, R3.elementProperty,
|
this._bindingMode, directive.sourceSpan, R3.elementProperty, o.literal(nodeIndex),
|
||||||
o.literal(input.templateName), o.literal(nodeIndex), convertedBinding.currValExpr);
|
o.literal(input.templateName),
|
||||||
|
o.importExpr(R3.bind).callFn([convertedBinding.currValExpr]));
|
||||||
}
|
}
|
||||||
|
|
||||||
// e.g. TodoComponentDef.r(0, 0);
|
// e.g. TodoComponentDef.r(0, 0);
|
||||||
|
@ -445,7 +466,7 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
|
||||||
const templateVisitor = new TemplateDefinitionBuilder(
|
const templateVisitor = new TemplateDefinitionBuilder(
|
||||||
this.outputCtx, this.constantPool, this.reflector, templateContext,
|
this.outputCtx, this.constantPool, this.reflector, templateContext,
|
||||||
this.bindingScope.nestedScope(), this.level + 1, contextName, templateName);
|
this.bindingScope.nestedScope(), this.level + 1, contextName, templateName);
|
||||||
const templateFunctionExpr = templateVisitor.buildTemplateFunction(ast.children);
|
const templateFunctionExpr = templateVisitor.buildTemplateFunction(ast.children, ast.variables);
|
||||||
this._postfix.push(templateFunctionExpr.toDeclStmt(templateName, null));
|
this._postfix.push(templateFunctionExpr.toDeclStmt(templateName, null));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -331,6 +331,224 @@ describe('r3_view_compiler', () => {
|
||||||
expectEmit(source, MyComponentDefinition, 'Incorrect MyComponent.ngComponentDef');
|
expectEmit(source, MyComponentDefinition, 'Incorrect MyComponent.ngComponentDef');
|
||||||
expectEmit(source, locals, 'Incorrect locals constant definition');
|
expectEmit(source, locals, 'Incorrect locals constant definition');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('template variables', () => {
|
||||||
|
const shared = {
|
||||||
|
shared: {
|
||||||
|
'for_of.ts': `
|
||||||
|
import {Directive, Input, SimpleChanges, TemplateRef, ViewContainerRef} from '@angular/core';
|
||||||
|
|
||||||
|
export interface ForOfContext {
|
||||||
|
$implicit: any;
|
||||||
|
index: number;
|
||||||
|
even: boolean;
|
||||||
|
odd: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Directive({selector: '[forOf]'})
|
||||||
|
export class ForOfDirective {
|
||||||
|
private previous: any[];
|
||||||
|
|
||||||
|
constructor(private view: ViewContainerRef, private template: TemplateRef<any>) {}
|
||||||
|
|
||||||
|
@Input() forOf: any[];
|
||||||
|
|
||||||
|
ngOnChanges(simpleChanges: SimpleChanges) {
|
||||||
|
if ('forOf' in simpleChanges) {
|
||||||
|
this.update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ngDoCheck(): void {
|
||||||
|
const previous = this.previous;
|
||||||
|
const current = this.forOf;
|
||||||
|
if (!previous || previous.length != current.length ||
|
||||||
|
previous.some((value: any, index: number) => current[index] !== previous[index])) {
|
||||||
|
this.update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private update() {
|
||||||
|
// TODO(chuckj): Not implemented yet
|
||||||
|
// this.view.clear();
|
||||||
|
if (this.forOf) {
|
||||||
|
const current = this.forOf;
|
||||||
|
for (let i = 0; i < current.length; i++) {
|
||||||
|
const context = {$implicit: current[i], index: i, even: i % 2 == 0, odd: i % 2 == 1};
|
||||||
|
// TODO(chuckj): Not implemented yet
|
||||||
|
// this.view.createEmbeddedView(this.template, context);
|
||||||
|
}
|
||||||
|
this.previous = [...this.forOf];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
it('should support a let variable and reference', () => {
|
||||||
|
const files = {
|
||||||
|
app: {
|
||||||
|
...shared,
|
||||||
|
'spec.ts': `
|
||||||
|
import {Component, NgModule} from '@angular/core';
|
||||||
|
import {ForOfDirective} from './shared/for_of';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'my-component',
|
||||||
|
template: \`<ul><li *for="let item of items">{{item.name}}</li></ul>\`
|
||||||
|
})
|
||||||
|
export class MyComponent {
|
||||||
|
items = [{name: 'one'}, {name: 'two'}];
|
||||||
|
}
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [MyComponent, ForOfDirective]
|
||||||
|
})
|
||||||
|
export class MyModule {}
|
||||||
|
`
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO(chuckj): Enforce this when the directives are specified
|
||||||
|
const ForDirectiveDefinition = `
|
||||||
|
static ngDirectiveDef = IDENT.ɵdefineDirective({
|
||||||
|
factory: function ForOfDirective_Factory() {
|
||||||
|
return new ForOfDirective(IDENT.ɵinjectViewContainerRef(), IDENT.ɵinjectTemplateRef());
|
||||||
|
},
|
||||||
|
features: [IDENT.ɵNgOnChangesFeature(NgForOf)],
|
||||||
|
refresh: function ForOfDirective_Refresh(directiveIndex: IDENT, elementIndex: IDENT) {
|
||||||
|
IDENT.ɵm<ForOfDirective>(directiveIndex).ngDoCheck();
|
||||||
|
},
|
||||||
|
inputs: {forOf: 'forOf'}
|
||||||
|
});
|
||||||
|
`;
|
||||||
|
|
||||||
|
const MyComponentDefinition = `
|
||||||
|
static ngComponentDef = IDENT.ɵdefineComponent({
|
||||||
|
tag: 'my-component',
|
||||||
|
factory: function MyComponent_Factory() { return new MyComponent(); },
|
||||||
|
template: function MyComponent_Template(ctx: IDENT, cm: IDENT) {
|
||||||
|
if (cm) {
|
||||||
|
IDENT.ɵE(0, 'ul');
|
||||||
|
IDENT.ɵC(1, IDENT, MyComponent_ForOfDirective_Template_1);
|
||||||
|
IDENT.ɵe();
|
||||||
|
}
|
||||||
|
IDENT.ɵp(1, 'forOf', IDENT.ɵb(ctx.items));
|
||||||
|
IDENT.ɵcR(1);
|
||||||
|
ForOfDirective.ngDirectiveDef.r(2, 1);
|
||||||
|
IDENT.ɵcr();
|
||||||
|
|
||||||
|
function MyComponent_ForOfDirective_Template_1(ctx0: IDENT, cm: IDENT) {
|
||||||
|
if (cm) {
|
||||||
|
IDENT.ɵE(0, 'li');
|
||||||
|
IDENT.ɵT(1);
|
||||||
|
IDENT.ɵe();
|
||||||
|
}
|
||||||
|
const IDENT = ctx0.$implicit;
|
||||||
|
IDENT.ɵt(1, IDENT.ɵb1('', IDENT.name, ''));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
`;
|
||||||
|
|
||||||
|
const result = compile(files, angularFiles);
|
||||||
|
const source = result.source;
|
||||||
|
|
||||||
|
// TODO(chuckj): Enforce this when the directives are specified
|
||||||
|
// expectEmit(source, ForDirectiveDefinition, 'Invalid directive definition');
|
||||||
|
expectEmit(source, MyComponentDefinition, 'Invalid component definition');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support accessing parent template variables', () => {
|
||||||
|
const files = {
|
||||||
|
app: {
|
||||||
|
...shared,
|
||||||
|
'spec.ts': `
|
||||||
|
import {Component, NgModule} from '@angular/core';
|
||||||
|
import {ForOfDirective} from './shared/for_of';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'my-component',
|
||||||
|
template: \`
|
||||||
|
<ul>
|
||||||
|
<li *for="let item of items">
|
||||||
|
<div>{{item.name}}</div>
|
||||||
|
<ul>
|
||||||
|
<li *for="let info of item.infos">
|
||||||
|
{{item.name}}: {{info.description}}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>\`
|
||||||
|
})
|
||||||
|
export class MyComponent {
|
||||||
|
items: Item[] = [
|
||||||
|
{name: 'one', infos: [{description: '11'}, {description: '12'}]},
|
||||||
|
{name: 'two', infos: [{description: '21'}, {description: '22'}]}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [MyComponent, ForOfDirective]
|
||||||
|
})
|
||||||
|
export class MyModule {}
|
||||||
|
`
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const MyComponentDefinition = `
|
||||||
|
static ngComponentDef = IDENT.ɵdefineComponent({
|
||||||
|
tag: 'my-component',
|
||||||
|
factory: function MyComponent_Factory() { return new MyComponent(); },
|
||||||
|
template: function MyComponent_Template(ctx: IDENT, cm: IDENT) {
|
||||||
|
if (cm) {
|
||||||
|
IDENT.ɵE(0, 'ul');
|
||||||
|
IDENT.ɵC(1, IDENT, MyComponent_ForOfDirective_Template_1);
|
||||||
|
IDENT.ɵe();
|
||||||
|
}
|
||||||
|
IDENT.ɵp(1, 'forOf', IDENT.ɵb(ctx.items));
|
||||||
|
IDENT.ɵcR(1);
|
||||||
|
IDENT.r(2, 1);
|
||||||
|
IDENT.ɵcr();
|
||||||
|
|
||||||
|
function MyComponent_ForOfDirective_Template_1(ctx0: IDENT, cm: IDENT) {
|
||||||
|
if (cm) {
|
||||||
|
IDENT.ɵE(0, 'li');
|
||||||
|
IDENT.ɵE(1, 'div');
|
||||||
|
IDENT.ɵT(2);
|
||||||
|
IDENT.ɵe();
|
||||||
|
IDENT.ɵE(3, 'ul');
|
||||||
|
IDENT.ɵC(4, IDENT, MyComponent_ForOfDirective_ForOfDirective_Template_4);
|
||||||
|
IDENT.ɵe();
|
||||||
|
IDENT.ɵe();
|
||||||
|
}
|
||||||
|
const IDENT = ctx0.$implicit;
|
||||||
|
IDENT.ɵp(4, 'forOf', IDENT.ɵb(IDENT.infos));
|
||||||
|
IDENT.ɵt(2, IDENT.ɵb1('', IDENT.name, ''));
|
||||||
|
IDENT.ɵcR(4);
|
||||||
|
IDENT.r(5, 4);
|
||||||
|
IDENT.ɵcr();
|
||||||
|
|
||||||
|
function MyComponent_ForOfDirective_ForOfDirective_Template_4(
|
||||||
|
ctx1: IDENT, cm: IDENT) {
|
||||||
|
if (cm) {
|
||||||
|
IDENT.ɵE(0, 'li');
|
||||||
|
IDENT.ɵT(1);
|
||||||
|
IDENT.ɵe();
|
||||||
|
}
|
||||||
|
const IDENT = ctx1.$implicit;
|
||||||
|
IDENT.ɵt(1, IDENT.ɵb2(' ', IDENT.name, ': ', IDENT.description, ' '));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});`;
|
||||||
|
|
||||||
|
const result = compile(files, angularFiles);
|
||||||
|
const source = result.source;
|
||||||
|
expectEmit(source, MyComponentDefinition, 'Invalid component definition');
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -389,7 +607,7 @@ function expectEmit(source: string, emitted: string, description: string) {
|
||||||
if (!m) {
|
if (!m) {
|
||||||
const contextPieceWidth = contextWidth / 2;
|
const contextPieceWidth = contextWidth / 2;
|
||||||
fail(
|
fail(
|
||||||
`${description}: Expected to find ${expected} '${source.substr(0,last)}[<---HERE]${source.substr(last)}'`);
|
`${description}: Expected to find ${expected} '${source.substr(0,last)}[<---HERE expected "${expected}"]${source.substr(last)}'`);
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
last = (m.index || 0) + m[0].length;
|
last = (m.index || 0) + m[0].length;
|
||||||
|
@ -401,7 +619,7 @@ function expectEmit(source: string, emitted: string, description: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const IDENT_LIKE = /^[a-z][A-Z]/;
|
const IDENT_LIKE = /^[a-z][A-Z]/;
|
||||||
const SPECIAL_RE_CHAR = /\/|\(|\)|\||\*|\+|\[|\]|\{|\}/g;
|
const SPECIAL_RE_CHAR = /\/|\(|\)|\||\*|\+|\[|\]|\{|\}|\$/g;
|
||||||
function r(...pieces: (string | RegExp)[]): RegExp {
|
function r(...pieces: (string | RegExp)[]): RegExp {
|
||||||
let results: string[] = [];
|
let results: string[] = [];
|
||||||
let first = true;
|
let first = true;
|
||||||
|
|
Loading…
Reference in New Issue