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

@ -20,9 +20,9 @@ Turns the li element and its contents into a template, and uses that to instanti
@cheatsheetItem @cheatsheetItem
syntax: syntax:
`<div [ngSwitch]="conditionExpression"> `<div [ngSwitch]="conditionExpression">
<template [ngSwitchCase]="case1Exp">...</template> <ng-template [ngSwitchCase]="case1Exp">...</ng-template>
<template ngSwitchCase="case2LiteralString">...</template> <ng-template ngSwitchCase="case2LiteralString">...</ng-template>
<template ngSwitchDefault>...</template> <ng-template ngSwitchDefault>...</ng-template>
</div>`|`[ngSwitch]`|`[ngSwitchCase]`|`ngSwitchCase`|`ngSwitchDefault` </div>`|`[ngSwitch]`|`[ngSwitchCase]`|`ngSwitchCase`|`ngSwitchDefault`
description: description:
Conditionally swaps the contents of the div by selecting one of the embedded templates based on the current value of `conditionExpression`. Conditionally swaps the contents of the div by selecting one of the embedded templates based on the current value of `conditionExpression`.

View File

@ -65,7 +65,7 @@ syntax:
`<p *myUnless="myExpression">...</p>`|`*myUnless` `<p *myUnless="myExpression">...</p>`|`*myUnless`
description: description:
The `*` symbol turns the current element into an embedded template. Equivalent to: The `*` symbol turns the current element into an embedded template. Equivalent to:
`<template [myUnless]="myExpression"><p>...</p></template>` `<ng-template [myUnless]="myExpression"><p>...</p></ng-template>`
@cheatsheetItem @cheatsheetItem
syntax: syntax:

View File

