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:

committed by
Igor Minar

parent
3f519207a4
commit
bf8eb41248
@ -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);
|
||||
|
Reference in New Issue
Block a user