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
syntax:
`<div [ngSwitch]="conditionExpression">
<template [ngSwitchCase]="case1Exp">...</template>
<template ngSwitchCase="case2LiteralString">...</template>
<template ngSwitchDefault>...</template>
<ng-template [ngSwitchCase]="case1Exp">...</ng-template>
<ng-template ngSwitchCase="case2LiteralString">...</ng-template>
<ng-template ngSwitchDefault>...</ng-template>
</div>`|`[ngSwitch]`|`[ngSwitchCase]`|`ngSwitchCase`|`ngSwitchDefault`
description:
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`
description:
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
syntax:

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);

View File

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

View File

@ -58,7 +58,8 @@ export class HtmlTagDefinition implements TagDefinition {
}
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 {

View File

@ -30,9 +30,9 @@ const PLURAL_CASES: string[] = ['zero', 'one', 'two', 'few', 'many', 'other'];
*
* ```
* <ng-container [ngPlural]="messages.length">
* <template ngPluralCase="=0">zero</template>
* <template ngPluralCase="=1">one</template>
* <template ngPluralCase="other">more than one</template>
* <ng-template ngPluralCase="=0">zero</ng-template>
* <ng-template ngPluralCase="=1">one</ng-template>
* <ng-template ngPluralCase="other">more than one</ng-template>
* </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 {
const children = ast.cases.map(c => {
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);
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);
});
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);
}
// ICU messages (excluding plural form) are expanded to `NgSwitch` and `NgSwitychCase`s
function _expandDefaultForm(ast: html.Expansion, errors: ParseError[]): html.Element {
const children = ast.cases.map(c => {
const expansionResult = expandNodes(c.expression);
@ -109,12 +111,12 @@ function _expandDefaultForm(ast: html.Expansion, errors: ParseError[]): html.Ele
if (c.value === 'other') {
// other is the default case when no values match
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);
}
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);
});
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 {
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 {
constructor(

View File

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

View File

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

View File

@ -563,16 +563,23 @@ Binding to attribute 'onEvent' is disallowed for security reasons ("<my-componen
const dirA =
CompileDirectiveMetadata
.create({
selector: 'template',
selector: 'template,ng-template',
outputs: ['e'],
type: createTypeMeta({reference: {filePath: someModuleUrl, name: 'DirA'}})
})
.toSummary();
expect(humanizeTplAst(parse('<template (e)="f"></template>', [dirA]))).toEqual([
[EmbeddedTemplateAst],
[BoundEventAst, 'e', null, 'f'],
[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>', []))
.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', () => {
@ -1204,25 +1212,35 @@ Reference "#a" is defined several times ("<div #a></div><div [ERROR ->]#a></div>
});
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>', [
]))).toEqual([[EmbeddedTemplateAst]]);
expect(humanizeTplAst(parse('<TEMPLATE></TEMPLATE>', [
]))).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([
[ElementAst, ':svg:svg'],
[EmbeddedTemplateAst],
]);
expect(humanizeTplAst(parse('<svg><ng-template></ng-template></svg>', []))).toEqual([
[ElementAst, ':svg:svg'],
[EmbeddedTemplateAst],
]);
});
it('should support references via #...', () => {
expect(humanizeTplAst(parse('<template #a>', []))).toEqual([
[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],
[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-...', () => {
expect(humanizeTplAst(parse('<template let-a="b">', [
]))).toEqual([[EmbeddedTemplateAst], [VariableAst, 'a', 'b']]);
expect(humanizeTplAst(parse('<template let-a="b">', []))).toEqual([
[EmbeddedTemplateAst],
[VariableAst, 'a', 'b'],
]);
expect(humanizeTplAst(parse('<ng-template let-a="b">', []))).toEqual([
[EmbeddedTemplateAst],
[VariableAst, 'a', 'b'],
]);
});
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();
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', () => {
it('should wrap the element into an EmbeddedTemplateAST', () => {
expect(humanizeTplAst(parse('<div template>', [
]))).toEqual([[EmbeddedTemplateAst], [ElementAst, 'div']]);
expect(humanizeTplAst(parse('<div template>', []))).toEqual([
[EmbeddedTemplateAst],
[ElementAst, 'div'],
]);
});
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', () => {
it('should project text nodes with wildcard selector', () => {
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', () => {
expect(humanizeContentProjection(parse('<div><a x></a><b></b></div>', [
createComp('div', ['a[x]'])
]))).toEqual([['div', null], ['a', 0], ['b', null]]);
expect(humanizeContentProjection(
parse('<div><a x></a><b></b></div>', [createComp('div', ['a[x]'])])))
.toEqual([
['div', null],
['a', 0],
['b', null],
]);
});
});
describe('embedded templates', () => {
it('should project embedded templates with wildcard selector', () => {
expect(humanizeContentProjection(parse('<div><template></template></div>', [
createComp('div', ['*'])
]))).toEqual([['div', null], ['template', 0]]);
expect(humanizeContentProjection(parse(
'<div><template></template><ng-template></ng-template></div>',
[createComp('div', ['*'])])))
.toEqual([
['div', null],
['template', 0],
['template', 0],
]);
});
it('should project embedded templates with css selector', () => {
expect(humanizeContentProjection(parse(
'<div><template x></template><template></template></div>',
[createComp('div', ['template[x]'])])))
.toEqual([['div', null], ['template', 0], ['template', null]]);
'<div><ng-template x></ng-template><ng-template></ng-template></div>',
[createComp('div', ['ng-template[x]'])])))
.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]]);
});
it('should override <template>', () => {
expect(humanizeContentProjection(parse(
'<div><template ngProjectAs="b"></template></div>',
[createComp('div', ['template', 'b'])])))
.toEqual([['div', null], ['template', 1]]);
it('should override <ng-template>', () => {
expect(
humanizeContentProjection(parse(
'<div><template ngProjectAs="b"></template><ng-template ngProjectAs="b"></ng-template></div>',
[createComp('div', ['template', 'b'])])))
.toEqual([
['div', null],
['template', 1],
['template', 1],
]);
});
it('should override inline templates', () => {
expect(humanizeContentProjection(parse(
'<div><a *ngIf="cond" ngProjectAs="b"></a></div>',
[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`);
});
it('should treat *attr on a template element as valid',
() => { expect(() => parse('<template *ngIf>', [])).not.toThrowError(); });
it('should treat *attr on a template element as valid', () => {
expect(() => parse('<template *ngIf>', [])).not.toThrowError();
expect(() => parse('<ng-template *ngIf>', [])).not.toThrowError();
});
it('should treat template attribute on a template element as valid',
() => { expect(() => parse('<template template="ngIf">', [])).not.toThrowError(); });
it('should treat template attribute on a template element as valid', () => {
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:
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: []})
})
.toSummary();
expect(() => parse('<template [a]="b" (e)="f"></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". ("<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
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', () => {
@ -1745,7 +1816,8 @@ Property binding a not used by any directive on an embedded template. Make sure
it('should support embedded template', () => {
expect(humanizeTplAstSourceSpans(parse('<template></template>', [
]))).toEqual([[EmbeddedTemplateAst, '<template>']]);
expect(humanizeTplAstSourceSpans(parse('<ng-template></ng-template>', [
]))).toEqual([[EmbeddedTemplateAst, '<ng-template>']]);
});
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', () => {
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', () => {
@ -1917,8 +1995,8 @@ The pipe 'test' could not be found ("{{[ERROR ->]a | test}}"): TestComp@0:2`);
it('should expand plural messages', () => {
const shortForm = '{ count, plural, =0 {small} many {big} }';
const expandedForm = '<ng-container [ngPlural]="count">' +
'<template ngPluralCase="=0">small</template>' +
'<template ngPluralCase="many">big</template>' +
'<ng-template ngPluralCase="=0">small</ng-template>' +
'<ng-template ngPluralCase="many">big</ng-template>' +
'</ng-container>';
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', () => {
const shortForm = '{ sex, select, female {foo} other {bar} }';
const expandedForm = '<ng-container [ngSwitch]="sex">' +
'<template ngSwitchCase="female">foo</template>' +
'<template ngSwitchDefault>bar</template>' +
'<ng-template ngSwitchCase="female">foo</ng-template>' +
'<ng-template ngSwitchDefault>bar</ng-template>' +
'</ng-container>';
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[] {
const humanizer = new TemplateContentProjectionHumanizer();
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.
*
* You can access a `TemplateRef`, in two ways. Via a directive placed on a `<template>` element (or
* directive prefixed with `*`) and have the `TemplateRef` for this Embedded View injected into the
* constructor of the directive using the `TemplateRef` Token. Alternatively you can query for the
* `TemplateRef` from a Component or a Directive via {@link Query}.
* You can access a `TemplateRef`, in two ways. Via a directive placed on a `<ng-template>` element
* (or directive prefixed with `*`) and have the `TemplateRef` for this Embedded View injected into
* the constructor of the directive using the `TemplateRef` Token. Alternatively you can query for
* the `TemplateRef` from a Component or a Directive via {@link Query}.
*
* To instantiate Embedded Views based on a Template, use
* {@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}}
* <ul>
* <template ngFor let-item [ngForOf]="items"></template>
* <ng-template ngFor let-item [ngForOf]="items"></ng-template>
* </ul>
* ```
*
@ -74,7 +74,7 @@ export abstract class ViewRef extends ChangeDetectorRef {
* <!-- ViewRef: outer-0 -->
* Count: 2
* <ul>
* <template view-container-ref></template>
* <ng-template view-container-ref></ng-template>
* <!-- ViewRef: inner-1 --><li>first</li><!-- /ViewRef: inner-1 -->
* <!-- ViewRef: inner-2 --><li>second</li><!-- /ViewRef: inner-2 -->
* </ul>

View File

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

View File

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

View File

@ -485,8 +485,8 @@ function createTests({viewEngine}: {viewEngine: boolean}) {
fakeAsync(() => { expect(_bindAndCheckSimpleValue('"$"')).toEqual(['someProp=$']); }));
it('should read locals', fakeAsync(() => {
const ctx =
createCompFixture('<template testLocals let-local="someLocal">{{local}}</template>');
const ctx = createCompFixture(
'<ng-template testLocals let-local="someLocal">{{local}}</ng-template>');
ctx.detectChanges(false);
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',
() => {
@Component({template: '<template #vc>{{name}}</template>'})
@Component({template: '<ng-template #vc>{{name}}</ng-template>'})
class Comp {
name = 'Tom';
@ViewChild('vc', {read: ViewContainerRef}) vc: ViewContainerRef;

View File

@ -130,7 +130,7 @@ export function main() {
it('should support using structural directives with ngTemplateOutlet', () => {
@Component({
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 {
templateCtx = {shown: false};

View File

@ -363,10 +363,10 @@ function declareTests({useJit, viewEngine}: {useJit: boolean, viewEngine: boolea
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]});
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}});
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', () => {
TestBed.configureTestingModule({declarations: [MyComp, PollutedContext, NoContext]});
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}});
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.', () => {
TestBed.configureTestingModule({declarations: [MyComp, SomeViewport]});
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}});
const fixture = TestBed.createComponent(MyComp);
@ -412,11 +412,11 @@ function declareTests({useJit, viewEngine}: {useJit: boolean, viewEngine: boolea
expect(fixture.debugElement.children.length).toBe(0);
});
it('should use a comment while stamping out `<template>` elements.', () => {
TestBed.configureTestingModule({declarations: [MyComp]});
const template = '<template></template>';
TestBed.overrideComponent(MyComp, {set: {template}});
const fixture = TestBed.createComponent(MyComp);
it('should use a comment while stamping out `<ng-template>` elements.', () => {
const fixture =
TestBed.configureTestingModule({declarations: [MyComp]})
.overrideComponent(MyComp, {set: {template: '<ng-template></ng-template>'}})
.createComponent(MyComp);
const childNodesOfWrapper = getDOM().childNodes(fixture.nativeElement);
expect(childNodesOfWrapper.length).toBe(1);
@ -448,7 +448,7 @@ function declareTests({useJit, viewEngine}: {useJit: boolean, viewEngine: boolea
schemas: [NO_ERRORS_SCHEMA],
});
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}});
const fixture = TestBed.createComponent(MyComp);
@ -484,7 +484,7 @@ function declareTests({useJit, viewEngine}: {useJit: boolean, viewEngine: boolea
() => {
TestBed.configureTestingModule({declarations: [MyComp, ChildComp]});
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}});
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', () => {
TestBed.configureTestingModule({declarations: [MyComp]});
const template = '<template ref-alice></template>';
TestBed.overrideComponent(MyComp, {set: {template}});
const fixture = TestBed.createComponent(MyComp);
const fixture =
TestBed.configureTestingModule({declarations: [MyComp]})
.overrideComponent(MyComp, {set: {template: '<template ref-alice></template>'}})
.createComponent(MyComp);
const value = fixture.debugElement.childNodes[0].references['alice'];
expect(value.createEmbeddedView).toBeTruthy();
@ -552,14 +552,16 @@ function declareTests({useJit, viewEngine}: {useJit: boolean, viewEngine: boolea
describe('variables', () => {
it('should allow to use variables in a for loop', () => {
TestBed.configureTestingModule({declarations: [MyComp, ChildCompNoTemplate]});
const template =
'<template ngFor [ngForOf]="[1]" let-i><child-cmp-no-template #cmp></child-cmp-no-template>{{i}}-{{cmp.ctxProp}}</template>';
TestBed.overrideComponent(MyComp, {set: {template}});
const fixture = TestBed.createComponent(MyComp);
'<ng-template ngFor [ngForOf]="[1]" let-i><child-cmp-no-template #cmp></child-cmp-no-template>{{i}}-{{cmp.ctxProp}}</ng-template>';
const fixture =
TestBed.configureTestingModule({declarations: [MyComp, ChildCompNoTemplate]})
.overrideComponent(MyComp, {set: {template}})
.createComponent(MyComp);
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');
});
});
@ -774,11 +776,17 @@ function declareTests({useJit, viewEngine}: {useJit: boolean, viewEngine: boolea
}));
it('should support events via EventEmitter on template elements', async(() => {
TestBed.configureTestingModule(
{declarations: [MyComp, DirectiveEmittingEvent, DirectiveListeningEvent]});
const template = '<template emitter listener (event)="ctxProp=$event"></template>';
TestBed.overrideComponent(MyComp, {set: {template}});
const fixture = TestBed.createComponent(MyComp);
const fixture =
TestBed
.configureTestingModule(
{declarations: [MyComp, DirectiveEmittingEvent, DirectiveListeningEvent]})
.overrideComponent(MyComp, {
set: {
template:
'<ng-template emitter listener (event)="ctxProp=$event"></ng-template>'
}
})
.createComponent(MyComp);
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', () => {
TestBed.configureTestingModule({declarations: [MyComp]});
const template = '<template [ngIf]="ctxBoolProp"></template>';
TestBed.overrideComponent(MyComp, {set: {template}});
const fixture = TestBed.createComponent(MyComp);
const fixture =
TestBed.configureTestingModule({declarations: [MyComp]})
.overrideComponent(
MyComp, {set: {template: '<ng-template [ngIf]="ctxBoolProp"></ng-template>'}})
.createComponent(MyComp);
fixture.componentInstance.ctxBoolProp = true;
fixture.detectChanges();

View File

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

View File

@ -225,7 +225,7 @@ function createTests({viewEngine}: {viewEngine: boolean}) {
describe('query for TemplateRef', () => {
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 needsTpl: NeedsTpl = view.debugElement.children[0].injector.get(NeedsTpl);
@ -237,7 +237,7 @@ function createTests({viewEngine}: {viewEngine: boolean}) {
it('should find named TemplateRefs', () => {
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 needsTpl: NeedsNamedTpl = view.debugElement.children[0].injector.get(NeedsNamedTpl);
expect(needsTpl.vc.createEmbeddedView(needsTpl.contentTpl).rootNodes[0])
@ -308,7 +308,7 @@ function createTests({viewEngine}: {viewEngine: boolean}) {
it('should support reading a ViewContainer', () => {
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 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', () => {
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>' +
'</needs-query-by-ref-binding>';
@ -536,7 +536,7 @@ function createTests({viewEngine}: {viewEngine: boolean}) {
describe('query over moved templates', () => {
it('should include manually projected templates in queries', () => {
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 q = view.debugElement.children[0].references['q'];
expect(q.query.length).toBe(0);
@ -730,14 +730,15 @@ class NeedsViewQueryOrderWithParent {
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 {
@ViewChildren(TemplateRef) viewQuery: QueryList<TemplateRef<Object>>;
@ContentChildren(TemplateRef) query: QueryList<TemplateRef<Object>>;
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 {
@ViewChild('tpl') viewTpl: TemplateRef<Object>;
@ContentChild('tpl') contentTpl: TemplateRef<Object>;
@ -767,7 +768,7 @@ class NeedsContentChildTemplateRef {
@Component({
selector: 'needs-content-child-template-ref-app',
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>'
})
class NeedsContentChildTemplateRefApp {

View File

@ -656,7 +656,8 @@ function createTests({viewEngine}: {viewEngine: boolean}) {
it('should inject TemplateRef', () => {
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)
.toEqual(el.childNodes[0].injector.get(NeedsViewContainerRef).viewContainer.element);
});

View File

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

View File

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

View File

@ -15,9 +15,9 @@ import {TableCell, emptyTable} from '../util';
selector: 'largetable',
template: `<table><tbody>
<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>
</ng-container></template>
</ng-container></ng-template>
</tr>
</tbody></table>`
})