@ -70,12 +70,12 @@ export class NgForOfRow<T> {
* - `<li *ngFor="let item of items; let i = index; trackBy: trackByFn">...</li>` * - `<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>` * - `<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> * <li>...</li>
* </template> * </ng-template>
* ``` * ```
* *
* ### Example * ### Example

View File

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

View File

@ -18,7 +18,7 @@ export function main() {
beforeEach(() => { TestBed.configureTestingModule({imports: [TestModule]}); }); beforeEach(() => { TestBed.configureTestingModule({imports: [TestModule]}); });
it('should do nothing if component is null', async(() => { 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}}); TestBed.overrideComponent(TestComponent, {set: {template: template}});
let fixture = TestBed.createComponent(TestComponent); let fixture = TestBed.createComponent(TestComponent);
@ -120,7 +120,7 @@ export function main() {
})); }));
it('should render projectable nodes, if supplied', async(() => { 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}}) TestBed.overrideComponent(TestComponent, {set: {template: template}})
.configureTestingModule({schemas: [NO_ERRORS_SCHEMA]}); .configureTestingModule({schemas: [NO_ERRORS_SCHEMA]});
@ -221,7 +221,7 @@ class InjectedComponentAgain {
} }
const TEST_CMP_TEMPLATE = 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}) @Component({selector: 'test-cmp', template: TEST_CMP_TEMPLATE})
class TestComponent { class TestComponent {
currentComponent: Type<any>; currentComponent: Type<any>;

View File

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

View File

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

View File

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

View File

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

View File

@ -109,12 +109,15 @@ export const COMPILER_PROVIDERS: Array<any|Type<any>|{[k: string]: any}|any[]> =
export class JitCompilerFactory implements CompilerFactory { export class JitCompilerFactory implements CompilerFactory {
private _defaultOptions: CompilerOptions[]; private _defaultOptions: CompilerOptions[];
constructor(@Inject(COMPILER_OPTIONS) defaultOptions: CompilerOptions[]) { constructor(@Inject(COMPILER_OPTIONS) defaultOptions: CompilerOptions[]) {
this._defaultOptions = [<CompilerOptions>{ const compilerOptions: CompilerOptions = {
useDebug: isDevMode(), useDebug: isDevMode(),
useJit: true, useJit: true,
defaultEncapsulation: ViewEncapsulation.Emulated, defaultEncapsulation: ViewEncapsulation.Emulated,
missingTranslation: MissingTranslationStrategy.Warning, missingTranslation: MissingTranslationStrategy.Warning,
}].concat(defaultOptions); enableLegacyTemplate: true,
};
this._defaultOptions = [compilerOptions, ...defaultOptions];
} }
createCompiler(options: CompilerOptions[] = []): Compiler { createCompiler(options: CompilerOptions[] = []): Compiler {
const opts = _mergeOptions(this._defaultOptions.concat(options)); const opts = _mergeOptions(this._defaultOptions.concat(options));

View File

@ -58,7 +58,8 @@ export class HtmlTagDefinition implements TagDefinition {
} }
const lcParent = currentParent.toLowerCase(); const lcParent = currentParent.toLowerCase();
return this.requiredParents[lcParent] != true && lcParent != 'template'; const isParentTemplate = lcParent === 'template' || currentParent === 'ng-template';
return !isParentTemplate && this.requiredParents[lcParent] != true;
} }
isClosedByChild(name: string): boolean { isClosedByChild(name: string): boolean {

View File

@ -30,9 +30,9 @@ const PLURAL_CASES: string[] = ['zero', 'one', 'two', 'few', 'many', 'other'];
* *
* ``` * ```
* <ng-container [ngPlural]="messages.length"> * <ng-container [ngPlural]="messages.length">
* <template ngPluralCase="=0">zero</template> * <ng-template ngPluralCase="=0">zero</ng-template>
* <template ngPluralCase="=1">one</template> * <ng-template ngPluralCase="=1">one</ng-template>
* <template ngPluralCase="other">more than one</template> * <ng-template ngPluralCase="other">more than one</ng-template>
* </ng-container> * </ng-container>
* ``` * ```
*/ */
@ -81,6 +81,7 @@ class _Expander implements html.Visitor {
} }
} }
// Plural forms are expanded to `NgPlural` and `NgPluralCase`s
function _expandPluralForm(ast: html.Expansion, errors: ParseError[]): html.Element { function _expandPluralForm(ast: html.Expansion, errors: ParseError[]): html.Element {
const children = ast.cases.map(c => { const children = ast.cases.map(c => {
if (PLURAL_CASES.indexOf(c.value) == -1 && !c.value.match(/^=\d+$/)) { if (PLURAL_CASES.indexOf(c.value) == -1 && !c.value.match(/^=\d+$/)) {
@ -93,7 +94,7 @@ function _expandPluralForm(ast: html.Expansion, errors: ParseError[]): html.Elem
errors.push(...expansionResult.errors); errors.push(...expansionResult.errors);
return new html.Element( return new html.Element(
`template`, [new html.Attribute('ngPluralCase', `${c.value}`, c.valueSourceSpan)], `ng-template`, [new html.Attribute('ngPluralCase', `${c.value}`, c.valueSourceSpan)],
expansionResult.nodes, c.sourceSpan, c.sourceSpan, c.sourceSpan); expansionResult.nodes, c.sourceSpan, c.sourceSpan, c.sourceSpan);
}); });
const switchAttr = new html.Attribute('[ngPlural]', ast.switchValue, ast.switchValueSourceSpan); const switchAttr = new html.Attribute('[ngPlural]', ast.switchValue, ast.switchValueSourceSpan);
@ -101,6 +102,7 @@ function _expandPluralForm(ast: html.Expansion, errors: ParseError[]): html.Elem
'ng-container', [switchAttr], children, ast.sourceSpan, ast.sourceSpan, ast.sourceSpan); 'ng-container', [switchAttr], children, ast.sourceSpan, ast.sourceSpan, ast.sourceSpan);
} }
// ICU messages (excluding plural form) are expanded to `NgSwitch` and `NgSwitychCase`s
function _expandDefaultForm(ast: html.Expansion, errors: ParseError[]): html.Element { function _expandDefaultForm(ast: html.Expansion, errors: ParseError[]): html.Element {
const children = ast.cases.map(c => { const children = ast.cases.map(c => {
const expansionResult = expandNodes(c.expression); const expansionResult = expandNodes(c.expression);
@ -109,12 +111,12 @@ function _expandDefaultForm(ast: html.Expansion, errors: ParseError[]): html.Ele
if (c.value === 'other') { if (c.value === 'other') {
// other is the default case when no values match // other is the default case when no values match
return new html.Element( return new html.Element(
`template`, [new html.Attribute('ngSwitchDefault', '', c.valueSourceSpan)], `ng-template`, [new html.Attribute('ngSwitchDefault', '', c.valueSourceSpan)],
expansionResult.nodes, c.sourceSpan, c.sourceSpan, c.sourceSpan); expansionResult.nodes, c.sourceSpan, c.sourceSpan, c.sourceSpan);
} }
return new html.Element( return new html.Element(
`template`, [new html.Attribute('ngSwitchCase', `${c.value}`, c.valueSourceSpan)], `ng-template`, [new html.Attribute('ngSwitchCase', `${c.value}`, c.valueSourceSpan)],
expansionResult.nodes, c.sourceSpan, c.sourceSpan, c.sourceSpan); expansionResult.nodes, c.sourceSpan, c.sourceSpan, c.sourceSpan);
}); });
const switchAttr = new html.Attribute('[ngSwitch]', ast.switchValue, ast.switchValueSourceSpan); const switchAttr = new html.Attribute('[ngSwitch]', ast.switchValue, ast.switchValueSourceSpan);

View File

@ -108,7 +108,7 @@ export class ReferenceAst implements TemplateAst {
} }
/** /**
* A variable declaration on a <template> (e.g. `var-someName="someLocalName"`). * A variable declaration on a <ng-template> (e.g. `var-someName="someLocalName"`).
*/ */
export class VariableAst implements TemplateAst { export class VariableAst implements TemplateAst {
constructor(public name: string, public value: string, public sourceSpan: ParseSourceSpan) {} constructor(public name: string, public value: string, public sourceSpan: ParseSourceSpan) {}
@ -135,7 +135,7 @@ export class ElementAst implements TemplateAst {
} }
/** /**
* A `<template>` element included in an Angular template. * A `<ng-template>` element included in an Angular template.
*/ */
export class EmbeddedTemplateAst implements TemplateAst { export class EmbeddedTemplateAst implements TemplateAst {
constructor( constructor(

View File

@ -54,7 +54,10 @@ const IDENT_PROPERTY_IDX = 9;
// Group 10 = identifier inside () // Group 10 = identifier inside ()
const IDENT_EVENT_IDX = 10; const IDENT_EVENT_IDX = 10;
const NG_TEMPLATE_ELEMENT = 'ng-template';
// deprecated in 4.x
const TEMPLATE_ELEMENT = 'template'; const TEMPLATE_ELEMENT = 'template';
// deprecated in 4.x
const TEMPLATE_ATTR = 'template'; const TEMPLATE_ATTR = 'template';
const TEMPLATE_ATTR_PREFIX = '*'; const TEMPLATE_ATTR_PREFIX = '*';
const CLASS_ATTR = 'class'; const CLASS_ATTR = 'class';
@ -269,8 +272,9 @@ class TemplateParseVisitor implements html.Visitor {
let hasInlineTemplates = false; let hasInlineTemplates = false;
const attrs: AttrAst[] = []; const attrs: AttrAst[] = [];
const lcElName = splitNsName(nodeName.toLowerCase())[1]; const isTemplateElement = isTemplate(
const isTemplateElement = lcElName == TEMPLATE_ELEMENT; element,
(m: string, span: ParseSourceSpan) => this._reportError(m, span, ParseErrorLevel.WARNING));
element.attrs.forEach(attr => { element.attrs.forEach(attr => {
const hasBinding = this._parseAttr( const hasBinding = this._parseAttr(
@ -282,6 +286,9 @@ class TemplateParseVisitor implements html.Visitor {
let normalizedName = this._normalizeAttributeName(attr.name); let normalizedName = this._normalizeAttributeName(attr.name);
if (normalizedName == TEMPLATE_ATTR) { if (normalizedName == TEMPLATE_ATTR) {
this._reportError(
`The template attribute is deprecated. Use an ng-template element instead.`,
attr.sourceSpan, ParseErrorLevel.WARNING);
templateBindingsSource = attr.value; templateBindingsSource = attr.value;
} else if (normalizedName.startsWith(TEMPLATE_ATTR_PREFIX)) { } else if (normalizedName.startsWith(TEMPLATE_ATTR_PREFIX)) {
templateBindingsSource = attr.value; templateBindingsSource = attr.value;
@ -379,10 +386,9 @@ class TemplateParseVisitor implements html.Visitor {
if (hasInlineTemplates) { if (hasInlineTemplates) {
const templateQueryStartIndex = this.contentQueryStartId; const templateQueryStartIndex = this.contentQueryStartId;
const templateCssSelector = const templateSelector = createElementCssSelector(TEMPLATE_ELEMENT, templateMatchableAttrs);
createElementCssSelector(TEMPLATE_ELEMENT, templateMatchableAttrs);
const {directives: templateDirectiveMetas} = const {directives: templateDirectiveMetas} =
this._parseDirectives(this.selectorMatcher, templateCssSelector); this._parseDirectives(this.selectorMatcher, templateSelector);
const templateBoundDirectivePropNames = new Set<string>(); const templateBoundDirectivePropNames = new Set<string>();
const templateDirectiveAsts = this._createDirectiveAsts( const templateDirectiveAsts = this._createDirectiveAsts(
true, element.name, templateDirectiveMetas, templateElementOrDirectiveProps, [], true, element.name, templateDirectiveMetas, templateElementOrDirectiveProps, [],
@ -896,3 +902,19 @@ function isEmptyExpression(ast: AST): boolean {
} }
return ast instanceof EmptyExpr; return ast instanceof EmptyExpr;
} }
// `template` is deprecated in 4.x
function isTemplate(
el: html.Element, reportDeprecation: (m: string, span: ParseSourceSpan) => void): boolean {
const tagNoNs = splitNsName(el.name)[1];
// `<ng-template>` is an angular construct and is lower case
if (tagNoNs === NG_TEMPLATE_ELEMENT) return true;
// `<template>` is HTML and case insensitive
if (tagNoNs.toLowerCase() === TEMPLATE_ELEMENT) {
reportDeprecation(
`The <template> element is deprecated. Use <ng-template> instead`, el.sourceSpan);
return true;
}
return false;
}

View File

@ -14,7 +14,7 @@ import {CompileQuery} from './compile_query';
// Note: We can't do this when we create the CompileElements already, // Note: We can't do this when we create the CompileElements already,
// as we create embedded views before the <template> elements themselves. // as we create embedded views before the <ng-template> elements themselves.
export function bindQueryValues(ce: CompileElement) { export function bindQueryValues(ce: CompileElement) {
const queriesWithReads: _QueryWithRead[] = []; const queriesWithReads: _QueryWithRead[] = [];

View File

@ -31,10 +31,14 @@ export function main() {
]); ]);
}); });
it('should parse text nodes inside template elements', () => { it('should parse text nodes inside <ng-template> elements', () => {
// deprecated in 4.0
expect(humanizeDom(parser.parse('<template>a</template>', 'TestComp'))).toEqual([ expect(humanizeDom(parser.parse('<template>a</template>', 'TestComp'))).toEqual([
[html.Element, 'template', 0], [html.Text, 'a', 1] [html.Element, 'template', 0], [html.Text, 'a', 1]
]); ]);
expect(humanizeDom(parser.parse('<ng-template>a</ng-template>', 'TestComp'))).toEqual([
[html.Element, 'ng-template', 0], [html.Text, 'a', 1]
]);
}); });
it('should parse CDATA', () => { it('should parse CDATA', () => {
@ -57,9 +61,11 @@ export function main() {
]); ]);
}); });
it('should parse elements inside of template elements', () => { it('should parse elements inside <ng-template> elements', () => {
expect(humanizeDom(parser.parse('<template><span></span></template>', 'TestComp'))) expect(humanizeDom(parser.parse('<template><span></span></template>', 'TestComp')))
.toEqual([[html.Element, 'template', 0], [html.Element, 'span', 1]]); .toEqual([[html.Element, 'template', 0], [html.Element, 'span', 1]]);
expect(humanizeDom(parser.parse('<ng-template><span></span></ng-template>', 'TestComp')))
.toEqual([[html.Element, 'ng-template', 0], [html.Element, 'span', 1]]);
}); });
it('should support void elements', () => { it('should support void elements', () => {
@ -158,11 +164,16 @@ export function main() {
]); ]);
}); });
it('should not add the requiredParent when the parent is a template', () => { it('should not add the requiredParent when the parent is a <ng-template>', () => {
expect(humanizeDom(parser.parse('<template><tr></tr></template>', 'TestComp'))).toEqual([ expect(humanizeDom(parser.parse('<template><tr></tr></template>', 'TestComp'))).toEqual([
[html.Element, 'template', 0], [html.Element, 'template', 0],
[html.Element, 'tr', 1], [html.Element, 'tr', 1],
]); ]);
expect(humanizeDom(parser.parse('<ng-template><tr></tr></ng-template>', 'TestComp')))
.toEqual([
[html.Element, 'ng-template', 0],
[html.Element, 'tr', 1],
]);
}); });
// https://github.com/angular/angular/issues/5967 // https://github.com/angular/angular/issues/5967
@ -252,11 +263,16 @@ export function main() {
]); ]);
}); });
it('should parse attributes on template elements', () => { it('should parse attributes on <ng-template> elements', () => {
expect(humanizeDom(parser.parse('<template k="v"></template>', 'TestComp'))).toEqual([ expect(humanizeDom(parser.parse('<template k="v"></template>', 'TestComp'))).toEqual([
[html.Element, 'template', 0], [html.Element, 'template', 0],
[html.Attribute, 'k', 'v'], [html.Attribute, 'k', 'v'],
]); ]);
expect(humanizeDom(parser.parse('<ng-template k="v"></ng-template>', 'TestComp')))
.toEqual([
[html.Element, 'ng-template', 0],
[html.Attribute, 'k', 'v'],
]);
}); });
it('should support namespace', () => { it('should support namespace', () => {

View File

@ -27,7 +27,7 @@ export function main() {
expect(humanizeNodes(res.nodes)).toEqual([ expect(humanizeNodes(res.nodes)).toEqual([
[html.Element, 'ng-container', 0], [html.Element, 'ng-container', 0],
[html.Attribute, '[ngPlural]', 'messages.length'], [html.Attribute, '[ngPlural]', 'messages.length'],
[html.Element, 'template', 1], [html.Element, 'ng-template', 1],
[html.Attribute, 'ngPluralCase', '=0'], [html.Attribute, 'ngPluralCase', '=0'],
[html.Text, 'zero', 2], [html.Text, 'zero', 2],
[html.Element, 'b', 2], [html.Element, 'b', 2],
@ -41,11 +41,11 @@ export function main() {
expect(humanizeNodes(res.nodes)).toEqual([ expect(humanizeNodes(res.nodes)).toEqual([
[html.Element, 'ng-container', 0], [html.Element, 'ng-container', 0],
[html.Attribute, '[ngPlural]', 'messages.length'], [html.Attribute, '[ngPlural]', 'messages.length'],
[html.Element, 'template', 1], [html.Element, 'ng-template', 1],
[html.Attribute, 'ngPluralCase', '=0'], [html.Attribute, 'ngPluralCase', '=0'],
[html.Element, 'ng-container', 2], [html.Element, 'ng-container', 2],
[html.Attribute, '[ngSwitch]', 'p.gender'], [html.Attribute, '[ngSwitch]', 'p.gender'],
[html.Element, 'template', 3], [html.Element, 'ng-template', 3],
[html.Attribute, 'ngSwitchCase', '=m'], [html.Attribute, 'ngSwitchCase', '=m'],
[html.Text, 'm', 4], [html.Text, 'm', 4],
[html.Text, ' ', 2], [html.Text, ' ', 2],
@ -86,10 +86,10 @@ export function main() {
expect(humanizeNodes(res.nodes)).toEqual([ expect(humanizeNodes(res.nodes)).toEqual([
[html.Element, 'ng-container', 0], [html.Element, 'ng-container', 0],
[html.Attribute, '[ngSwitch]', 'person.gender'], [html.Attribute, '[ngSwitch]', 'person.gender'],
[html.Element, 'template', 1], [html.Element, 'ng-template', 1],
[html.Attribute, 'ngSwitchCase', 'male'], [html.Attribute, 'ngSwitchCase', 'male'],
[html.Text, 'm', 2], [html.Text, 'm', 2],
[html.Element, 'template', 1], [html.Element, 'ng-template', 1],
[html.Attribute, 'ngSwitchDefault', ''], [html.Attribute, 'ngSwitchDefault', ''],
[html.Text, 'default', 2], [html.Text, 'default', 2],
]); ]);
@ -103,7 +103,7 @@ export function main() {
[html.Element, 'span', 1], [html.Element, 'span', 1],
[html.Element, 'ng-container', 2], [html.Element, 'ng-container', 2],
[html.Attribute, '[ngSwitch]', 'a'], [html.Attribute, '[ngSwitch]', 'a'],
[html.Element, 'template', 3], [html.Element, 'ng-template', 3],
[html.Attribute, 'ngSwitchCase', '=4'], [html.Attribute, 'ngSwitchCase', '=4'],
[html.Text, 'c', 4], [html.Text, 'c', 4],
]); ]);

View File

@ -563,16 +563,23 @@ Binding to attribute 'onEvent' is disallowed for security reasons ("<my-componen
const dirA = const dirA =
CompileDirectiveMetadata CompileDirectiveMetadata
.create({ .create({
selector: 'template', selector: 'template,ng-template',
outputs: ['e'], outputs: ['e'],
type: createTypeMeta({reference: {filePath: someModuleUrl, name: 'DirA'}}) type: createTypeMeta({reference: {filePath: someModuleUrl, name: 'DirA'}})
}) })
.toSummary(); .toSummary();
expect(humanizeTplAst(parse('<template (e)="f"></template>', [dirA]))).toEqual([ expect(humanizeTplAst(parse('<template (e)="f"></template>', [dirA]))).toEqual([
[EmbeddedTemplateAst], [EmbeddedTemplateAst],
[BoundEventAst, 'e', null, 'f'], [BoundEventAst, 'e', null, 'f'],
[DirectiveAst, dirA], [DirectiveAst, dirA],
]); ]);
expect(humanizeTplAst(parse('<ng-template (e)="f"></ng-template>', [dirA]))).toEqual([
[EmbeddedTemplateAst],
[BoundEventAst, 'e', null, 'f'],
[DirectiveAst, dirA],
]);
}); });
}); });
@ -1167,7 +1174,8 @@ Reference "#a" is defined several times ("<div #a></div><div [ERROR ->]#a></div>
() => { () => {
expect(() => parse('<div #a><template #a><span>OK</span></template></div>', [])) expect(() => parse('<div #a><template #a><span>OK</span></template></div>', []))
.not.toThrowError(); .not.toThrowError();
expect(() => parse('<div #a><ng-template #a><span>OK</span></ng-template></div>', []))
.not.toThrowError();
}); });
it('should assign references with empty value to components', () => { it('should assign references with empty value to components', () => {
@ -1204,25 +1212,35 @@ Reference "#a" is defined several times ("<div #a></div><div [ERROR ->]#a></div>
}); });
describe('explicit templates', () => { describe('explicit templates', () => {
it('should create embedded templates for <template> elements', () => { it('should create embedded templates for <ng-template> elements', () => {
expect(humanizeTplAst(parse('<template></template>', [ expect(humanizeTplAst(parse('<template></template>', [
]))).toEqual([[EmbeddedTemplateAst]]); ]))).toEqual([[EmbeddedTemplateAst]]);
expect(humanizeTplAst(parse('<TEMPLATE></TEMPLATE>', [ expect(humanizeTplAst(parse('<TEMPLATE></TEMPLATE>', [
]))).toEqual([[EmbeddedTemplateAst]]); ]))).toEqual([[EmbeddedTemplateAst]]);
expect(humanizeTplAst(parse('<ng-template></ng-template>', [
]))).toEqual([[EmbeddedTemplateAst]]);
}); });
it('should create embedded templates for <template> elements regardless the namespace', it('should create embedded templates for <ng-template> elements regardless the namespace',
() => { () => {
expect(humanizeTplAst(parse('<svg><template></template></svg>', []))).toEqual([ expect(humanizeTplAst(parse('<svg><template></template></svg>', []))).toEqual([
[ElementAst, ':svg:svg'], [ElementAst, ':svg:svg'],
[EmbeddedTemplateAst], [EmbeddedTemplateAst],
]); ]);
expect(humanizeTplAst(parse('<svg><ng-template></ng-template></svg>', []))).toEqual([
[ElementAst, ':svg:svg'],
[EmbeddedTemplateAst],
]);
}); });
it('should support references via #...', () => { it('should support references via #...', () => {
expect(humanizeTplAst(parse('<template #a>', []))).toEqual([ expect(humanizeTplAst(parse('<template #a>', []))).toEqual([
[EmbeddedTemplateAst], [EmbeddedTemplateAst],
[ReferenceAst, 'a', createIdentifierToken(Identifiers.TemplateRef)] [ReferenceAst, 'a', createIdentifierToken(Identifiers.TemplateRef)],
]);
expect(humanizeTplAst(parse('<ng-template #a>', []))).toEqual([
[EmbeddedTemplateAst],
[ReferenceAst, 'a', createIdentifierToken(Identifiers.TemplateRef)],
]); ]);
}); });
@ -1231,11 +1249,21 @@ Reference "#a" is defined several times ("<div #a></div><div [ERROR ->]#a></div>
[EmbeddedTemplateAst], [EmbeddedTemplateAst],
[ReferenceAst, 'a', createIdentifierToken(Identifiers.TemplateRef)] [ReferenceAst, 'a', createIdentifierToken(Identifiers.TemplateRef)]
]); ]);
expect(humanizeTplAst(parse('<ng-template ref-a>', []))).toEqual([
[EmbeddedTemplateAst],
[ReferenceAst, 'a', createIdentifierToken(Identifiers.TemplateRef)]
]);
}); });
it('should parse variables via let-...', () => { it('should parse variables via let-...', () => {
expect(humanizeTplAst(parse('<template let-a="b">', [ expect(humanizeTplAst(parse('<template let-a="b">', []))).toEqual([
]))).toEqual([[EmbeddedTemplateAst], [VariableAst, 'a', 'b']]); [EmbeddedTemplateAst],
[VariableAst, 'a', 'b'],
]);
expect(humanizeTplAst(parse('<ng-template let-a="b">', []))).toEqual([
[EmbeddedTemplateAst],
[VariableAst, 'a', 'b'],
]);
}); });
it('should not locate directives in variables', () => { it('should not locate directives in variables', () => {
@ -1247,7 +1275,12 @@ Reference "#a" is defined several times ("<div #a></div><div [ERROR ->]#a></div>
}) })
.toSummary(); .toSummary();
expect(humanizeTplAst(parse('<template let-a="b"></template>', [dirA]))).toEqual([ expect(humanizeTplAst(parse('<template let-a="b"></template>', [dirA]))).toEqual([
[EmbeddedTemplateAst], [VariableAst, 'a', 'b'] [EmbeddedTemplateAst],
[VariableAst, 'a', 'b'],
]);
expect(humanizeTplAst(parse('<ng-template let-a="b"></ng-template>', [dirA]))).toEqual([
[EmbeddedTemplateAst],
[VariableAst, 'a', 'b'],
]); ]);
}); });
@ -1255,8 +1288,10 @@ Reference "#a" is defined several times ("<div #a></div><div [ERROR ->]#a></div>
describe('inline templates', () => { describe('inline templates', () => {
it('should wrap the element into an EmbeddedTemplateAST', () => { it('should wrap the element into an EmbeddedTemplateAST', () => {
expect(humanizeTplAst(parse('<div template>', [ expect(humanizeTplAst(parse('<div template>', []))).toEqual([
]))).toEqual([[EmbeddedTemplateAst], [ElementAst, 'div']]); [EmbeddedTemplateAst],
[ElementAst, 'div'],
]);
}); });
it('should wrap the element with data-template attribute into an EmbeddedTemplateAST ', it('should wrap the element with data-template attribute into an EmbeddedTemplateAST ',
@ -1403,7 +1438,10 @@ Reference "#a" is defined several times ("<div #a></div><div [ERROR ->]#a></div>
describe('project text nodes', () => { describe('project text nodes', () => {
it('should project text nodes with wildcard selector', () => { it('should project text nodes with wildcard selector', () => {
expect(humanizeContentProjection(parse('<div>hello</div>', [createComp('div', ['*'])]))) expect(humanizeContentProjection(parse('<div>hello</div>', [createComp('div', ['*'])])))
.toEqual([['div', null], ['#text(hello)', 0]]); .toEqual([
['div', null],
['#text(hello)', 0],
]);
}); });
}); });
@ -1415,24 +1453,37 @@ Reference "#a" is defined several times ("<div #a></div><div [ERROR ->]#a></div>
}); });
it('should project elements with css selector', () => { it('should project elements with css selector', () => {
expect(humanizeContentProjection(parse('<div><a x></a><b></b></div>', [ expect(humanizeContentProjection(
createComp('div', ['a[x]']) parse('<div><a x></a><b></b></div>', [createComp('div', ['a[x]'])])))
]))).toEqual([['div', null], ['a', 0], ['b', null]]); .toEqual([
['div', null],
['a', 0],
['b', null],
]);
}); });
}); });
describe('embedded templates', () => { describe('embedded templates', () => {
it('should project embedded templates with wildcard selector', () => { it('should project embedded templates with wildcard selector', () => {
expect(humanizeContentProjection(parse('<div><template></template></div>', [ expect(humanizeContentProjection(parse(
createComp('div', ['*']) '<div><template></template><ng-template></ng-template></div>',
]))).toEqual([['div', null], ['template', 0]]); [createComp('div', ['*'])])))
.toEqual([
['div', null],
['template', 0],
['template', 0],
]);
}); });
it('should project embedded templates with css selector', () => { it('should project embedded templates with css selector', () => {
expect(humanizeContentProjection(parse( expect(humanizeContentProjection(parse(
'<div><template x></template><template></template></div>', '<div><ng-template x></ng-template><ng-template></ng-template></div>',
[createComp('div', ['template[x]'])]))) [createComp('div', ['ng-template[x]'])])))
.toEqual([['div', null], ['template', 0], ['template', null]]); .toEqual([
['div', null],
['template', 0],
['template', null],
]);
}); });
}); });
@ -1501,18 +1552,27 @@ Reference "#a" is defined several times ("<div #a></div><div [ERROR ->]#a></div>
.toEqual([['div', null], ['ng-content', 1]]); .toEqual([['div', null], ['ng-content', 1]]);
}); });
it('should override <template>', () => { it('should override <ng-template>', () => {
expect(humanizeContentProjection(parse( expect(
'<div><template ngProjectAs="b"></template></div>', humanizeContentProjection(parse(
[createComp('div', ['template', 'b'])]))) '<div><template ngProjectAs="b"></template><ng-template ngProjectAs="b"></ng-template></div>',
.toEqual([['div', null], ['template', 1]]); [createComp('div', ['template', 'b'])])))
.toEqual([
['div', null],
['template', 1],
['template', 1],
]);
}); });
it('should override inline templates', () => { it('should override inline templates', () => {
expect(humanizeContentProjection(parse( expect(humanizeContentProjection(parse(
'<div><a *ngIf="cond" ngProjectAs="b"></a></div>', '<div><a *ngIf="cond" ngProjectAs="b"></a></div>',
[createComp('div', ['a', 'b']), ngIf]))) [createComp('div', ['a', 'b']), ngIf])))
.toEqual([['div', null], ['template', 1], ['a', null]]); .toEqual([
['div', null],
['template', 1],
['a', null],
]);
}); });
}); });
@ -1539,13 +1599,17 @@ Reference "#a" is defined several times ("<div #a></div><div [ERROR ->]#a></div>
`<ng-content> element cannot have content. ("[ERROR ->]<ng-content>content</ng-content>"): TestComp@0:0`); `<ng-content> element cannot have content. ("[ERROR ->]<ng-content>content</ng-content>"): TestComp@0:0`);
}); });
it('should treat *attr on a template element as valid', it('should treat *attr on a template element as valid', () => {
() => { expect(() => parse('<template *ngIf>', [])).not.toThrowError(); }); expect(() => parse('<template *ngIf>', [])).not.toThrowError();
expect(() => parse('<ng-template *ngIf>', [])).not.toThrowError();
});
it('should treat template attribute on a template element as valid', it('should treat template attribute on a template element as valid', () => {
() => { expect(() => parse('<template template="ngIf">', [])).not.toThrowError(); }); expect(() => parse('<template template="ngIf">', [])).not.toThrowError();
expect(() => parse('<ng-template template="ngIf">', [])).not.toThrowError();
});
it('should report when mutliple *attrs are used on the same element', () => { it('should report when multiple *attrs are used on the same element', () => {
expect(() => parse('<div *ngIf *ngFor>', [])).toThrowError(`Template parse errors: expect(() => parse('<div *ngIf *ngFor>', [])).toThrowError(`Template parse errors:
Can't have multiple template bindings on one element. Use only one attribute named 'template' or prefixed with * ("<div *ngIf [ERROR ->]*ngFor>"): TestComp@0:11`); Can't have multiple template bindings on one element. Use only one attribute named 'template' or prefixed with * ("<div *ngIf [ERROR ->]*ngFor>"): TestComp@0:11`);
}); });
@ -1630,11 +1694,18 @@ Parser Error: Unexpected token 'b' at column 3 in [a b] in TestComp@0:5 ("<div [
template: new CompileTemplateMetadata({ngContentSelectors: []}) template: new CompileTemplateMetadata({ngContentSelectors: []})
}) })
.toSummary(); .toSummary();
expect(() => parse('<template [a]="b" (e)="f"></template>', [dirA])) expect(() => parse('<template [a]="b" (e)="f"></template>', [dirA]))
.toThrowError(`Template parse errors: .toThrowError(`Template parse errors:
Event binding e not emitted by any directive on an embedded template. Make sure that the event name is spelled correctly and all directives are listed in the "@NgModule.declarations". ("<template [a]="b" [ERROR ->](e)="f"></template>"): TestComp@0:18 Event binding e not emitted by any directive on an embedded template. Make sure that the event name is spelled correctly and all directives are listed in the "@NgModule.declarations". ("<template [a]="b" [ERROR ->](e)="f"></template>"): TestComp@0:18
Components on an embedded template: DirA ("[ERROR ->]<template [a]="b" (e)="f"></template>"): TestComp@0:0 Components on an embedded template: DirA ("[ERROR ->]<template [a]="b" (e)="f"></template>"): TestComp@0:0
Property binding a not used by any directive on an embedded template. Make sure that the property name is spelled correctly and all directives are listed in the "@NgModule.declarations". ("[ERROR ->]<template [a]="b" (e)="f"></template>"): TestComp@0:0`); Property binding a not used by any directive on an embedded template. Make sure that the property name is spelled correctly and all directives are listed in the "@NgModule.declarations". ("[ERROR ->]<template [a]="b" (e)="f"></template>"): TestComp@0:0`);
expect(() => parse('<ng-template [a]="b" (e)="f"></ng-template>', [dirA]))
.toThrowError(`Template parse errors:
Event binding e not emitted by any directive on an embedded template. Make sure that the event name is spelled correctly and all directives are listed in the "@NgModule.declarations". ("<ng-template [a]="b" [ERROR ->](e)="f"></ng-template>"): TestComp@0:21
Components on an embedded template: DirA ("[ERROR ->]<ng-template [a]="b" (e)="f"></ng-template>"): TestComp@0:0
Property binding a not used by any directive on an embedded template. Make sure that the property name is spelled correctly and all directives are listed in the "@NgModule.declarations". ("[ERROR ->]<ng-template [a]="b" (e)="f"></ng-template>"): TestComp@0:0`);
}); });
it('should not allow components or element bindings on inline embedded templates', () => { it('should not allow components or element bindings on inline embedded templates', () => {
@ -1745,7 +1816,8 @@ Property binding a not used by any directive on an embedded template. Make sure
it('should support embedded template', () => { it('should support embedded template', () => {
expect(humanizeTplAstSourceSpans(parse('<template></template>', [ expect(humanizeTplAstSourceSpans(parse('<template></template>', [
]))).toEqual([[EmbeddedTemplateAst, '<template>']]); ]))).toEqual([[EmbeddedTemplateAst, '<template>']]);
expect(humanizeTplAstSourceSpans(parse('<ng-template></ng-template>', [
]))).toEqual([[EmbeddedTemplateAst, '<ng-template>']]);
}); });
it('should support element and attributes', () => { it('should support element and attributes', () => {
@ -1762,8 +1834,14 @@ Property binding a not used by any directive on an embedded template. Make sure
it('should support variables', () => { it('should support variables', () => {
expect(humanizeTplAstSourceSpans(parse('<template let-a="b"></template>', []))).toEqual([ expect(humanizeTplAstSourceSpans(parse('<template let-a="b"></template>', []))).toEqual([
[EmbeddedTemplateAst, '<template let-a="b">'], [VariableAst, 'a', 'b', 'let-a="b"'] [EmbeddedTemplateAst, '<template let-a="b">'],
[VariableAst, 'a', 'b', 'let-a="b"'],
]); ]);
expect(humanizeTplAstSourceSpans(parse('<ng-template let-a="b"></ng-template>', [])))
.toEqual([
[EmbeddedTemplateAst, '<ng-template let-a="b">'],
[VariableAst, 'a', 'b', 'let-a="b"'],
]);
}); });
it('should support events', () => { it('should support events', () => {
@ -1917,8 +1995,8 @@ The pipe 'test' could not be found ("{{[ERROR ->]a | test}}"): TestComp@0:2`);
it('should expand plural messages', () => { it('should expand plural messages', () => {
const shortForm = '{ count, plural, =0 {small} many {big} }'; const shortForm = '{ count, plural, =0 {small} many {big} }';
const expandedForm = '<ng-container [ngPlural]="count">' + const expandedForm = '<ng-container [ngPlural]="count">' +
'<template ngPluralCase="=0">small</template>' + '<ng-template ngPluralCase="=0">small</ng-template>' +
'<template ngPluralCase="many">big</template>' + '<ng-template ngPluralCase="many">big</ng-template>' +
'</ng-container>'; '</ng-container>';
expect(humanizeTplAst(parse(shortForm, []))).toEqual(humanizeTplAst(parse(expandedForm, [ expect(humanizeTplAst(parse(shortForm, []))).toEqual(humanizeTplAst(parse(expandedForm, [
@ -1928,8 +2006,8 @@ The pipe 'test' could not be found ("{{[ERROR ->]a | test}}"): TestComp@0:2`);
it('should expand select messages', () => { it('should expand select messages', () => {
const shortForm = '{ sex, select, female {foo} other {bar} }'; const shortForm = '{ sex, select, female {foo} other {bar} }';
const expandedForm = '<ng-container [ngSwitch]="sex">' + const expandedForm = '<ng-container [ngSwitch]="sex">' +
'<template ngSwitchCase="female">foo</template>' + '<ng-template ngSwitchCase="female">foo</ng-template>' +
'<template ngSwitchDefault>bar</template>' + '<ng-template ngSwitchDefault>bar</ng-template>' +
'</ng-container>'; '</ng-container>';
expect(humanizeTplAst(parse(shortForm, []))).toEqual(humanizeTplAst(parse(expandedForm, [ expect(humanizeTplAst(parse(shortForm, []))).toEqual(humanizeTplAst(parse(expandedForm, [
@ -2057,10 +2135,6 @@ class TemplateHumanizer implements TemplateAstVisitor {
} }
} }
function sourceInfo(ast: TemplateAst): string {
return `${ast.sourceSpan}: ${ast.sourceSpan.start}`;
}
function humanizeContentProjection(templateAsts: TemplateAst[]): any[] { function humanizeContentProjection(templateAsts: TemplateAst[]): any[] {
const humanizer = new TemplateContentProjectionHumanizer(); const humanizer = new TemplateContentProjectionHumanizer();
templateVisitAll(humanizer, templateAsts); templateVisitAll(humanizer, templateAsts);

View File

@ -14,10 +14,10 @@ import {EmbeddedViewRef} from './view_ref';
/** /**
* Represents an Embedded Template that can be used to instantiate Embedded Views. * Represents an Embedded Template that can be used to instantiate Embedded Views.
* *
* You can access a `TemplateRef`, in two ways. Via a directive placed on a `<template>` element (or * You can access a `TemplateRef`, in two ways. Via a directive placed on a `<ng-template>` element
* directive prefixed with `*`) and have the `TemplateRef` for this Embedded View injected into the * (or directive prefixed with `*`) and have the `TemplateRef` for this Embedded View injected into
* constructor of the directive using the `TemplateRef` Token. Alternatively you can query for the * the constructor of the directive using the `TemplateRef` Token. Alternatively you can query for
* `TemplateRef` from a Component or a Directive via {@link Query}. * the `TemplateRef` from a Component or a Directive via {@link Query}.
* *
* To instantiate Embedded Views based on a Template, use * To instantiate Embedded Views based on a Template, use
* {@link ViewContainerRef#createEmbeddedView}, which will create the View and attach it to the * {@link ViewContainerRef#createEmbeddedView}, which will create the View and attach it to the

View File

@ -57,7 +57,7 @@ export abstract class ViewRef extends ChangeDetectorRef {
* ``` * ```
* Count: {{items.length}} * Count: {{items.length}}
* <ul> * <ul>
* <template ngFor let-item [ngForOf]="items"></template> * <ng-template ngFor let-item [ngForOf]="items"></ng-template>
* </ul> * </ul>
* ``` * ```
* *
@ -74,7 +74,7 @@ export abstract class ViewRef extends ChangeDetectorRef {
* <!-- ViewRef: outer-0 --> * <!-- ViewRef: outer-0 -->
* Count: 2 * Count: 2
* <ul> * <ul>
* <template view-container-ref></template> * <ng-template view-container-ref></ng-template>
* <!-- ViewRef: inner-1 --><li>first</li><!-- /ViewRef: inner-1 --> * <!-- ViewRef: inner-1 --><li>first</li><!-- /ViewRef: inner-1 -->
* <!-- ViewRef: inner-2 --><li>second</li><!-- /ViewRef: inner-2 --> * <!-- ViewRef: inner-2 --><li>second</li><!-- /ViewRef: inner-2 -->
* </ul> * </ul>

View File

@ -10,10 +10,8 @@ export enum ViewType {
// A view that contains the host element with bound component directive. // A view that contains the host element with bound component directive.
// Contains a COMPONENT view // Contains a COMPONENT view
HOST, HOST,
// The view of the component // The view of the component can contain 0 to n EMBEDDED views
// Can contain 0 to n EMBEDDED views
COMPONENT, COMPONENT,
// A view that is embedded into another View via a <template> element // A view is embedded into another View via a <ng-template> element inside of a COMPONENT view
// inside of a COMPONENT view
EMBEDDED EMBEDDED
} }

View File

@ -286,7 +286,7 @@ export function main() {
vc: ViewContainerRef; vc: ViewContainerRef;
} }
@Component({template: '<template #t>Dynamic content</template>'}) @Component({template: '<ng-template #t>Dynamic content</ng-template>'})
class EmbeddedViewComp { class EmbeddedViewComp {
@ViewChild(TemplateRef) @ViewChild(TemplateRef)
tplRef: TemplateRef<Object>; tplRef: TemplateRef<Object>;

View File

@ -485,8 +485,8 @@ function createTests({viewEngine}: {viewEngine: boolean}) {
fakeAsync(() => { expect(_bindAndCheckSimpleValue('"$"')).toEqual(['someProp=$']); })); fakeAsync(() => { expect(_bindAndCheckSimpleValue('"$"')).toEqual(['someProp=$']); }));
it('should read locals', fakeAsync(() => { it('should read locals', fakeAsync(() => {
const ctx = const ctx = createCompFixture(
createCompFixture('<template testLocals let-local="someLocal">{{local}}</template>'); '<ng-template testLocals let-local="someLocal">{{local}}</ng-template>');
ctx.detectChanges(false); ctx.detectChanges(false);
expect(renderLog.log).toEqual(['{{someLocalValue}}']); expect(renderLog.log).toEqual(['{{someLocalValue}}']);
@ -1242,7 +1242,7 @@ function createTests({viewEngine}: {viewEngine: boolean}) {
it('should recurse into nested view containers even if there are no bindings in the component view', it('should recurse into nested view containers even if there are no bindings in the component view',
() => { () => {
@Component({template: '<template #vc>{{name}}</template>'}) @Component({template: '<ng-template #vc>{{name}}</ng-template>'})
class Comp { class Comp {
name = 'Tom'; name = 'Tom';
@ViewChild('vc', {read: ViewContainerRef}) vc: ViewContainerRef; @ViewChild('vc', {read: ViewContainerRef}) vc: ViewContainerRef;

View File

@ -130,7 +130,7 @@ export function main() {
it('should support using structural directives with ngTemplateOutlet', () => { it('should support using structural directives with ngTemplateOutlet', () => {
@Component({ @Component({
template: template:
'<child [templateCtx]="templateCtx"><template let-shown="shown" #tpl><span *ngIf="shown">hello</span></template></child>' '<child [templateCtx]="templateCtx"><ng-template let-shown="shown" #tpl><span *ngIf="shown">hello</span></ng-template></child>'
}) })
class Parent { class Parent {
templateCtx = {shown: false}; templateCtx = {shown: false};

View File

@ -363,10 +363,10 @@ function declareTests({useJit, viewEngine}: {useJit: boolean, viewEngine: boolea
const fixture = TestBed.createComponent(MyComp); const fixture = TestBed.createComponent(MyComp);
}); });
it('should support template directives via `<template>` elements.', () => { it('should support template directives via `<ng-template>` elements.', () => {
TestBed.configureTestingModule({declarations: [MyComp, SomeViewport]}); TestBed.configureTestingModule({declarations: [MyComp, SomeViewport]});
const template = const template =
'<template some-viewport let-greeting="someTmpl"><span>{{greeting}}</span></template>'; '<ng-template some-viewport let-greeting="someTmpl"><span>{{greeting}}</span></ng-template>';
TestBed.overrideComponent(MyComp, {set: {template}}); TestBed.overrideComponent(MyComp, {set: {template}});
const fixture = TestBed.createComponent(MyComp); const fixture = TestBed.createComponent(MyComp);
@ -382,7 +382,7 @@ function declareTests({useJit, viewEngine}: {useJit: boolean, viewEngine: boolea
it('should not share empty context for template directives - issue #10045', () => { it('should not share empty context for template directives - issue #10045', () => {
TestBed.configureTestingModule({declarations: [MyComp, PollutedContext, NoContext]}); TestBed.configureTestingModule({declarations: [MyComp, PollutedContext, NoContext]});
const template = const template =
'<template pollutedContext let-foo="bar">{{foo}}</template><template noContext let-foo="bar">{{foo}}</template>'; '<ng-template pollutedContext let-foo="bar">{{foo}}</ng-template><ng-template noContext let-foo="bar">{{foo}}</ng-template>';
TestBed.overrideComponent(MyComp, {set: {template}}); TestBed.overrideComponent(MyComp, {set: {template}});
const fixture = TestBed.createComponent(MyComp); const fixture = TestBed.createComponent(MyComp);
@ -393,7 +393,7 @@ function declareTests({useJit, viewEngine}: {useJit: boolean, viewEngine: boolea
it('should not detach views in ViewContainers when the parent view is destroyed.', () => { it('should not detach views in ViewContainers when the parent view is destroyed.', () => {
TestBed.configureTestingModule({declarations: [MyComp, SomeViewport]}); TestBed.configureTestingModule({declarations: [MyComp, SomeViewport]});
const template = const template =
'<div *ngIf="ctxBoolProp"><template some-viewport let-greeting="someTmpl"><span>{{greeting}}</span></template></div>'; '<div *ngIf="ctxBoolProp"><ng-template some-viewport let-greeting="someTmpl"><span>{{greeting}}</span></ng-template></div>';
TestBed.overrideComponent(MyComp, {set: {template}}); TestBed.overrideComponent(MyComp, {set: {template}});
const fixture = TestBed.createComponent(MyComp); const fixture = TestBed.createComponent(MyComp);
@ -412,11 +412,11 @@ function declareTests({useJit, viewEngine}: {useJit: boolean, viewEngine: boolea
expect(fixture.debugElement.children.length).toBe(0); expect(fixture.debugElement.children.length).toBe(0);
}); });
it('should use a comment while stamping out `<template>` elements.', () => { it('should use a comment while stamping out `<ng-template>` elements.', () => {
TestBed.configureTestingModule({declarations: [MyComp]}); const fixture =
const template = '<template></template>'; TestBed.configureTestingModule({declarations: [MyComp]})
TestBed.overrideComponent(MyComp, {set: {template}}); .overrideComponent(MyComp, {set: {template: '<ng-template></ng-template>'}})
const fixture = TestBed.createComponent(MyComp); .createComponent(MyComp);
const childNodesOfWrapper = getDOM().childNodes(fixture.nativeElement); const childNodesOfWrapper = getDOM().childNodes(fixture.nativeElement);
expect(childNodesOfWrapper.length).toBe(1); expect(childNodesOfWrapper.length).toBe(1);
@ -448,7 +448,7 @@ function declareTests({useJit, viewEngine}: {useJit: boolean, viewEngine: boolea
schemas: [NO_ERRORS_SCHEMA], schemas: [NO_ERRORS_SCHEMA],
}); });
const template = const template =
'<some-directive><toolbar><template toolbarpart let-toolbarProp="toolbarProp">{{ctxProp}},{{toolbarProp}},<cmp-with-host></cmp-with-host></template></toolbar></some-directive>'; '<some-directive><toolbar><ng-template toolbarpart let-toolbarProp="toolbarProp">{{ctxProp}},{{toolbarProp}},<cmp-with-host></cmp-with-host></ng-template></toolbar></some-directive>';
TestBed.overrideComponent(MyComp, {set: {template}}); TestBed.overrideComponent(MyComp, {set: {template}});
const fixture = TestBed.createComponent(MyComp); const fixture = TestBed.createComponent(MyComp);
@ -484,7 +484,7 @@ function declareTests({useJit, viewEngine}: {useJit: boolean, viewEngine: boolea
() => { () => {
TestBed.configureTestingModule({declarations: [MyComp, ChildComp]}); TestBed.configureTestingModule({declarations: [MyComp, ChildComp]});
const template = const template =
'<template [ngIf]="true">{{alice.ctxProp}}</template>|{{alice.ctxProp}}|<child-cmp ref-alice></child-cmp>'; '<ng-template [ngIf]="true">{{alice.ctxProp}}</ng-template>|{{alice.ctxProp}}|<child-cmp ref-alice></child-cmp>';
TestBed.overrideComponent(MyComp, {set: {template}}); TestBed.overrideComponent(MyComp, {set: {template}});
const fixture = TestBed.createComponent(MyComp); const fixture = TestBed.createComponent(MyComp);
@ -530,10 +530,10 @@ function declareTests({useJit, viewEngine}: {useJit: boolean, viewEngine: boolea
}); });
it('should assign the TemplateRef to a user-defined variable', () => { it('should assign the TemplateRef to a user-defined variable', () => {
TestBed.configureTestingModule({declarations: [MyComp]}); const fixture =
const template = '<template ref-alice></template>'; TestBed.configureTestingModule({declarations: [MyComp]})
TestBed.overrideComponent(MyComp, {set: {template}}); .overrideComponent(MyComp, {set: {template: '<template ref-alice></template>'}})
const fixture = TestBed.createComponent(MyComp); .createComponent(MyComp);
const value = fixture.debugElement.childNodes[0].references['alice']; const value = fixture.debugElement.childNodes[0].references['alice'];
expect(value.createEmbeddedView).toBeTruthy(); expect(value.createEmbeddedView).toBeTruthy();
@ -552,14 +552,16 @@ function declareTests({useJit, viewEngine}: {useJit: boolean, viewEngine: boolea
describe('variables', () => { describe('variables', () => {
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]});
const template = const template =
'<template ngFor [ngForOf]="[1]" let-i><child-cmp-no-template #cmp></child-cmp-no-template>{{i}}-{{cmp.ctxProp}}</template>'; '<ng-template ngFor [ngForOf]="[1]" let-i><child-cmp-no-template #cmp></child-cmp-no-template>{{i}}-{{cmp.ctxProp}}</ng-template>';
TestBed.overrideComponent(MyComp, {set: {template}});
const fixture = TestBed.createComponent(MyComp); const fixture =
TestBed.configureTestingModule({declarations: [MyComp, ChildCompNoTemplate]})
.overrideComponent(MyComp, {set: {template}})
.createComponent(MyComp);
fixture.detectChanges(); fixture.detectChanges();
// Get the element at index 2, since index 0 is the <template>. // Get the element at index 2, since index 0 is the <ng-template>.
expect(getDOM().childNodes(fixture.nativeElement)[2]).toHaveText('1-hello'); expect(getDOM().childNodes(fixture.nativeElement)[2]).toHaveText('1-hello');
}); });
}); });
@ -774,11 +776,17 @@ function declareTests({useJit, viewEngine}: {useJit: boolean, viewEngine: boolea
})); }));
it('should support events via EventEmitter on template elements', async(() => { it('should support events via EventEmitter on template elements', async(() => {
TestBed.configureTestingModule( const fixture =
{declarations: [MyComp, DirectiveEmittingEvent, DirectiveListeningEvent]}); TestBed
const template = '<template emitter listener (event)="ctxProp=$event"></template>'; .configureTestingModule(
TestBed.overrideComponent(MyComp, {set: {template}}); {declarations: [MyComp, DirectiveEmittingEvent, DirectiveListeningEvent]})
const fixture = TestBed.createComponent(MyComp); .overrideComponent(MyComp, {
set: {
template:
'<ng-template emitter listener (event)="ctxProp=$event"></ng-template>'
}
})
.createComponent(MyComp);
const tc = fixture.debugElement.childNodes[0]; const tc = fixture.debugElement.childNodes[0];
@ -1487,10 +1495,11 @@ function declareTests({useJit, viewEngine}: {useJit: boolean, viewEngine: boolea
}); });
it('should reflect property values on template comments', () => { it('should reflect property values on template comments', () => {
TestBed.configureTestingModule({declarations: [MyComp]}); const fixture =
const template = '<template [ngIf]="ctxBoolProp"></template>'; TestBed.configureTestingModule({declarations: [MyComp]})
TestBed.overrideComponent(MyComp, {set: {template}}); .overrideComponent(
const fixture = TestBed.createComponent(MyComp); MyComp, {set: {template: '<ng-template [ngIf]="ctxBoolProp"></ng-template>'}})
.createComponent(MyComp);
fixture.componentInstance.ctxBoolProp = true; fixture.componentInstance.ctxBoolProp = true;
fixture.detectChanges(); fixture.detectChanges();

View File

@ -132,7 +132,7 @@ function createTests({viewEngine}: {viewEngine: boolean}) {
TestBed.overrideComponent(MainComp, { TestBed.overrideComponent(MainComp, {
set: { set: {
template: '<multiple-content-tags>' + template: '<multiple-content-tags>' +
'<template manual class="left"><div>A1</div></template>' + '<ng-template manual class="left"><div>A1</div></ng-template>' +
'<div>B</div>' + '<div>B</div>' +
'</multiple-content-tags>' '</multiple-content-tags>'
} }
@ -175,7 +175,7 @@ function createTests({viewEngine}: {viewEngine: boolean}) {
TestBed.overrideComponent(MainComp, { TestBed.overrideComponent(MainComp, {
set: { set: {
template: '<outer>' + template: '<outer>' +
'<template manual class="left"><div>A</div></template>' + '<ng-template manual class="left"><div>A</div></ng-template>' +
'<div>B</div>' + '<div>B</div>' +
'<div>C</div>' + '<div>C</div>' +
'</outer>' '</outer>'
@ -260,7 +260,7 @@ function createTests({viewEngine}: {viewEngine: boolean}) {
TestBed.overrideComponent(MainComp, { TestBed.overrideComponent(MainComp, {
set: { set: {
template: '<empty>' + template: '<empty>' +
' <template manual><div>A</div></template>' + ' <ng-template manual><div>A</div></ng-template>' +
'</empty>' + '</empty>' +
'START(<div project></div>)END' 'START(<div project></div>)END'
} }
@ -282,7 +282,7 @@ function createTests({viewEngine}: {viewEngine: boolean}) {
{declarations: [Empty, ProjectDirective, ManualViewportDirective]}); {declarations: [Empty, ProjectDirective, ManualViewportDirective]});
TestBed.overrideComponent(MainComp, { TestBed.overrideComponent(MainComp, {
set: { set: {
template: '<simple><template manual><div>A</div></template></simple>' + template: '<simple><ng-template manual><div>A</div></ng-template></simple>' +
'START(<div project></div>)END' 'START(<div project></div>)END'
} }
}); });
@ -488,7 +488,7 @@ function createTests({viewEngine}: {viewEngine: boolean}) {
set: { set: {
template: '<conditional-content>' + template: '<conditional-content>' +
'<div class="left">A</div>' + '<div class="left">A</div>' +
'<template manual class="left">B</template>' + '<ng-template manual class="left">B</ng-template>' +
'<div class="left">C</div>' + '<div class="left">C</div>' +
'<div>D</div>' + '<div>D</div>' +
'</conditional-content>' '</conditional-content>'
@ -628,7 +628,7 @@ class ConditionalContentComponent {
@Component({ @Component({
selector: 'conditional-text', selector: 'conditional-text',
template: template:
'MAIN(<template manual>FIRST(<template manual>SECOND(<ng-content></ng-content>)</template>)</template>)', 'MAIN(<ng-template manual>FIRST(<ng-template manual>SECOND(<ng-content></ng-content>)</ng-template>)</ng-template>)',
}) })
class ConditionalTextComponent { class ConditionalTextComponent {
} }

View File

@ -225,7 +225,7 @@ function createTests({viewEngine}: {viewEngine: boolean}) {
describe('query for TemplateRef', () => { describe('query for TemplateRef', () => {
it('should find TemplateRefs in the light and shadow dom', () => { it('should find TemplateRefs in the light and shadow dom', () => {
const template = '<needs-tpl><template><div>light</div></template></needs-tpl>'; const template = '<needs-tpl><ng-template><div>light</div></ng-template></needs-tpl>';
const view = createTestCmpAndDetectChanges(MyComp0, template); const view = createTestCmpAndDetectChanges(MyComp0, template);
const needsTpl: NeedsTpl = view.debugElement.children[0].injector.get(NeedsTpl); const needsTpl: NeedsTpl = view.debugElement.children[0].injector.get(NeedsTpl);
@ -237,7 +237,7 @@ function createTests({viewEngine}: {viewEngine: boolean}) {
it('should find named TemplateRefs', () => { it('should find named TemplateRefs', () => {
const template = const template =
'<needs-named-tpl><template #tpl><div>light</div></template></needs-named-tpl>'; '<needs-named-tpl><ng-template #tpl><div>light</div></ng-template></needs-named-tpl>';
const view = createTestCmpAndDetectChanges(MyComp0, template); const view = createTestCmpAndDetectChanges(MyComp0, template);
const needsTpl: NeedsNamedTpl = view.debugElement.children[0].injector.get(NeedsNamedTpl); const needsTpl: NeedsNamedTpl = view.debugElement.children[0].injector.get(NeedsNamedTpl);
expect(needsTpl.vc.createEmbeddedView(needsTpl.contentTpl).rootNodes[0]) expect(needsTpl.vc.createEmbeddedView(needsTpl.contentTpl).rootNodes[0])
@ -308,7 +308,7 @@ function createTests({viewEngine}: {viewEngine: boolean}) {
it('should support reading a ViewContainer', () => { it('should support reading a ViewContainer', () => {
const template = const template =
'<needs-viewcontainer-read><template>hello</template></needs-viewcontainer-read>'; '<needs-viewcontainer-read><ng-template>hello</ng-template></needs-viewcontainer-read>';
const view = createTestCmpAndDetectChanges(MyComp0, template); const view = createTestCmpAndDetectChanges(MyComp0, template);
const comp: NeedsViewContainerWithRead = const comp: NeedsViewContainerWithRead =
@ -403,7 +403,7 @@ function createTests({viewEngine}: {viewEngine: boolean}) {
it('should contain all the elements in the light dom with the given var binding', () => { it('should contain all the elements in the light dom with the given var binding', () => {
const template = '<needs-query-by-ref-binding #q>' + const template = '<needs-query-by-ref-binding #q>' +
'<div template="ngFor: let item of list">' + '<div *ngFor="let item of list">' +
'<div #textLabel>{{item}}</div>' + '<div #textLabel>{{item}}</div>' +
'</div>' + '</div>' +
'</needs-query-by-ref-binding>'; '</needs-query-by-ref-binding>';
@ -536,7 +536,7 @@ function createTests({viewEngine}: {viewEngine: boolean}) {
describe('query over moved templates', () => { describe('query over moved templates', () => {
it('should include manually projected templates in queries', () => { it('should include manually projected templates in queries', () => {
const template = const template =
'<manual-projecting #q><template><div text="1"></div></template></manual-projecting>'; '<manual-projecting #q><ng-template><div text="1"></div></ng-template></manual-projecting>';
const view = createTestCmpAndDetectChanges(MyComp0, template); const view = createTestCmpAndDetectChanges(MyComp0, template);
const q = view.debugElement.children[0].references['q']; const q = view.debugElement.children[0].references['q'];
expect(q.query.length).toBe(0); expect(q.query.length).toBe(0);
@ -730,14 +730,15 @@ class NeedsViewQueryOrderWithParent {
list: string[] = ['2', '3']; list: string[] = ['2', '3'];
} }
@Component({selector: 'needs-tpl', template: '<template><div>shadow</div></template>'}) @Component({selector: 'needs-tpl', template: '<ng-template><div>shadow</div></ng-template>'})
class NeedsTpl { class NeedsTpl {
@ViewChildren(TemplateRef) viewQuery: QueryList<TemplateRef<Object>>; @ViewChildren(TemplateRef) viewQuery: QueryList<TemplateRef<Object>>;
@ContentChildren(TemplateRef) query: QueryList<TemplateRef<Object>>; @ContentChildren(TemplateRef) query: QueryList<TemplateRef<Object>>;
constructor(public vc: ViewContainerRef) {} constructor(public vc: ViewContainerRef) {}
} }
@Component({selector: 'needs-named-tpl', template: '<template #tpl><div>shadow</div></template>'}) @Component(
{selector: 'needs-named-tpl', template: '<ng-template #tpl><div>shadow</div></ng-template>'})
class NeedsNamedTpl { class NeedsNamedTpl {
@ViewChild('tpl') viewTpl: TemplateRef<Object>; @ViewChild('tpl') viewTpl: TemplateRef<Object>;
@ContentChild('tpl') contentTpl: TemplateRef<Object>; @ContentChild('tpl') contentTpl: TemplateRef<Object>;
@ -767,7 +768,7 @@ class NeedsContentChildTemplateRef {
@Component({ @Component({
selector: 'needs-content-child-template-ref-app', selector: 'needs-content-child-template-ref-app',
template: '<needs-content-child-template-ref>' + template: '<needs-content-child-template-ref>' +
'<template>OUTER<template>INNER</template></template>' + '<ng-template>OUTER<ng-template>INNER</ng-template></ng-template>' +
'</needs-content-child-template-ref>' '</needs-content-child-template-ref>'
}) })
class NeedsContentChildTemplateRefApp { class NeedsContentChildTemplateRefApp {

View File

@ -656,7 +656,8 @@ function createTests({viewEngine}: {viewEngine: boolean}) {
it('should inject TemplateRef', () => { it('should inject TemplateRef', () => {
TestBed.configureTestingModule({declarations: [NeedsViewContainerRef, NeedsTemplateRef]}); TestBed.configureTestingModule({declarations: [NeedsViewContainerRef, NeedsTemplateRef]});
const el = createComponent('<template needsViewContainerRef needsTemplateRef></template>'); const el =
createComponent('<ng-template needsViewContainerRef needsTemplateRef></ng-template>');
expect(el.childNodes[0].injector.get(NeedsTemplateRef).templateRef.elementRef) expect(el.childNodes[0].injector.get(NeedsTemplateRef).templateRef.elementRef)
.toEqual(el.childNodes[0].injector.get(NeedsViewContainerRef).viewContainer.element); .toEqual(el.childNodes[0].injector.get(NeedsViewContainerRef).viewContainer.element);
}); });

View File

@ -34,7 +34,7 @@ class NgIfSimple {
show = {{show}} show = {{show}}
<br> <br>
<div *ngIf="show; else elseBlock">Text to show</div> <div *ngIf="show; else elseBlock">Text to show</div>
<template #elseBlock>Alternate text while primary text is hidden</template> <ng-template #elseBlock>Alternate text while primary text is hidden</ng-template>
` `
}) })
class NgIfElse { class NgIfElse {
@ -51,9 +51,9 @@ class NgIfElse {
show = {{show}} show = {{show}}
<br> <br>
<div *ngIf="show; then thenBlock; else elseBlock">this is ignored</div> <div *ngIf="show; then thenBlock; else elseBlock">this is ignored</div>
<template #primaryBlock>Primary text to show</template> <ng-template #primaryBlock>Primary text to show</ng-template>
<template #secondaryBlock>Secondary text to show</template> <ng-template #secondaryBlock>Secondary text to show</ng-template>
<template #elseBlock>Alternate text while primary text is hidden</template> <ng-template #elseBlock>Alternate text while primary text is hidden</ng-template>
` `
}) })
class NgIfThenElse implements OnInit { class NgIfThenElse implements OnInit {
@ -82,7 +82,7 @@ class NgIfThenElse implements OnInit {
<div *ngIf="userObservable | async; else loading; let user"> <div *ngIf="userObservable | async; else loading; let user">
Hello {{user.last}}, {{user.first}}! Hello {{user.last}}, {{user.first}}!
</div> </div>
<template #loading let-user>Waiting... (user is {{user|json}})</template> <ng-template #loading let-user>Waiting... (user is {{user|json}})</ng-template>
` `
}) })
class NgIfLet { class NgIfLet {

View File

@ -22,9 +22,9 @@ import {Subject} from 'rxjs/Subject';
<ng-container *ngTemplateOutlet="svk; context: myContext"></ng-container> <ng-container *ngTemplateOutlet="svk; context: myContext"></ng-container>
<hr> <hr>
<template #greet><span>Hello</span></template> <ng-template #greet><span>Hello</span></ng-template>
<template #eng let-name><span>Hello {{name}}!</span></template> <ng-template #eng let-name><span>Hello {{name}}!</span></ng-template>
<template #svk let-person="localSk"><span>Ahoj {{person}}!</span></template> <ng-template #svk let-person="localSk"><span>Ahoj {{person}}!</span></ng-template>
` `
}) })
class NgTemplateOutletExample { class NgTemplateOutletExample {

View File

@ -15,9 +15,9 @@ import {TableCell, emptyTable} from '../util';
selector: 'largetable', selector: 'largetable',
template: `<table><tbody> template: `<table><tbody>
<tr *ngFor="let row of data; trackBy: trackByIndex"> <tr *ngFor="let row of data; trackBy: trackByIndex">
<template ngFor [ngForOf]="row" [ngForTrackBy]="trackByIndex" let-cell><ng-container [ngSwitch]="cell.row % 2"> <ng-template ngFor [ngForOf]="row" [ngForTrackBy]="trackByIndex" let-cell><ng-container [ngSwitch]="cell.row % 2">
<td *ngSwitchCase="0" style="background-color: grey">{{cell.value}}</td><td *ngSwitchDefault>{{cell.value}}</td> <td *ngSwitchCase="0" style="background-color: grey">{{cell.value}}</td><td *ngSwitchDefault>{{cell.value}}</td>
</ng-container></template> </ng-container></ng-template>
</tr> </tr>
</tbody></table>` </tbody></table>`
}) })