feat(compiler): introduce <ng-template>, deprecate <template> and template attribute

The rationale of this change is to improve the inter-operability with web
components that might make use of the `<template>` tag.

DEPRECATION

The template tags and template attribute are deprecated:

    <template ngFor [ngFor]=items let-item><li>...</li></template>
    <li template="ngFor: let item of items">...</li>

should be rewritten as:

    <ng-template ngFor [ngFor]=items let-item><li>...</li></ng-template>

Note that they still be supported in 4.x with a deprecartion warning in
development mode.

MIGRATION

- `template` tags (or elements with a `template` attribute) should be rewritten
as a `ng-template` tag,
- `ng-content` selectors should be updated to referto a `ng-template` where they
use to refer to a template: `<ng-content selector="template[attr]">` should be
rewritten as `<ng-content selector="ng-template[attr]">`
- if you consume a component relying on your templates being actual `template`
elements (that is they include a `<ng-content selector="template[attr]">`). You
should  still migrate to `ng-template` and make use of `ngProjectAs` to override
the way `ng-content` sees the template:
`<ng-template projectAs="template[attr]">`
- while `template` elements are deprecated in 4.x they continue to work.
This commit is contained in:
Victor Berchet
2017-01-09 13:16:46 -08:00
committed by Igor Minar
parent 3f519207a4
commit bf8eb41248
31 changed files with 312 additions and 184 deletions

View File

