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

@ -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[] = [];