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:
@ -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]);
|
||||
}));
|
||||
|
@ -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;
|
||||
|
@ -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];
|
||||
|
@ -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'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user