@ -70,12 +70,12 @@ export class NgForOfRow<T> {
* - `<li *ngFor="let item of items; let i = index; trackBy: trackByFn">...</li>`
* - `<li template="ngFor let item of items; let i = index; trackBy: trackByFn">...</li>`
*
* With `<template>` element:
* With `<ng-template>` element:
*
* ```
* <template ngFor let-item [ngForOf]="items" let-i="index" [ngForTrackBy]="trackByFn">
* <ng-template ngFor let-item [ngForOf]="items" let-i="index" [ngForTrackBy]="trackByFn">
* <li>...</li>
* </template>
* </ng-template>
* ```
*
* ### Example

View File

@ -26,7 +26,7 @@ import {Directive, EmbeddedViewRef, Input, TemplateRef, ViewContainerRef} from '
* # Showing an alternative template using `else`
*
* If it is necessary to display a template when the `expression` is falsy use the `else` template
* binding as shown. Note that the `else` binding points to a `<template>` labeled `#elseBlock`.
* binding as shown. Note that the `else` binding points to a `<ng-template>` labeled `#elseBlock`.
* The template can be defined anywhere in the component view but is typically placed right after
* `ngIf` for readability.
*
@ -76,25 +76,25 @@ import {Directive, EmbeddedViewRef, Input, TemplateRef, ViewContainerRef} from '
* Simple form:
* - `<div *ngIf="condition">...</div>`
* - `<div template="ngIf condition">...</div>`
* - `<template [ngIf]="condition"><div>...</div></template>`
* - `<ng-template [ngIf]="condition"><div>...</div></ng-template>`
*
* Form with an else block:
* ```
* <div *ngIf="condition; else elseBlock">...</div>
* <template #elseBlock>...</template>
* <ng-template #elseBlock>...</ng-template>
* ```
*
* Form with a `then` and `else` block:
* ```
* <div *ngIf="condition; then thenBlock else elseBlock"></div>
* <template #thenBlock>...</template>
* <template #elseBlock>...</template>
* <ng-template #thenBlock>...</ng-template>
* <ng-template #elseBlock>...</ng-template>
* ```
*
* Form with storing the value locally:
* ```
* <div *ngIf="condition; else elseBlock; let value">{{value}}</div>
* <template #elseBlock>...</template>
* <ng-template #elseBlock>...</ng-template>
* ```
*
* @stable

View File

@ -18,7 +18,7 @@ export function main() {
beforeEach(() => { TestBed.configureTestingModule({imports: [TestModule]}); });
it('should do nothing if component is null', async(() => {
const template = `<template *ngComponentOutlet="currentComponent"></template>`;
const template = `<ng-template *ngComponentOutlet="currentComponent"></ng-template>`;
TestBed.overrideComponent(TestComponent, {set: {template: template}});
let fixture = TestBed.createComponent(TestComponent);
@ -120,7 +120,7 @@ export function main() {
}));
it('should render projectable nodes, if supplied', async(() => {
const template = `<template>projected foo</template>${TEST_CMP_TEMPLATE}`;
const template = `<ng-template>projected foo</ng-template>${TEST_CMP_TEMPLATE}`;
TestBed.overrideComponent(TestComponent, {set: {template: template}})
.configureTestingModule({schemas: [NO_ERRORS_SCHEMA]});
@ -221,7 +221,7 @@ class InjectedComponentAgain {
}
const TEST_CMP_TEMPLATE =
`<template *ngComponentOutlet="currentComponent; injector: injector; content: projectables; ngModuleFactory: module;"></template>`;
`<ng-template *ngComponentOutlet="currentComponent; injector: injector; content: projectables; ngModuleFactory: module;"></ng-template>`;
@Component({selector: 'test-cmp', template: TEST_CMP_TEMPLATE})
class TestComponent {
currentComponent: Type<any>;

View File

@ -243,7 +243,7 @@ export function main() {
it('should allow to use a custom template', async(() => {
const template =
'<ng-container *ngFor="let item of items; template: tpl"></ng-container>' +
'<template let-item let-i="index" #tpl><p>{{i}}: {{item}};</p></template>';
'<ng-template let-item let-i="index" #tpl><p>{{i}}: {{item}};</p></ng-template>';
fixture = createTestComponent(template);
getComponent().items = ['a', 'b', 'c'];
fixture.detectChanges();
@ -262,7 +262,7 @@ export function main() {
it('should use a custom template when both default and a custom one are present', async(() => {
const template =
'<ng-container *ngFor="let item of items; template: tpl">{{i}};</ng-container>' +
'<template let-item let-i="index" #tpl>{{i}}: {{item}};</template>';
'<ng-template let-item let-i="index" #tpl>{{i}}: {{item}};</ng-template>';
fixture = createTestComponent(template);
getComponent().items = ['a', 'b', 'c'];
fixture.detectChanges();

View File

@ -37,7 +37,7 @@ export function main() {
}));
it('should work on a template element', async(() => {
const template = '<template [ngIf]="booleanCondition">hello2</template>';
const template = '<ng-template [ngIf]="booleanCondition">hello2</ng-template>';
fixture = createTestComponent(template);
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('hello2');
@ -141,7 +141,7 @@ export function main() {
describe('else', () => {
it('should support else', async(() => {
const template = '<span *ngIf="booleanCondition; else elseBlock">TRUE</span>' +
'<template #elseBlock>FALSE</template>';
'<ng-template #elseBlock>FALSE</ng-template>';
fixture = createTestComponent(template);
@ -156,8 +156,8 @@ export function main() {
it('should support then and else', async(() => {
const template =
'<span *ngIf="booleanCondition; then thenBlock; else elseBlock">IGNORE</span>' +
'<template #thenBlock>THEN</template>' +
'<template #elseBlock>ELSE</template>';
'<ng-template #thenBlock>THEN</ng-template>' +
'<ng-template #elseBlock>ELSE</ng-template>';
fixture = createTestComponent(template);
@ -172,8 +172,8 @@ export function main() {
it('should support dynamic else', async(() => {
const template =
'<span *ngIf="booleanCondition; else nestedBooleanCondition ? b1 : b2">TRUE</span>' +
'<template #b1>FALSE1</template>' +
'<template #b2>FALSE2</template>';
'<ng-template #b1>FALSE1</ng-template>' +
'<ng-template #b2>FALSE2</ng-template>';
fixture = createTestComponent(template);
@ -191,7 +191,7 @@ export function main() {
it('should support binding to variable', async(() => {
const template = '<span *ngIf="booleanCondition; else elseBlock; let v">{{v}}</span>' +
'<template #elseBlock let-v>{{v}}</template>';
'<ng-template #elseBlock let-v>{{v}}</ng-template>';
fixture = createTestComponent(template);

View File

@ -34,8 +34,8 @@ export function main() {
it('should display the template according to the exact value', async(() => {
const template = '<ul [ngPlural]="switchValue">' +
'<template ngPluralCase="=0"><li>you have no messages.</li></template>' +
'<template ngPluralCase="=1"><li>you have one message.</li></template>' +
'<ng-template ngPluralCase="=0"><li>you have no messages.</li></ng-template>' +
'<ng-template ngPluralCase="=1"><li>you have one message.</li></ng-template>' +
'</ul>';
fixture = createTestComponent(template);
@ -67,7 +67,7 @@ export function main() {
// https://github.com/angular/angular/issues/9882
it('should not throw when ngPluralCase contains expressions', async(() => {
const template = '<ul [ngPlural]="switchValue">' +
'<template ngPluralCase="=0"><li>{{ switchValue }}</li></template>' +
'<ng-template ngPluralCase="=0"><li>{{ switchValue }}</li></ng-template>' +
'</ul>';
fixture = createTestComponent(template);
@ -79,8 +79,8 @@ export function main() {
it('should be applicable to <ng-container> elements', async(() => {
const template = '<ng-container [ngPlural]="switchValue">' +
'<template ngPluralCase="=0">you have no messages.</template>' +
'<template ngPluralCase="=1">you have one message.</template>' +
'<ng-template ngPluralCase="=0">you have no messages.</ng-template>' +
'<ng-template ngPluralCase="=1">you have one message.</ng-template>' +
'</ng-container>';
fixture = createTestComponent(template);
@ -94,8 +94,8 @@ export function main() {
it('should display the template according to the category', async(() => {
const template = '<ul [ngPlural]="switchValue">' +
'<template ngPluralCase="few"><li>you have a few messages.</li></template>' +
'<template ngPluralCase="many"><li>you have many messages.</li></template>' +
'<ng-template ngPluralCase="few"><li>you have a few messages.</li></ng-template>' +
'<ng-template ngPluralCase="many"><li>you have many messages.</li></ng-template>' +
'</ul>';
fixture = createTestComponent(template);
@ -109,8 +109,8 @@ export function main() {
it('should default to other when no matches are found', async(() => {
const template = '<ul [ngPlural]="switchValue">' +
'<template ngPluralCase="few"><li>you have a few messages.</li></template>' +
'<template ngPluralCase="other"><li>default message.</li></template>' +
'<ng-template ngPluralCase="few"><li>you have a few messages.</li></ng-template>' +
'<ng-template ngPluralCase="other"><li>default message.</li></ng-template>' +
'</ul>';
fixture = createTestComponent(template);
@ -121,8 +121,8 @@ export function main() {
it('should prioritize value matches over category matches', async(() => {
const template = '<ul [ngPlural]="switchValue">' +
'<template ngPluralCase="few"><li>you have a few messages.</li></template>' +
'<template ngPluralCase="=2">you have two messages.</template>' +
'<ng-template ngPluralCase="few"><li>you have a few messages.</li></ng-template>' +
'<ng-template ngPluralCase="=2">you have two messages.</ng-template>' +
'</ul>';
fixture = createTestComponent(template);

View File

@ -41,14 +41,14 @@ export function main() {
}));
it('should insert content specified by TemplateRef', async(() => {
const template = `<template #tpl>foo</template>` +
const template = `<ng-template #tpl>foo</ng-template>` +
`<ng-container [ngTemplateOutlet]="tpl"></ng-container>`;
fixture = createTestComponent(template);
detectChangesAndExpectText('foo');
}));
it('should clear content if TemplateRef becomes `null`', async(() => {
const template = `<tpl-refs #refs="tplRefs"><template>foo</template></tpl-refs>` +
const template = `<tpl-refs #refs="tplRefs"><ng-template>foo</ng-template></tpl-refs>` +
`<ng-container [ngTemplateOutlet]="currentTplRef"></ng-container>`;
fixture = createTestComponent(template);
fixture.detectChanges();
@ -63,7 +63,7 @@ export function main() {
it('should swap content if TemplateRef changes', async(() => {
const template =
`<tpl-refs #refs="tplRefs"><template>foo</template><template>bar</template></tpl-refs>` +
`<tpl-refs #refs="tplRefs"><ng-template>foo</ng-template><ng-template>bar</ng-template></tpl-refs>` +
`<ng-container [ngTemplateOutlet]="currentTplRef"></ng-container>`;
fixture = createTestComponent(template);
@ -78,14 +78,14 @@ export function main() {
}));
it('should display template if context is `null`', async(() => {
const template = `<template #tpl>foo</template>` +
const template = `<ng-template #tpl>foo</ng-template>` +
`<ng-container *ngTemplateOutlet="tpl; context: null"></ng-container>`;
fixture = createTestComponent(template);
detectChangesAndExpectText('foo');
}));
it('should reflect initial context and changes', async(() => {
const template = `<template let-foo="foo" #tpl>{{foo}}</template>` +
const template = `<ng-template let-foo="foo" #tpl>{{foo}}</ng-template>` +
`<ng-container *ngTemplateOutlet="tpl; context: context"></ng-container>`;
fixture = createTestComponent(template);
@ -97,7 +97,7 @@ export function main() {
}));
it('should reflect user defined `$implicit` property in the context', async(() => {
const template = `<template let-ctx #tpl>{{ctx.foo}}</template>` +
const template = `<ng-template let-ctx #tpl>{{ctx.foo}}</ng-template>` +
`<ng-container *ngTemplateOutlet="tpl; context: context"></ng-container>`;
fixture = createTestComponent(template);
fixture.componentInstance.context = {$implicit: {foo: 'bra'}};
@ -105,7 +105,8 @@ export function main() {
}));
it('should reflect context re-binding', async(() => {
const template = `<template let-shawshank="shawshank" #tpl>{{shawshank}}</template>` +
const template =
`<ng-template let-shawshank="shawshank" #tpl>{{shawshank}}</ng-template>` +
`<ng-container *ngTemplateOutlet="tpl; context: context"></ng-container>`;
fixture = createTestComponent(template);