refactor(compiler): Drop support for the deprecated <template>. Use <ng-template> instead (#22783)

BREAKING CHANGE:

The `<template>` tag was deprecated in Angular v4 to avoid collisions (i.e. when
using Web Components).

This commit removes support for `<template>`. `<ng-template>` should be used
instead.

BEFORE:

    <!-- html template -->
    <template>some template content</template>

    # tsconfig.json
    {
      # ...
      "angularCompilerOptions": {
        # ...
        # This option is no more supported and will have no effect
        "enableLegacyTemplate": [true|false]
      }
    }

AFTER:

    <!-- html template -->
    <ng-template>some template content</ng-template>

PR Close #22783
This commit is contained in:
Victor Berchet
2018-03-15 14:52:22 -07:00
committed by Miško Hevery
parent 4e6ac185e5
commit 0ebd577db4
20 changed files with 60 additions and 265 deletions
-7
View File
@@ -215,13 +215,6 @@ value | description
This tells the compiler to print extra information while compiling templates.
### *enableLegacyTemplate*
The use of `<template>` element was deprecated starting in Angular 4.0 in favor of using
`<ng-template>` to avoid colliding with the DOM's element of the same name. Setting this option to
`true` enables the use of the deprecated `<template>` element . This option
is `false` by default. This option might be required by some third-party Angular libraries.
### *disableExpressionLowering*
The Angular template compiler transforms code that is used, or could be used, in an annotation
@@ -25,9 +25,9 @@ import {ScrollAreaComponent} from './scroll_area';
<div style="display: flex">
<scroll-area id="testArea"></scroll-area>
</div>
<div template="ngIf scrollAreas.length > 0">
<div *ngIf="scrollAreas.length > 0">
<p>Following tables are only here to add weight to the UI:</p>
<scroll-area template="ngFor let scrollArea of scrollAreas"></scroll-area>
<scroll-area *ngFor="let scrollArea of scrollAreas"></scroll-area>
</div>
</div>`
})
@@ -62,7 +62,7 @@ export class Stage {
directives: [NgFor],
template: `
<div [style.width.px]="cellWidth">
<button template="ngFor let stage of stages"
<button *ngFor="let stage of stages"
[disabled]="stage.isDisabled"
[style.background-color]="stage.backgroundColor"
on-click="setStage(stage)">
@@ -25,7 +25,7 @@ import {ScrollItemComponent} from './scroll_item';
<div id="padding"></div>
<div id="inner">
<scroll-item
template="ngFor let item of visibleItems"
*ngFor="let item of visibleItems"
[offering]="item">
</scroll-item>
</div>
@@ -50,7 +50,6 @@ export interface CompilerOptions extends ts.CompilerOptions {
annotateForClosureCompiler?: boolean;
annotationsAs?: 'decorators'|'static fields';
trace?: boolean;
enableLegacyTemplate?: boolean;
disableExpressionLowering?: boolean;
i18nOutLocale?: string;
i18nOutFormat?: string;
@@ -125,9 +125,6 @@ export interface CompilerOptions extends ts.CompilerOptions {
// Print extra information while running the compiler
trace?: boolean;
// Whether to enable support for <template> and the template attribute (false by default)
enableLegacyTemplate?: boolean;
// Whether to enable lowering expressions lambdas and expressions in a reference value
// position.
disableExpressionLowering?: boolean;
@@ -828,7 +828,6 @@ function getAotCompilerOptions(options: CompilerOptions): AotCompilerOptions {
return {
locale: options.i18nInLocale,
i18nFormat: options.i18nInFormat || options.i18nOutFormat, translations, missingTranslation,
enableLegacyTemplate: options.enableLegacyTemplate,
enableSummariesForJit: options.enableSummariesForJit,
preserveWhitespaces: options.preserveWhitespaces,
fullTemplateTypeCheck: options.fullTemplateTypeCheck,
@@ -536,7 +536,6 @@ The recommended options for producing a ivy application are
| `"renderer2BackPatching"` | `true` | implied |
| `"generateCodeForLibraries"` | `true` | default |
| `"annotationsAs"` | `remove` | implied |
| `"enableLegacyTemplate"` | `false` | default |
| `"preserveWhitespaces"` | `false` | default |
| `"skipMetadataEmit"` | `true` | default |
| `"strictMetadataEmit"` | `false` | implied |
@@ -574,7 +573,6 @@ The recommended options for producing a ivy library are:
| `"renderer2BackPatching"` | `false` | default |
| `"generateCodeForLibraries"` | `false` | |
| `"annotationsAs"` | `remove` | implied |
| `"enableLegacyTemplate"` | `false` | default |
| `"preserveWhitespaces"` | `false` | default |
| `"skipMetadataEmit"` | `false` | |
| `"strictMetadataEmit"` | `true ` | |
@@ -598,25 +596,21 @@ depending on the target:
| | `"renderer2BackPatching"` | `true` | enforced |
| | `"generateCodeForLibraries"` | `true` | |
| | `"annotationsAs"` | `remove` | |
| | `"enableLegacyTemplate"` | `false` | |
| | `"preserveWhitespaces"` | `false` | |
| | `"skipMetadataEmit"` | `false` | |
| | `"strictMetadataEmit"` | `true` | |
| | `"skipTemplateCodegen"` | `false` | |
| | `"fullTemplateTypeCheck"` | `true` | |
| | `"enableLegacyTemplate"` | `false` | |
| | | | |
| `"library"` | `"generateRenderer2Factories"` | `false` | enforced |
| | `"renderer2BackPatching"` | `false` | enforced |
| | `"generateCodeForLibraries"` | `false` | enforced |
| | `"annotationsAs"` | `decorators` | |
| | `"enableLegacyTemplate"` | `false` | |
| | `"preserveWhitespaces"` | `false` | |
| | `"skipMetadataEmit"` | `false` | enforced |
| | `"strictMetadataEmit"` | `true` | |
| | `"skipTemplateCodegen"` | `false` | enforced |
| | `"fullTemplateTypeCheck"` | `true` | |
| | `"enableLegacyTemplate"` | `false` | |
| | | | |
| `"package"` | `"flatModuleOutFile"` | | required |
| | `"flatModuleId"` | | required |
@@ -625,13 +619,11 @@ depending on the target:
| | `"renderer2BackPatching"` | `false` | enforced |
| | `"generateCodeForLibraries"` | `false` | enforced |
| | `"annotationsAs"` | `remove` | |
| | `"enableLegacyTemplate"` | `false` | |
| | `"preserveWhitespaces"` | `false` | |
| | `"skipMetadataEmit"` | `false` | enforced |
| | `"strictMetadataEmit"` | `true` | |
| | `"skipTemplateCodegen"` | `false` | enforced |
| | `"fullTemplateTypeCheck"` | `true` | |
| | `"enableLegacyTemplate"` | `false` | |
Options that are marked "enforced" are reported as an error if they are
explicitly set to a value different from what is specified here. The options
@@ -70,7 +70,6 @@ export function createAotCompiler(
const config = new CompilerConfig({
defaultEncapsulation: ViewEncapsulation.Emulated,
useJit: false,
enableLegacyTemplate: options.enableLegacyTemplate === true,
missingTranslation: options.missingTranslation,
preserveWhitespaces: options.preserveWhitespaces,
strictInjectionParameters: options.strictInjectionParameters,
@@ -13,7 +13,6 @@ export interface AotCompilerOptions {
i18nFormat?: string;
translations?: string;
missingTranslation?: MissingTranslationStrategy;
enableLegacyTemplate?: boolean;
enableSummariesForJit?: boolean;
preserveWhitespaces?: boolean;
fullTemplateTypeCheck?: boolean;
+1 -7
View File
@@ -14,9 +14,6 @@ import {noUndefined} from './util';
export class CompilerConfig {
public defaultEncapsulation: ViewEncapsulation|null;
// Whether to support the `<template>` tag and the `template` attribute to define angular
// templates. They have been deprecated in 4.x, `<ng-template>` should be used instead.
public enableLegacyTemplate: boolean;
public useJit: boolean;
public jitDevMode: boolean;
public missingTranslation: MissingTranslationStrategy|null;
@@ -25,13 +22,11 @@ export class CompilerConfig {
constructor(
{defaultEncapsulation = ViewEncapsulation.Emulated, useJit = true, jitDevMode = false,
missingTranslation = null, enableLegacyTemplate, preserveWhitespaces,
strictInjectionParameters}: {
missingTranslation = null, preserveWhitespaces, strictInjectionParameters}: {
defaultEncapsulation?: ViewEncapsulation,
useJit?: boolean,
jitDevMode?: boolean,
missingTranslation?: MissingTranslationStrategy,
enableLegacyTemplate?: boolean,
preserveWhitespaces?: boolean,
strictInjectionParameters?: boolean,
} = {}) {
@@ -39,7 +34,6 @@ export class CompilerConfig {
this.useJit = !!useJit;
this.jitDevMode = !!jitDevMode;
this.missingTranslation = missingTranslation;
this.enableLegacyTemplate = enableLegacyTemplate === true;
this.preserveWhitespaces = preserveWhitespacesDefault(noUndefined(preserveWhitespaces));
this.strictInjectionParameters = strictInjectionParameters === true;
}
-1
View File
@@ -71,7 +71,6 @@ export function mergeNsAndName(prefix: string, localName: string): string {
// This list is not exhaustive to keep the compiler footprint low.
// The `&#123;` / `&#x1ab;` syntax should be used when the named character reference does not
// exist.
export const NAMED_ENTITIES: {[k: string]: string} = {
'Aacute': '\u00C1',
'aacute': '\u00E1',
@@ -55,20 +55,11 @@ const IDENT_PROPERTY_IDX = 9;
// Group 10 = identifier inside ()
const IDENT_EVENT_IDX = 10;
// deprecated in 4.x
const TEMPLATE_ELEMENT = 'template';
// deprecated in 4.x
const TEMPLATE_ATTR = 'template';
const TEMPLATE_ATTR_PREFIX = '*';
const CLASS_ATTR = 'class';
const TEXT_CSS_SELECTOR = CssSelector.parse('*')[0];
const TEMPLATE_ELEMENT_DEPRECATION_WARNING =
'The <template> element is deprecated. Use <ng-template> instead';
const TEMPLATE_ATTR_DEPRECATION_WARNING =
'The template attribute is deprecated. Use an ng-template element instead.';
let warningCounts: {[warning: string]: number} = {};
function warnOnlyOnce(warnings: string[]): (warning: ParseError) => boolean {
@@ -109,10 +100,7 @@ export class TemplateParser {
preserveWhitespaces: boolean): {template: TemplateAst[], pipes: CompilePipeSummary[]} {
const result = this.tryParse(
component, template, directives, pipes, schemas, templateUrl, preserveWhitespaces);
const warnings =
result.errors !.filter(error => error.level === ParseErrorLevel.WARNING)
.filter(warnOnlyOnce(
[TEMPLATE_ATTR_DEPRECATION_WARNING, TEMPLATE_ELEMENT_DEPRECATION_WARNING]));
const warnings = result.errors !.filter(error => error.level === ParseErrorLevel.WARNING);
const errors = result.errors !.filter(error => error.level === ParseErrorLevel.ERROR);
@@ -295,9 +283,7 @@ class TemplateParseVisitor implements html.Visitor {
let hasInlineTemplates = false;
const attrs: AttrAst[] = [];
const isTemplateElement = isTemplate(
element, this.config.enableLegacyTemplate,
(m: string, span: ParseSourceSpan) => this._reportError(m, span, ParseErrorLevel.WARNING));
const isTemplateElement = isNgTemplate(element.name);
element.attrs.forEach(attr => {
const hasBinding = this._parseAttr(
@@ -306,13 +292,9 @@ class TemplateParseVisitor implements html.Visitor {
let templateBindingsSource: string|undefined;
let prefixToken: string|undefined;
let normalizedName = this._normalizeAttributeName(attr.name);
const normalizedName = this._normalizeAttributeName(attr.name);
if (this.config.enableLegacyTemplate && normalizedName == TEMPLATE_ATTR) {
this._reportError(
TEMPLATE_ATTR_DEPRECATION_WARNING, attr.sourceSpan, ParseErrorLevel.WARNING);
templateBindingsSource = attr.value;
} else if (normalizedName.startsWith(TEMPLATE_ATTR_PREFIX)) {
if (normalizedName.startsWith(TEMPLATE_ATTR_PREFIX)) {
templateBindingsSource = attr.value;
prefixToken = normalizedName.substring(TEMPLATE_ATTR_PREFIX.length) + ':';
}
@@ -321,7 +303,7 @@ class TemplateParseVisitor implements html.Visitor {
if (hasTemplateBinding) {
if (hasInlineTemplates) {
this._reportError(
`Can't have multiple template bindings on one element. Use only one attribute named 'template' or prefixed with *`,
`Can't have multiple template bindings on one element. Use only one attribute prefixed with *`,
attr.sourceSpan);
}
hasInlineTemplates = true;
@@ -400,7 +382,7 @@ class TemplateParseVisitor implements html.Visitor {
if (hasInlineTemplates) {
const templateQueryStartIndex = this.contentQueryStartId;
const templateSelector = createElementCssSelector(TEMPLATE_ELEMENT, templateMatchableAttrs);
const templateSelector = createElementCssSelector('ng-template', templateMatchableAttrs);
const {directives: templateDirectiveMetas} =
this._parseDirectives(this.selectorMatcher, templateSelector);
const templateBoundDirectivePropNames = new Set<string>();
@@ -908,20 +890,4 @@ function isEmptyExpression(ast: AST): boolean {
ast = ast.ast;
}
return ast instanceof EmptyExpr;
}
// `template` is deprecated in 4.x
function isTemplate(
el: html.Element, enableLegacyTemplate: boolean,
reportDeprecation: (m: string, span: ParseSourceSpan) => void): boolean {
if (isNgTemplate(el.name)) return true;
const tagNoNs = splitNsName(el.name)[1];
// `<template>` is HTML and case insensitive
if (tagNoNs.toLowerCase() === TEMPLATE_ELEMENT) {
if (enableLegacyTemplate && tagNoNs.toLowerCase() === TEMPLATE_ELEMENT) {
reportDeprecation(TEMPLATE_ELEMENT_DEPRECATION_WARNING, el.sourceSpan !);
return true;
}
}
return false;
}
}
@@ -32,10 +32,6 @@ import {humanizeDom, humanizeDomSourceSpans, humanizeLineColumn} from './ast_spe
});
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]
]);
@@ -62,8 +58,6 @@ import {humanizeDom, humanizeDomSourceSpans, humanizeLineColumn} from './ast_spe
});
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]]);
});
@@ -175,10 +169,6 @@ import {humanizeDom, humanizeDomSourceSpans, humanizeLineColumn} from './ast_spe
});
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],
@@ -282,10 +272,6 @@ import {humanizeDom, humanizeDomSourceSpans, humanizeLineColumn} from './ast_spe
});
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],
@@ -158,7 +158,6 @@ function doCompile(
const config = new CompilerConfig({
defaultEncapsulation: ViewEncapsulation.Emulated,
useJit: false,
enableLegacyTemplate: options.enableLegacyTemplate === true,
missingTranslation: options.missingTranslation,
preserveWhitespaces: options.preserveWhitespaces,
strictInjectionParameters: options.strictInjectionParameters,
File diff suppressed because it is too large Load Diff
-3
View File
@@ -90,9 +90,6 @@ export type CompilerOptions = {
defaultEncapsulation?: ViewEncapsulation,
providers?: StaticProvider[],
missingTranslation?: MissingTranslationStrategy,
// Whether to support the `<template>` tag and the `template` attribute to define angular
// templates. They have been deprecated in 4.x, `<ng-template>` should be used instead.
enableLegacyTemplate?: boolean,
preserveWhitespaces?: boolean,
};
+1 -24
View File
@@ -38,14 +38,7 @@ const ANCHOR_ELEMENT = new InjectionToken('AnchorElement');
function declareTests({useJit}: {useJit: boolean}) {
describe('integration tests', function() {
beforeEach(() => {
TestBed.configureCompiler({
useJit,
providers: [
{provide: CompilerConfig, useValue: new CompilerConfig({enableLegacyTemplate: true})}
]
});
});
beforeEach(() => { TestBed.configureCompiler({useJit}); });
describe('react to record changes', function() {
it('should consume text node changes', () => {
@@ -420,22 +413,6 @@ function declareTests({useJit}: {useJit: boolean}) {
expect(getDOM().isCommentNode(childNodesOfWrapper[0])).toBe(true);
});
it('should support template directives via `template` attribute.', () => {
TestBed.configureTestingModule({declarations: [MyComp, SomeViewport]});
const template =
'<span template="some-viewport: let greeting=someTmpl">{{greeting}}</span>';
TestBed.overrideComponent(MyComp, {set: {template}});
const fixture = TestBed.createComponent(MyComp);
fixture.detectChanges();
const childNodesOfWrapper = getDOM().childNodes(fixture.nativeElement);
// 1 template + 2 copies.
expect(childNodesOfWrapper.length).toBe(3);
expect(childNodesOfWrapper[1]).toHaveText('hello');
expect(childNodesOfWrapper[2]).toHaveText('again');
});
it('should allow to transplant TemplateRefs into other ViewContainers', () => {
TestBed.configureTestingModule({
declarations: [
@@ -162,7 +162,6 @@ export class JitCompilerFactory implements CompilerFactory {
useJit: true,
defaultEncapsulation: ViewEncapsulation.Emulated,
missingTranslation: MissingTranslationStrategy.Warning,
enableLegacyTemplate: false,
};
this._defaultOptions = [compilerOptions, ...defaultOptions];
@@ -182,7 +181,6 @@ export class JitCompilerFactory implements CompilerFactory {
// from the app providers
defaultEncapsulation: opts.defaultEncapsulation,
missingTranslation: opts.missingTranslation,
enableLegacyTemplate: opts.enableLegacyTemplate,
preserveWhitespaces: opts.preserveWhitespaces,
});
},
@@ -200,7 +198,6 @@ function _mergeOptions(optionsArr: CompilerOptions[]): CompilerOptions {
defaultEncapsulation: _lastDefined(optionsArr.map(options => options.defaultEncapsulation)),
providers: _mergeArrays(optionsArr.map(options => options.providers !)),
missingTranslation: _lastDefined(optionsArr.map(options => options.missingTranslation)),
enableLegacyTemplate: _lastDefined(optionsArr.map(options => options.enableLegacyTemplate)),
preserveWhitespaces: _lastDefined(optionsArr.map(options => options.preserveWhitespaces)),
};
}
-1
View File
@@ -112,7 +112,6 @@ export declare type CompilerOptions = {
defaultEncapsulation?: ViewEncapsulation;
providers?: StaticProvider[];
missingTranslation?: MissingTranslationStrategy;
enableLegacyTemplate?: boolean;
preserveWhitespaces?: boolean;
};