feat(core): separate refs from vars.

Introduces `ref-` to give a name to an element or a directive (also works for `<template>` elements), and `let-` to introduce an input variable for a `<template>` element.

BREAKING CHANGE:
- `#...` now always means `ref-`.
- `<template #abc>` now defines a reference to the TemplateRef, instead of an input variable used inside of the template.
- `#...` inside of a *ngIf, … directives is deprecated.
  Use `let …` instead.
- `var-...` is deprecated. Replace with `let-...` for `<template>` elements and `ref-` for non `<template>` elements.

Closes #7158

Closes #8264
This commit is contained in:
Tobias Bosch
2016-04-25 19:52:24 -07:00
parent ff2ae7a2e1
commit d2efac18ed
69 changed files with 651 additions and 414 deletions

View File

@ -438,7 +438,7 @@ export function main() {
it('should read locals', fakeAsync(() => {
var ctx =
createCompFixture('<template testLocals var-local="someLocal">{{local}}</template>');
createCompFixture('<template testLocals let-local="someLocal">{{local}}</template>');
ctx.detectChanges(false);
expect(renderLog.log).toEqual(['{{someLocalValue}}']);
@ -581,7 +581,7 @@ export function main() {
it('should throw when trying to assign to a local', fakeAsync(() => {
expect(() => {_bindSimpleProp('(event)="$event=1"')})
.toThrowError(new RegExp("Cannot reassign a variable binding"));
.toThrowError(new RegExp("Cannot assign to a reference or variable!"));
}));
it('should support short-circuiting', fakeAsync(() => {
@ -609,7 +609,7 @@ export function main() {
it('should read directive properties', fakeAsync(() => {
var ctx =
createCompFixture(
'<div testDirective [a]="42" var-dir="testDirective" [someProp]="dir.a"></div>')
'<div testDirective [a]="42" ref-dir="testDirective" [someProp]="dir.a"></div>')
ctx.detectChanges(false);
expect(renderLog.loggedValues).toEqual([42]);
}));

View File

@ -259,7 +259,7 @@ class OnChangeComponent implements OnChanges {
])
@View(
template:
'<span *ngFor="#item of list">{{item}}</span><directive-logging-checks></directive-logging-checks>',
'<span *ngFor="let item of list">{{item}}</span><directive-logging-checks></directive-logging-checks>',
directives: const [NgFor, DirectiveLoggingChecks])
class ComponentWithObservableList {
Iterable list;

View File

@ -89,7 +89,7 @@ import {EmbeddedViewRef} from 'angular2/src/core/linker/view_ref';
import {ComponentResolver} from 'angular2/src/core/linker/component_resolver';
import {ElementRef} from 'angular2/src/core/linker/element_ref';
import {TemplateRef} from 'angular2/src/core/linker/template_ref';
import {TemplateRef_, TemplateRef} from 'angular2/src/core/linker/template_ref';
import {Renderer} from 'angular2/src/core/render';
@ -473,7 +473,7 @@ function declareTests(isJit: boolean) {
tcb.overrideView(
MyComp, new ViewMetadata({
template:
'<template some-viewport var-greeting="some-tmpl"><copy-me>{{greeting}}</copy-me></template>',
'<template some-viewport let-greeting="some-tmpl"><copy-me>{{greeting}}</copy-me></template>',
directives: [SomeViewport]
}))
@ -509,7 +509,7 @@ function declareTests(isJit: boolean) {
tcb.overrideView(
MyComp, new ViewMetadata({
template:
'<copy-me template="some-viewport: var greeting=some-tmpl">{{greeting}}</copy-me>',
'<copy-me template="some-viewport: let greeting=some-tmpl">{{greeting}}</copy-me>',
directives: [SomeViewport]
}))
@ -531,7 +531,7 @@ function declareTests(isJit: boolean) {
tcb.overrideView(
MyComp, new ViewMetadata({
template:
'<some-directive><toolbar><template toolbarpart var-toolbarProp="toolbarProp">{{ctxProp}},{{toolbarProp}},<cmp-with-host></cmp-with-host></template></toolbar></some-directive>',
'<some-directive><toolbar><template toolbarpart let-toolbarProp="toolbarProp">{{ctxProp}},{{toolbarProp}},<cmp-with-host></cmp-with-host></template></toolbar></some-directive>',
directives: [SomeDirective, CompWithHost, ToolbarComponent, ToolbarPart]
}))
.createAsync(MyComp)
@ -547,12 +547,12 @@ function declareTests(isJit: boolean) {
});
}));
describe("variable bindings", () => {
it('should assign a component to a var-',
describe("reference bindings", () => {
it('should assign a component to a ref-',
inject([TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async) => {
tcb.overrideView(MyComp, new ViewMetadata({
template: '<p><child-cmp var-alice></child-cmp></p>',
template: '<p><child-cmp ref-alice></child-cmp></p>',
directives: [ChildComp]
}))
@ -564,7 +564,7 @@ function declareTests(isJit: boolean) {
async.done();
})}));
it('should assign a directive to a var-',
it('should assign a directive to a ref-',
inject(
[TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
tcb.overrideView(MyComp, new ViewMetadata({
@ -588,7 +588,7 @@ function declareTests(isJit: boolean) {
tcb.overrideView(
MyComp, new ViewMetadata({
template:
'<template [ngIf]="true">{{alice.ctxProp}}</template>|{{alice.ctxProp}}|<child-cmp var-alice></child-cmp>',
'<template [ngIf]="true">{{alice.ctxProp}}</template>|{{alice.ctxProp}}|<child-cmp ref-alice></child-cmp>',
directives: [ChildComp, NgIf]
}))
@ -600,14 +600,14 @@ function declareTests(isJit: boolean) {
async.done();
})}));
it('should assign two component instances each with a var-',
it('should assign two component instances each with a ref-',
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async) => {
tcb.overrideView(
MyComp, new ViewMetadata({
template:
'<p><child-cmp var-alice></child-cmp><child-cmp var-bob></child-cmp></p>',
'<p><child-cmp ref-alice></child-cmp><child-cmp ref-bob></child-cmp></p>',
directives: [ChildComp]
}))
@ -622,7 +622,7 @@ function declareTests(isJit: boolean) {
async.done();
})}));
it('should assign the component instance to a var- with shorthand syntax',
it('should assign the component instance to a ref- with shorthand syntax',
inject([TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder,
async) => {tcb.overrideView(MyComp, new ViewMetadata({
@ -643,7 +643,7 @@ function declareTests(isJit: boolean) {
inject([TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async) => {
tcb.overrideView(MyComp, new ViewMetadata({
template: '<div><div var-alice><i>Hello</i></div></div>'
template: '<div><div ref-alice><i>Hello</i></div></div>'
}))
.createAsync(MyComp)
@ -657,11 +657,26 @@ function declareTests(isJit: boolean) {
async.done();
})}));
it('should assign the TemplateRef to a user-defined variable',
inject([TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async) => {
tcb.overrideView(MyComp, new ViewMetadata(
{template: '<template ref-alice></template>'}))
.createAsync(MyComp)
.then((fixture) => {
var value = fixture.debugElement.childNodes[0].getLocal('alice');
expect(value).toBeAnInstanceOf(TemplateRef_);
async.done();
})}));
it('should preserve case',
inject(
[TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
tcb.overrideView(MyComp, new ViewMetadata({
template: '<p><child-cmp var-superAlice></child-cmp></p>',
template: '<p><child-cmp ref-superAlice></child-cmp></p>',
directives: [ChildComp]
}))
@ -673,14 +688,16 @@ function declareTests(isJit: boolean) {
async.done();
});
}));
});
describe('variables', () => {
it('should allow to use variables in a for loop',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder,
async) => {
tcb.overrideView(
MyComp, new ViewMetadata({
template:
'<template ngFor [ngForOf]="[1]" var-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>',
directives: [ChildCompNoTemplate, NgFor]
}))
@ -2281,7 +2298,7 @@ class ToolbarViewContainer {
@Component({
selector: 'toolbar',
template: 'TOOLBAR(<div *ngFor="var part of query" [toolbarVc]="part"></div>)',
template: 'TOOLBAR(<div *ngFor="let part of query" [toolbarVc]="part"></div>)',
directives: [ToolbarViewContainer, NgFor]
})
@Injectable()
@ -2489,7 +2506,7 @@ class DirectiveThrowingAnError {
@Component({
selector: 'component-with-template',
directives: [NgFor],
template: `No View Decorator: <div *ngFor="#item of items">{{item}}</div>`
template: `No View Decorator: <div *ngFor="let item of items">{{item}}</div>`
})
class ComponentWithTemplate {
items = [1, 2, 3];

View File

@ -246,7 +246,7 @@ export function main() {
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template =
'<div text="1"></div>' +
'<needs-query text="2"><div *ngFor="var i of list" [text]="i"></div></needs-query>' +
'<needs-query text="2"><div *ngFor="let i of list" [text]="i"></div></needs-query>' +
'<div text="4"></div>';
tcb.overrideTemplate(MyComp, template)
@ -268,7 +268,7 @@ export function main() {
describe('query for TemplateRef', () => {
it('should find TemplateRefs in the light and shadow dom',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = '<needs-tpl><template var-x="light"></template></needs-tpl>';
var template = '<needs-tpl><template let-x="light"></template></needs-tpl>';
tcb.overrideTemplate(MyComp, template)
.createAsync(MyComp)
.then((view) => {
@ -284,6 +284,23 @@ export function main() {
});
}));
it('should find named TemplateRefs',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template =
'<needs-named-tpl><template let-x="light" #tpl></template></needs-named-tpl>';
tcb.overrideTemplate(MyComp, template)
.createAsync(MyComp)
.then((view) => {
view.detectChanges();
var needsTpl: NeedsNamedTpl = view.debugElement.children[0].inject(NeedsNamedTpl);
expect(needsTpl.vc.createEmbeddedView(needsTpl.contentTpl).hasLocal('light'))
.toBe(true);
expect(needsTpl.vc.createEmbeddedView(needsTpl.viewTpl).hasLocal('shadow'))
.toBe(true);
async.done();
});
}));
});
describe('read a different token', () => {
@ -462,9 +479,10 @@ export function main() {
describe("querying by var binding", () => {
it('should contain all the child directives in the light dom with the given var binding',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = '<needs-query-by-var-binding #q>' +
'<div *ngFor="#item of list" [text]="item" #textLabel="textDir"></div>' +
'</needs-query-by-var-binding>';
var template =
'<needs-query-by-ref-binding #q>' +
'<div *ngFor="let item of list" [text]="item" #textLabel="textDir"></div>' +
'</needs-query-by-ref-binding>';
tcb.overrideTemplate(MyComp, template)
.createAsync(MyComp)
@ -484,10 +502,10 @@ export function main() {
it('should support querying by multiple var bindings',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = '<needs-query-by-var-bindings #q>' +
var template = '<needs-query-by-ref-bindings #q>' +
'<div text="one" #textLabel1="textDir"></div>' +
'<div text="two" #textLabel2="textDir"></div>' +
'</needs-query-by-var-bindings>';
'</needs-query-by-ref-bindings>';
tcb.overrideTemplate(MyComp, template)
.createAsync(MyComp)
@ -504,9 +522,10 @@ export function main() {
it('should support dynamically inserted directives',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = '<needs-query-by-var-binding #q>' +
'<div *ngFor="#item of list" [text]="item" #textLabel="textDir"></div>' +
'</needs-query-by-var-binding>';
var template =
'<needs-query-by-ref-binding #q>' +
'<div *ngFor="let item of list" [text]="item" #textLabel="textDir"></div>' +
'</needs-query-by-ref-binding>';
tcb.overrideTemplate(MyComp, template)
.createAsync(MyComp)
@ -529,11 +548,11 @@ export function main() {
it('should contain all the elements in the light dom with the given var binding',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = '<needs-query-by-var-binding #q>' +
'<div template="ngFor: #item of list">' +
var template = '<needs-query-by-ref-binding #q>' +
'<div template="ngFor: let item of list">' +
'<div #textLabel>{{item}}</div>' +
'</div>' +
'</needs-query-by-var-binding>';
'</needs-query-by-ref-binding>';
tcb.overrideTemplate(MyComp, template)
.createAsync(MyComp)
@ -570,7 +589,7 @@ export function main() {
it('should support querying the view by using a view query',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = '<needs-view-query-by-var-binding #q></needs-view-query-by-var-binding>';
var template = '<needs-view-query-by-ref-binding #q></needs-view-query-by-ref-binding>';
tcb.overrideTemplate(MyComp, template)
.createAsync(MyComp)
@ -859,7 +878,7 @@ class InertDirective {
@Component({
selector: 'needs-query',
directives: [NgFor, TextDirective],
template: '<div text="ignoreme"></div><b *ngFor="var dir of query">{{dir.text}}|</b>'
template: '<div text="ignoreme"></div><b *ngFor="let dir of query">{{dir.text}}|</b>'
})
@Injectable()
class NeedsQuery {
@ -878,7 +897,7 @@ class NeedsFourQueries {
@Component({
selector: 'needs-query-desc',
directives: [NgFor],
template: '<div *ngFor="var dir of query">{{dir.text}}|</div>'
template: '<div *ngFor="let dir of query">{{dir.text}}|</div>'
})
@Injectable()
class NeedsQueryDesc {
@ -888,7 +907,7 @@ class NeedsQueryDesc {
}
}
@Component({selector: 'needs-query-by-var-binding', directives: [], template: '<ng-content>'})
@Component({selector: 'needs-query-by-ref-binding', directives: [], template: '<ng-content>'})
@Injectable()
class NeedsQueryByLabel {
query: QueryList<any>;
@ -898,7 +917,7 @@ class NeedsQueryByLabel {
}
@Component({
selector: 'needs-view-query-by-var-binding',
selector: 'needs-view-query-by-ref-binding',
directives: [],
template: '<div #textLabel>text</div>'
})
@ -908,7 +927,7 @@ class NeedsViewQueryByLabel {
constructor(@ViewQuery("textLabel") query: QueryList<any>) { this.query = query; }
}
@Component({selector: 'needs-query-by-var-bindings', directives: [], template: '<ng-content>'})
@Component({selector: 'needs-query-by-ref-bindings', directives: [], template: '<ng-content>'})
@Injectable()
class NeedsQueryByTwoLabels {
query: QueryList<any>;
@ -920,7 +939,7 @@ class NeedsQueryByTwoLabels {
@Component({
selector: 'needs-query-and-project',
directives: [NgFor],
template: '<div *ngFor="var dir of query">{{dir.text}}|</div><ng-content></ng-content>'
template: '<div *ngFor="let dir of query">{{dir.text}}|</div><ng-content></ng-content>'
})
@Injectable()
class NeedsQueryAndProject {
@ -975,7 +994,7 @@ class NeedsViewQueryNestedIf {
selector: 'needs-view-query-order',
directives: [NgFor, TextDirective, InertDirective],
template: '<div text="1"></div>' +
'<div *ngFor="var i of list" [text]="i"></div>' +
'<div *ngFor="let i of list" [text]="i"></div>' +
'<div text="4"></div>'
})
@Injectable()
@ -992,7 +1011,7 @@ class NeedsViewQueryOrder {
selector: 'needs-view-query-order-with-p',
directives: [NgFor, TextDirective, InertDirective],
template: '<div dir><div text="1"></div>' +
'<div *ngFor="var i of list" [text]="i"></div>' +
'<div *ngFor="let i of list" [text]="i"></div>' +
'<div text="4"></div></div>'
})
@Injectable()
@ -1005,7 +1024,7 @@ class NeedsViewQueryOrderWithParent {
}
}
@Component({selector: 'needs-tpl', template: '<template var-x="shadow"></template>'})
@Component({selector: 'needs-tpl', template: '<template let-x="shadow"></template>'})
class NeedsTpl {
viewQuery: QueryList<TemplateRef>;
query: QueryList<TemplateRef>;
@ -1016,6 +1035,13 @@ class NeedsTpl {
}
}
@Component({selector: 'needs-named-tpl', template: '<template #tpl let-x="shadow"></template>'})
class NeedsNamedTpl {
@ViewChild('tpl') viewTpl: TemplateRef;
@ContentChild('tpl') contentTpl: TemplateRef;
constructor(public vc: ViewContainerRef) {}
}
@Component({selector: 'needs-content-children-read', template: ''})
class NeedsContentChildrenWithRead {
@ContentChildren('q', {read: TextDirective}) textDirChildren: QueryList<TextDirective>;
@ -1076,6 +1102,7 @@ class NeedsViewContainerWithRead {
NeedsViewChild,
NeedsContentChild,
NeedsTpl,
NeedsNamedTpl,
TextDirective,
InertDirective,
NgIf,
@ -1097,4 +1124,4 @@ class MyComp {
this.shouldShow = false;
this.list = ['1d', '2d', '3d'];
}
}
}