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
@ -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));
|
||||
|
@ -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 {
|
||||
|
@ -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);
|
||||
|
@ -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(
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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[] = [];
|
||||
|
||||
|
Reference in New Issue
Block a user