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-14 17:27:38 -07:00 committed by Miško Hevery
parent 4e6ac185e5
commit 0ebd577db4
20 changed files with 60 additions and 265 deletions

View File

@ -215,13 +215,6 @@ value | description
This tells the compiler to print extra information while compiling templates. 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* ### *disableExpressionLowering*
The Angular template compiler transforms code that is used, or could be used, in an annotation The Angular template compiler transforms code that is used, or could be used, in an annotation

View File

@ -25,9 +25,9 @@ import {ScrollAreaComponent} from './scroll_area';
<div style="display: flex"> <div style="display: flex">
<scroll-area id="testArea"></scroll-area> <scroll-area id="testArea"></scroll-area>
</div> </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> <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>
</div>` </div>`
}) })

View File

@ -62,7 +62,7 @@ export class Stage {
directives: [NgFor], directives: [NgFor],
template: ` template: `
<div [style.width.px]="cellWidth"> <div [style.width.px]="cellWidth">
<button template="ngFor let stage of stages" <button *ngFor="let stage of stages"
[disabled]="stage.isDisabled" [disabled]="stage.isDisabled"
[style.background-color]="stage.backgroundColor" [style.background-color]="stage.backgroundColor"
on-click="setStage(stage)"> on-click="setStage(stage)">

View File

@ -25,7 +25,7 @@ import {ScrollItemComponent} from './scroll_item';
<div id="padding"></div> <div id="padding"></div>
<div id="inner"> <div id="inner">
<scroll-item <scroll-item
template="ngFor let item of visibleItems" *ngFor="let item of visibleItems"
[offering]="item"> [offering]="item">
</scroll-item> </scroll-item>
</div> </div>

View File

@ -50,7 +50,6 @@ export interface CompilerOptions extends ts.CompilerOptions {
annotateForClosureCompiler?: boolean; annotateForClosureCompiler?: boolean;
annotationsAs?: 'decorators'|'static fields'; annotationsAs?: 'decorators'|'static fields';
trace?: boolean; trace?: boolean;
enableLegacyTemplate?: boolean;
disableExpressionLowering?: boolean; disableExpressionLowering?: boolean;
i18nOutLocale?: string; i18nOutLocale?: string;
i18nOutFormat?: string; i18nOutFormat?: string;

View File

@ -125,9 +125,6 @@ export interface CompilerOptions extends ts.CompilerOptions {
// Print extra information while running the compiler // Print extra information while running the compiler
trace?: boolean; 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 // Whether to enable lowering expressions lambdas and expressions in a reference value
// position. // position.
disableExpressionLowering?: boolean; disableExpressionLowering?: boolean;

View File

@ -828,7 +828,6 @@ function getAotCompilerOptions(options: CompilerOptions): AotCompilerOptions {
return { return {
locale: options.i18nInLocale, locale: options.i18nInLocale,
i18nFormat: options.i18nInFormat || options.i18nOutFormat, translations, missingTranslation, i18nFormat: options.i18nInFormat || options.i18nOutFormat, translations, missingTranslation,
enableLegacyTemplate: options.enableLegacyTemplate,
enableSummariesForJit: options.enableSummariesForJit, enableSummariesForJit: options.enableSummariesForJit,
preserveWhitespaces: options.preserveWhitespaces, preserveWhitespaces: options.preserveWhitespaces,
fullTemplateTypeCheck: options.fullTemplateTypeCheck, fullTemplateTypeCheck: options.fullTemplateTypeCheck,

View File

@ -536,7 +536,6 @@ The recommended options for producing a ivy application are
| `"renderer2BackPatching"` | `true` | implied | | `"renderer2BackPatching"` | `true` | implied |
| `"generateCodeForLibraries"` | `true` | default | | `"generateCodeForLibraries"` | `true` | default |
| `"annotationsAs"` | `remove` | implied | | `"annotationsAs"` | `remove` | implied |
| `"enableLegacyTemplate"` | `false` | default |
| `"preserveWhitespaces"` | `false` | default | | `"preserveWhitespaces"` | `false` | default |
| `"skipMetadataEmit"` | `true` | default | | `"skipMetadataEmit"` | `true` | default |
| `"strictMetadataEmit"` | `false` | implied | | `"strictMetadataEmit"` | `false` | implied |
@ -574,7 +573,6 @@ The recommended options for producing a ivy library are:
| `"renderer2BackPatching"` | `false` | default | | `"renderer2BackPatching"` | `false` | default |
| `"generateCodeForLibraries"` | `false` | | | `"generateCodeForLibraries"` | `false` | |
| `"annotationsAs"` | `remove` | implied | | `"annotationsAs"` | `remove` | implied |
| `"enableLegacyTemplate"` | `false` | default |
| `"preserveWhitespaces"` | `false` | default | | `"preserveWhitespaces"` | `false` | default |
| `"skipMetadataEmit"` | `false` | | | `"skipMetadataEmit"` | `false` | |
| `"strictMetadataEmit"` | `true ` | | | `"strictMetadataEmit"` | `true ` | |
@ -598,25 +596,21 @@ depending on the target:
| | `"renderer2BackPatching"` | `true` | enforced | | | `"renderer2BackPatching"` | `true` | enforced |
| | `"generateCodeForLibraries"` | `true` | | | | `"generateCodeForLibraries"` | `true` | |
| | `"annotationsAs"` | `remove` | | | | `"annotationsAs"` | `remove` | |
| | `"enableLegacyTemplate"` | `false` | |
| | `"preserveWhitespaces"` | `false` | | | | `"preserveWhitespaces"` | `false` | |
| | `"skipMetadataEmit"` | `false` | | | | `"skipMetadataEmit"` | `false` | |
| | `"strictMetadataEmit"` | `true` | | | | `"strictMetadataEmit"` | `true` | |
| | `"skipTemplateCodegen"` | `false` | | | | `"skipTemplateCodegen"` | `false` | |
| | `"fullTemplateTypeCheck"` | `true` | | | | `"fullTemplateTypeCheck"` | `true` | |
| | `"enableLegacyTemplate"` | `false` | |
| | | | | | | | | |
| `"library"` | `"generateRenderer2Factories"` | `false` | enforced | | `"library"` | `"generateRenderer2Factories"` | `false` | enforced |
| | `"renderer2BackPatching"` | `false` | enforced | | | `"renderer2BackPatching"` | `false` | enforced |
| | `"generateCodeForLibraries"` | `false` | enforced | | | `"generateCodeForLibraries"` | `false` | enforced |
| | `"annotationsAs"` | `decorators` | | | | `"annotationsAs"` | `decorators` | |
| | `"enableLegacyTemplate"` | `false` | |
| | `"preserveWhitespaces"` | `false` | | | | `"preserveWhitespaces"` | `false` | |
| | `"skipMetadataEmit"` | `false` | enforced | | | `"skipMetadataEmit"` | `false` | enforced |
| | `"strictMetadataEmit"` | `true` | | | | `"strictMetadataEmit"` | `true` | |
| | `"skipTemplateCodegen"` | `false` | enforced | | | `"skipTemplateCodegen"` | `false` | enforced |
| | `"fullTemplateTypeCheck"` | `true` | | | | `"fullTemplateTypeCheck"` | `true` | |
| | `"enableLegacyTemplate"` | `false` | |
| | | | | | | | | |
| `"package"` | `"flatModuleOutFile"` | | required | | `"package"` | `"flatModuleOutFile"` | | required |
| | `"flatModuleId"` | | required | | | `"flatModuleId"` | | required |
@ -625,13 +619,11 @@ depending on the target:
| | `"renderer2BackPatching"` | `false` | enforced | | | `"renderer2BackPatching"` | `false` | enforced |
| | `"generateCodeForLibraries"` | `false` | enforced | | | `"generateCodeForLibraries"` | `false` | enforced |
| | `"annotationsAs"` | `remove` | | | | `"annotationsAs"` | `remove` | |
| | `"enableLegacyTemplate"` | `false` | |
| | `"preserveWhitespaces"` | `false` | | | | `"preserveWhitespaces"` | `false` | |
| | `"skipMetadataEmit"` | `false` | enforced | | | `"skipMetadataEmit"` | `false` | enforced |
| | `"strictMetadataEmit"` | `true` | | | | `"strictMetadataEmit"` | `true` | |
| | `"skipTemplateCodegen"` | `false` | enforced | | | `"skipTemplateCodegen"` | `false` | enforced |
| | `"fullTemplateTypeCheck"` | `true` | | | | `"fullTemplateTypeCheck"` | `true` | |
| | `"enableLegacyTemplate"` | `false` | |
Options that are marked "enforced" are reported as an error if they are 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 explicitly set to a value different from what is specified here. The options

View File

@ -70,7 +70,6 @@ export function createAotCompiler(
const config = new CompilerConfig({ const config = new CompilerConfig({
defaultEncapsulation: ViewEncapsulation.Emulated, defaultEncapsulation: ViewEncapsulation.Emulated,
useJit: false, useJit: false,
enableLegacyTemplate: options.enableLegacyTemplate === true,
missingTranslation: options.missingTranslation, missingTranslation: options.missingTranslation,
preserveWhitespaces: options.preserveWhitespaces, preserveWhitespaces: options.preserveWhitespaces,
strictInjectionParameters: options.strictInjectionParameters, strictInjectionParameters: options.strictInjectionParameters,

View File

@ -13,7 +13,6 @@ export interface AotCompilerOptions {
i18nFormat?: string; i18nFormat?: string;
translations?: string; translations?: string;
missingTranslation?: MissingTranslationStrategy; missingTranslation?: MissingTranslationStrategy;
enableLegacyTemplate?: boolean;
enableSummariesForJit?: boolean; enableSummariesForJit?: boolean;
preserveWhitespaces?: boolean; preserveWhitespaces?: boolean;
fullTemplateTypeCheck?: boolean; fullTemplateTypeCheck?: boolean;

View File

@ -14,9 +14,6 @@ import {noUndefined} from './util';
export class CompilerConfig { export class CompilerConfig {
public defaultEncapsulation: ViewEncapsulation|null; 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 useJit: boolean;
public jitDevMode: boolean; public jitDevMode: boolean;
public missingTranslation: MissingTranslationStrategy|null; public missingTranslation: MissingTranslationStrategy|null;
@ -25,13 +22,11 @@ export class CompilerConfig {
constructor( constructor(
{defaultEncapsulation = ViewEncapsulation.Emulated, useJit = true, jitDevMode = false, {defaultEncapsulation = ViewEncapsulation.Emulated, useJit = true, jitDevMode = false,
missingTranslation = null, enableLegacyTemplate, preserveWhitespaces, missingTranslation = null, preserveWhitespaces, strictInjectionParameters}: {
strictInjectionParameters}: {
defaultEncapsulation?: ViewEncapsulation, defaultEncapsulation?: ViewEncapsulation,
useJit?: boolean, useJit?: boolean,
jitDevMode?: boolean, jitDevMode?: boolean,
missingTranslation?: MissingTranslationStrategy, missingTranslation?: MissingTranslationStrategy,
enableLegacyTemplate?: boolean,
preserveWhitespaces?: boolean, preserveWhitespaces?: boolean,
strictInjectionParameters?: boolean, strictInjectionParameters?: boolean,
} = {}) { } = {}) {
@ -39,7 +34,6 @@ export class CompilerConfig {
this.useJit = !!useJit; this.useJit = !!useJit;
this.jitDevMode = !!jitDevMode; this.jitDevMode = !!jitDevMode;
this.missingTranslation = missingTranslation; this.missingTranslation = missingTranslation;
this.enableLegacyTemplate = enableLegacyTemplate === true;
this.preserveWhitespaces = preserveWhitespacesDefault(noUndefined(preserveWhitespaces)); this.preserveWhitespaces = preserveWhitespacesDefault(noUndefined(preserveWhitespaces));
this.strictInjectionParameters = strictInjectionParameters === true; this.strictInjectionParameters = strictInjectionParameters === true;
} }

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. // 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 // The `&#123;` / `&#x1ab;` syntax should be used when the named character reference does not
// exist. // exist.
export const NAMED_ENTITIES: {[k: string]: string} = { export const NAMED_ENTITIES: {[k: string]: string} = {
'Aacute': '\u00C1', 'Aacute': '\u00C1',
'aacute': '\u00E1', 'aacute': '\u00E1',

View File

@ -55,20 +55,11 @@ const IDENT_PROPERTY_IDX = 9;
// Group 10 = identifier inside () // Group 10 = identifier inside ()
const IDENT_EVENT_IDX = 10; 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 TEMPLATE_ATTR_PREFIX = '*';
const CLASS_ATTR = 'class'; const CLASS_ATTR = 'class';
const TEXT_CSS_SELECTOR = CssSelector.parse('*')[0]; 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} = {}; let warningCounts: {[warning: string]: number} = {};
function warnOnlyOnce(warnings: string[]): (warning: ParseError) => boolean { function warnOnlyOnce(warnings: string[]): (warning: ParseError) => boolean {
@ -109,10 +100,7 @@ export class TemplateParser {
preserveWhitespaces: boolean): {template: TemplateAst[], pipes: CompilePipeSummary[]} { preserveWhitespaces: boolean): {template: TemplateAst[], pipes: CompilePipeSummary[]} {
const result = this.tryParse( const result = this.tryParse(
component, template, directives, pipes, schemas, templateUrl, preserveWhitespaces); component, template, directives, pipes, schemas, templateUrl, preserveWhitespaces);
const warnings = const warnings = result.errors !.filter(error => error.level === ParseErrorLevel.WARNING);
result.errors !.filter(error => error.level === ParseErrorLevel.WARNING)
.filter(warnOnlyOnce(
[TEMPLATE_ATTR_DEPRECATION_WARNING, TEMPLATE_ELEMENT_DEPRECATION_WARNING]));
const errors = result.errors !.filter(error => error.level === ParseErrorLevel.ERROR); const errors = result.errors !.filter(error => error.level === ParseErrorLevel.ERROR);
@ -295,9 +283,7 @@ class TemplateParseVisitor implements html.Visitor {
let hasInlineTemplates = false; let hasInlineTemplates = false;
const attrs: AttrAst[] = []; const attrs: AttrAst[] = [];
const isTemplateElement = isTemplate( const isTemplateElement = isNgTemplate(element.name);
element, this.config.enableLegacyTemplate,
(m: string, span: ParseSourceSpan) => this._reportError(m, span, ParseErrorLevel.WARNING));
element.attrs.forEach(attr => { element.attrs.forEach(attr => {
const hasBinding = this._parseAttr( const hasBinding = this._parseAttr(
@ -306,13 +292,9 @@ class TemplateParseVisitor implements html.Visitor {
let templateBindingsSource: string|undefined; let templateBindingsSource: string|undefined;
let prefixToken: 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) { if (normalizedName.startsWith(TEMPLATE_ATTR_PREFIX)) {
this._reportError(
TEMPLATE_ATTR_DEPRECATION_WARNING, attr.sourceSpan, ParseErrorLevel.WARNING);
templateBindingsSource = attr.value;
} else if (normalizedName.startsWith(TEMPLATE_ATTR_PREFIX)) {
templateBindingsSource = attr.value; templateBindingsSource = attr.value;
prefixToken = normalizedName.substring(TEMPLATE_ATTR_PREFIX.length) + ':'; prefixToken = normalizedName.substring(TEMPLATE_ATTR_PREFIX.length) + ':';
} }
@ -321,7 +303,7 @@ class TemplateParseVisitor implements html.Visitor {
if (hasTemplateBinding) { if (hasTemplateBinding) {
if (hasInlineTemplates) { if (hasInlineTemplates) {
this._reportError( 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); attr.sourceSpan);
} }
hasInlineTemplates = true; hasInlineTemplates = true;
@ -400,7 +382,7 @@ class TemplateParseVisitor implements html.Visitor {
if (hasInlineTemplates) { if (hasInlineTemplates) {
const templateQueryStartIndex = this.contentQueryStartId; const templateQueryStartIndex = this.contentQueryStartId;
const templateSelector = createElementCssSelector(TEMPLATE_ELEMENT, templateMatchableAttrs); const templateSelector = createElementCssSelector('ng-template', templateMatchableAttrs);
const {directives: templateDirectiveMetas} = const {directives: templateDirectiveMetas} =
this._parseDirectives(this.selectorMatcher, templateSelector); this._parseDirectives(this.selectorMatcher, templateSelector);
const templateBoundDirectivePropNames = new Set<string>(); const templateBoundDirectivePropNames = new Set<string>();
@ -909,19 +891,3 @@ function isEmptyExpression(ast: AST): boolean {
} }
return ast instanceof EmptyExpr; 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;
}

View File

@ -32,10 +32,6 @@ import {humanizeDom, humanizeDomSourceSpans, humanizeLineColumn} from './ast_spe
}); });
it('should parse text nodes inside <ng-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([ expect(humanizeDom(parser.parse('<ng-template>a</ng-template>', 'TestComp'))).toEqual([
[html.Element, 'ng-template', 0], [html.Text, 'a', 1] [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', () => { 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'))) expect(humanizeDom(parser.parse('<ng-template><span></span></ng-template>', 'TestComp')))
.toEqual([[html.Element, 'ng-template', 0], [html.Element, 'span', 1]]); .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>', () => { 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'))) expect(humanizeDom(parser.parse('<ng-template><tr></tr></ng-template>', 'TestComp')))
.toEqual([ .toEqual([
[html.Element, 'ng-template', 0], [html.Element, 'ng-template', 0],
@ -282,10 +272,6 @@ import {humanizeDom, humanizeDomSourceSpans, humanizeLineColumn} from './ast_spe
}); });
it('should parse attributes on <ng-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'))) expect(humanizeDom(parser.parse('<ng-template k="v"></ng-template>', 'TestComp')))
.toEqual([ .toEqual([
[html.Element, 'ng-template', 0], [html.Element, 'ng-template', 0],

View File

@ -158,7 +158,6 @@ function doCompile(
const config = new CompilerConfig({ const config = new CompilerConfig({
defaultEncapsulation: ViewEncapsulation.Emulated, defaultEncapsulation: ViewEncapsulation.Emulated,
useJit: false, useJit: false,
enableLegacyTemplate: options.enableLegacyTemplate === true,
missingTranslation: options.missingTranslation, missingTranslation: options.missingTranslation,
preserveWhitespaces: options.preserveWhitespaces, preserveWhitespaces: options.preserveWhitespaces,
strictInjectionParameters: options.strictInjectionParameters, strictInjectionParameters: options.strictInjectionParameters,

View File

@ -336,7 +336,6 @@ class ArrayConsole implements Console {
TestBed.configureCompiler({ TestBed.configureCompiler({
providers: [ providers: [
{provide: Console, useValue: console}, {provide: Console, useValue: console},
{provide: CompilerConfig, useValue: new CompilerConfig({enableLegacyTemplate: true})}
], ],
}); });
}); });
@ -812,6 +811,35 @@ Binding to attribute 'onEvent' is disallowed for security reasons ("<my-componen
] ]
]); ]);
}); });
it('should support * directives', () => {
expect(humanizeTplAst(parse('<div *ngIf>', [ngIf]))).toEqual([
[EmbeddedTemplateAst],
[DirectiveAst, ngIf],
[BoundDirectivePropertyAst, 'ngIf', 'null'],
[ElementAst, 'div'],
]);
});
it('should support <ng-template>', () => {
expect(humanizeTplAst(parse('<ng-template>', []))).toEqual([
[EmbeddedTemplateAst],
]);
});
it('should treat <template> as a regular tag', () => {
expect(humanizeTplAst(parse('<template>', []))).toEqual([
[ElementAst, 'template'],
]);
});
it('should not special case the template attribute', () => {
expect(humanizeTplAst(parse('<p template="ngFor">', []))).toEqual([
[ElementAst, 'p'],
[AttrAst, 'template', 'ngFor'],
]);
});
}); });
describe('events', () => { describe('events', () => {
@ -852,17 +880,11 @@ Binding to attribute 'onEvent' is disallowed for security reasons ("<my-componen
() => { () => {
const dirA = const dirA =
compileDirectiveMetadataCreate({ compileDirectiveMetadataCreate({
selector: 'template,ng-template', selector: 'ng-template',
outputs: ['e'], outputs: ['e'],
type: createTypeMeta({reference: {filePath: someModuleUrl, name: 'DirA'}}) type: createTypeMeta({reference: {filePath: someModuleUrl, name: 'DirA'}})
}).toSummary(); }).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([ expect(humanizeTplAst(parse('<ng-template (e)="f"></ng-template>', [dirA]))).toEqual([
[EmbeddedTemplateAst], [EmbeddedTemplateAst],
[BoundEventAst, 'e', null, 'f'], [BoundEventAst, 'e', null, 'f'],
@ -952,7 +974,7 @@ Binding to attribute 'onEvent' is disallowed for security reasons ("<my-componen
it('should locate directives in inline templates', () => { it('should locate directives in inline templates', () => {
const dirTemplate = const dirTemplate =
compileDirectiveMetadataCreate({ compileDirectiveMetadataCreate({
selector: 'template', selector: 'ng-template',
type: createTypeMeta({reference: {filePath: someModuleUrl, name: 'onTemplate'}}) type: createTypeMeta({reference: {filePath: someModuleUrl, name: 'onTemplate'}})
}).toSummary(); }).toSummary();
expect(humanizeTplAst(parse('<div *ngIf="cond">', [ngIf, dirTemplate]))).toEqual([ expect(humanizeTplAst(parse('<div *ngIf="cond">', [ngIf, dirTemplate]))).toEqual([
@ -1460,8 +1482,6 @@ Reference "#a" is defined several times ("<div #a></div><div [ERROR ->]#a></div>
it('should not throw error when there is same reference name in different templates', it('should not throw error when there is same reference name in different templates',
() => { () => {
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>', [])) expect(() => parse('<div #a><ng-template #a><span>OK</span></ng-template></div>', []))
.not.toThrowError(); .not.toThrowError();
}); });
@ -1500,20 +1520,12 @@ Reference "#a" is defined several times ("<div #a></div><div [ERROR ->]#a></div>
beforeEach(() => { reflector = new JitReflector(); }); beforeEach(() => { reflector = new JitReflector(); });
it('should create embedded templates for <ng-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>', [ expect(humanizeTplAst(parse('<ng-template></ng-template>', [
]))).toEqual([[EmbeddedTemplateAst]]); ]))).toEqual([[EmbeddedTemplateAst]]);
}); });
it('should create embedded templates for <ng-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([ expect(humanizeTplAst(parse('<svg><ng-template></ng-template></svg>', []))).toEqual([
[ElementAst, ':svg:svg'], [ElementAst, ':svg:svg'],
[EmbeddedTemplateAst], [EmbeddedTemplateAst],
@ -1521,12 +1533,6 @@ Reference "#a" is defined several times ("<div #a></div><div [ERROR ->]#a></div>
}); });
it('should support references via #...', () => { it('should support references via #...', () => {
expect(humanizeTplAst(parse('<template #a>', []))).toEqual([
[EmbeddedTemplateAst],
[
ReferenceAst, 'a', createTokenForExternalReference(reflector, Identifiers.TemplateRef)
],
]);
expect(humanizeTplAst(parse('<ng-template #a>', []))).toEqual([ expect(humanizeTplAst(parse('<ng-template #a>', []))).toEqual([
[EmbeddedTemplateAst], [EmbeddedTemplateAst],
[ [
@ -1536,12 +1542,6 @@ Reference "#a" is defined several times ("<div #a></div><div [ERROR ->]#a></div>
}); });
it('should support references via ref-...', () => { it('should support references via ref-...', () => {
expect(humanizeTplAst(parse('<template ref-a>', []))).toEqual([
[EmbeddedTemplateAst],
[
ReferenceAst, 'a', createTokenForExternalReference(reflector, Identifiers.TemplateRef)
]
]);
expect(humanizeTplAst(parse('<ng-template ref-a>', []))).toEqual([ expect(humanizeTplAst(parse('<ng-template ref-a>', []))).toEqual([
[EmbeddedTemplateAst], [EmbeddedTemplateAst],
[ [
@ -1551,10 +1551,6 @@ Reference "#a" is defined several times ("<div #a></div><div [ERROR ->]#a></div>
}); });
it('should parse variables via let-...', () => { it('should parse variables via let-...', () => {
expect(humanizeTplAst(parse('<template let-a="b">', []))).toEqual([
[EmbeddedTemplateAst],
[VariableAst, 'a', 'b'],
]);
expect(humanizeTplAst(parse('<ng-template let-a="b">', []))).toEqual([ expect(humanizeTplAst(parse('<ng-template let-a="b">', []))).toEqual([
[EmbeddedTemplateAst], [EmbeddedTemplateAst],
[VariableAst, 'a', 'b'], [VariableAst, 'a', 'b'],
@ -1567,10 +1563,6 @@ Reference "#a" is defined several times ("<div #a></div><div [ERROR ->]#a></div>
selector: '[a]', selector: '[a]',
type: createTypeMeta({reference: {filePath: someModuleUrl, name: 'DirA'}}) type: createTypeMeta({reference: {filePath: someModuleUrl, name: 'DirA'}})
}).toSummary(); }).toSummary();
expect(humanizeTplAst(parse('<template let-a="b"></template>', [dirA]))).toEqual([
[EmbeddedTemplateAst],
[VariableAst, 'a', 'b'],
]);
expect(humanizeTplAst(parse('<ng-template let-a="b"></ng-template>', [dirA]))).toEqual([ expect(humanizeTplAst(parse('<ng-template let-a="b"></ng-template>', [dirA]))).toEqual([
[EmbeddedTemplateAst], [EmbeddedTemplateAst],
[VariableAst, 'a', 'b'], [VariableAst, 'a', 'b'],
@ -1580,30 +1572,6 @@ Reference "#a" is defined several times ("<div #a></div><div [ERROR ->]#a></div>
}); });
describe('inline templates', () => { describe('inline templates', () => {
it('should wrap the element into an EmbeddedTemplateAST', () => {
expect(humanizeTplAst(parse('<div template>', []))).toEqual([
[EmbeddedTemplateAst],
[ElementAst, 'div'],
]);
});
it('should wrap the element with data-template attribute into an EmbeddedTemplateAST ',
() => {
expect(humanizeTplAst(parse('<div data-template>', []))).toEqual([
[EmbeddedTemplateAst],
[ElementAst, 'div'],
]);
});
it('should parse bound properties', () => {
expect(humanizeTplAst(parse('<div template="ngIf test">', [ngIf]))).toEqual([
[EmbeddedTemplateAst],
[DirectiveAst, ngIf],
[BoundDirectivePropertyAst, 'ngIf', 'test'],
[ElementAst, 'div'],
]);
});
it('should report an error on variables declared with #', () => { it('should report an error on variables declared with #', () => {
expect(() => humanizeTplAst(parse('<div *ngIf="#a=b">', []))) expect(() => humanizeTplAst(parse('<div *ngIf="#a=b">', [])))
.toThrowError(/Parser Error: Unexpected token # at column 1/); .toThrowError(/Parser Error: Unexpected token # at column 1/);
@ -1646,7 +1614,7 @@ Reference "#a" is defined several times ("<div #a></div><div [ERROR ->]#a></div>
selector: '[b]', selector: '[b]',
type: createTypeMeta({reference: {filePath: someModuleUrl, name: 'DirB'}}) type: createTypeMeta({reference: {filePath: someModuleUrl, name: 'DirB'}})
}).toSummary(); }).toSummary();
expect(humanizeTplAst(parse('<div template="a b" b>', [dirA, dirB]))).toEqual([ expect(humanizeTplAst(parse('<div *a="b" b>', [dirA, dirB]))).toEqual([
[EmbeddedTemplateAst], [DirectiveAst, dirA], [BoundDirectivePropertyAst, 'a', 'b'], [EmbeddedTemplateAst], [DirectiveAst, dirA], [BoundDirectivePropertyAst, 'a', 'b'],
[ElementAst, 'div'], [AttrAst, 'b', ''], [DirectiveAst, dirB] [ElementAst, 'div'], [AttrAst, 'b', ''], [DirectiveAst, dirB]
]); ]);
@ -1658,8 +1626,12 @@ Reference "#a" is defined several times ("<div #a></div><div [ERROR ->]#a></div>
selector: '[a]', selector: '[a]',
type: createTypeMeta({reference: {filePath: someModuleUrl, name: 'DirA'}}) type: createTypeMeta({reference: {filePath: someModuleUrl, name: 'DirA'}})
}).toSummary(); }).toSummary();
expect(humanizeTplAst(parse('<div template="let a=b">', [dirA]))).toEqual([ expect(
[EmbeddedTemplateAst], [VariableAst, 'a', 'b'], [ElementAst, 'div'] humanizeTplAst(parse('<ng-template let-a="b"><div></div></ng-template>', [dirA])))
.toEqual([
[EmbeddedTemplateAst],
[VariableAst, 'a', 'b'],
[ElementAst, 'div'],
]); ]);
}); });
@ -1760,13 +1732,11 @@ Reference "#a" is defined several times ("<div #a></div><div [ERROR ->]#a></div>
describe('embedded templates', () => { describe('embedded templates', () => {
it('should project embedded templates with wildcard selector', () => { it('should project embedded templates with wildcard selector', () => {
expect(humanizeContentProjection(parse( expect(humanizeContentProjection(
'<div><template></template><ng-template></ng-template></div>', parse('<div><ng-template></ng-template></div>', [createComp('div', ['*'])])))
[createComp('div', ['*'])])))
.toEqual([ .toEqual([
['div', null], ['div', null],
['template', 0], ['template', 0],
['template', 0],
]); ]);
}); });
@ -1848,14 +1818,12 @@ Reference "#a" is defined several times ("<div #a></div><div [ERROR ->]#a></div>
}); });
it('should override <ng-template>', () => { it('should override <ng-template>', () => {
expect( expect(humanizeContentProjection(parse(
humanizeContentProjection(parse( '<div><ng-template ngProjectAs="b"></ng-template></div>',
'<div><template ngProjectAs="b"></template><ng-template ngProjectAs="b"></ng-template></div>',
[createComp('div', ['template', 'b'])]))) [createComp('div', ['template', 'b'])])))
.toEqual([ .toEqual([
['div', null], ['div', null],
['template', 1], ['template', 1],
['template', 1],
]); ]);
}); });
@ -1894,26 +1862,14 @@ 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`); `<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', () => { it('should treat *attr on a template element as valid',
expect(() => parse('<template *ngIf>', [])).not.toThrowError(); () => { expect(() => parse('<ng-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();
expect(() => parse('<ng-template template="ngIf">', [])).not.toThrowError();
});
it('should report when multiple *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: 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`); Can't have multiple template bindings on one element. Use only one attribute prefixed with * ("<div *ngIf [ERROR ->]*ngFor>"): TestComp@0:11`);
}); });
it('should report when mix of template and *attrs are used on the same element', () => {
expect(() => parse('<span template="ngIf" *ngFor>', []))
.toThrowError(`Template parse errors:
Can't have multiple template bindings on one element. Use only one attribute named 'template' or prefixed with * ("<span template="ngIf" [ERROR ->]*ngFor>"): TestComp@0:22`);
});
it('should report invalid property names', () => { it('should report invalid property names', () => {
expect(() => parse('<div [invalidProp]></div>', [])).toThrowError(`Template parse errors: expect(() => parse('<div [invalidProp]></div>', [])).toThrowError(`Template parse errors:
@ -1977,12 +1933,6 @@ Parser Error: Unexpected token 'b' at column 3 in [a b] in TestComp@0:5 ("<div [
template: compileTemplateMetadata({ngContentSelectors: []}) template: compileTemplateMetadata({ngContentSelectors: []})
}).toSummary(); }).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])) expect(() => parse('<ng-template [a]="b" (e)="f"></ng-template>', [dirA]))
.toThrowError(`Template parse errors: .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 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
@ -2093,8 +2043,6 @@ Property binding a not used by any directive on an embedded template. Make sure
}); });
it('should support embedded template', () => { it('should support embedded template', () => {
expect(humanizeTplAstSourceSpans(parse('<template></template>', [
]))).toEqual([[EmbeddedTemplateAst, '<template>']]);
expect(humanizeTplAstSourceSpans(parse('<ng-template></ng-template>', [ expect(humanizeTplAstSourceSpans(parse('<ng-template></ng-template>', [
]))).toEqual([[EmbeddedTemplateAst, '<ng-template>']]); ]))).toEqual([[EmbeddedTemplateAst, '<ng-template>']]);
}); });
@ -2112,10 +2060,6 @@ Property binding a not used by any directive on an embedded template. Make sure
}); });
it('should support variables', () => { it('should support variables', () => {
expect(humanizeTplAstSourceSpans(parse('<template let-a="b"></template>', []))).toEqual([
[EmbeddedTemplateAst, '<template let-a="b">'],
[VariableAst, 'a', 'b', 'let-a="b"'],
]);
expect(humanizeTplAstSourceSpans(parse('<ng-template let-a="b"></ng-template>', []))) expect(humanizeTplAstSourceSpans(parse('<ng-template let-a="b"></ng-template>', [])))
.toEqual([ .toEqual([
[EmbeddedTemplateAst, '<ng-template let-a="b">'], [EmbeddedTemplateAst, '<ng-template let-a="b">'],
@ -2342,47 +2286,6 @@ The pipe 'test' could not be found ("{{[ERROR ->]a | test}}"): TestComp@0:2`);
...humanizedExpandedForm, ...humanizedExpandedForm ...humanizedExpandedForm, ...humanizedExpandedForm
]); ]);
}); });
}); });
describe('Template Parser - `<template>` support disabled by default', () => {
beforeEach(() => {
TestBed.configureCompiler({
providers: [
{provide: Console, useValue: console},
{provide: CompilerConfig, useValue: new CompilerConfig()}
],
});
});
commonBeforeEach();
it('should support * directives', () => {
expect(humanizeTplAst(parse('<div *ngIf>', [ngIf]))).toEqual([
[EmbeddedTemplateAst],
[DirectiveAst, ngIf],
[BoundDirectivePropertyAst, 'ngIf', 'null'],
[ElementAst, 'div'],
]);
});
it('should support <ng-template>', () => {
expect(humanizeTplAst(parse('<ng-template>', []))).toEqual([
[EmbeddedTemplateAst],
]);
});
it('should treat <template> as a regular tag', () => {
expect(humanizeTplAst(parse('<template>', []))).toEqual([
[ElementAst, 'template'],
]);
});
it('should not special case the template attribute', () => {
expect(humanizeTplAst(parse('<p template="ngFor">', []))).toEqual([
[ElementAst, 'p'],
[AttrAst, 'template', 'ngFor'],
]);
});
});
})(); })();

View File

@ -90,9 +90,6 @@ export type CompilerOptions = {
defaultEncapsulation?: ViewEncapsulation, defaultEncapsulation?: ViewEncapsulation,
providers?: StaticProvider[], providers?: StaticProvider[],
missingTranslation?: MissingTranslationStrategy, 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, preserveWhitespaces?: boolean,
}; };

View File

@ -38,14 +38,7 @@ const ANCHOR_ELEMENT = new InjectionToken('AnchorElement');
function declareTests({useJit}: {useJit: boolean}) { function declareTests({useJit}: {useJit: boolean}) {
describe('integration tests', function() { describe('integration tests', function() {
beforeEach(() => { beforeEach(() => { TestBed.configureCompiler({useJit}); });
TestBed.configureCompiler({
useJit,
providers: [
{provide: CompilerConfig, useValue: new CompilerConfig({enableLegacyTemplate: true})}
]
});
});
describe('react to record changes', function() { describe('react to record changes', function() {
it('should consume text node changes', () => { it('should consume text node changes', () => {
@ -420,22 +413,6 @@ function declareTests({useJit}: {useJit: boolean}) {
expect(getDOM().isCommentNode(childNodesOfWrapper[0])).toBe(true); 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', () => { it('should allow to transplant TemplateRefs into other ViewContainers', () => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
declarations: [ declarations: [

View File

@ -162,7 +162,6 @@ export class JitCompilerFactory implements CompilerFactory {
useJit: true, useJit: true,
defaultEncapsulation: ViewEncapsulation.Emulated, defaultEncapsulation: ViewEncapsulation.Emulated,
missingTranslation: MissingTranslationStrategy.Warning, missingTranslation: MissingTranslationStrategy.Warning,
enableLegacyTemplate: false,
}; };
this._defaultOptions = [compilerOptions, ...defaultOptions]; this._defaultOptions = [compilerOptions, ...defaultOptions];
@ -182,7 +181,6 @@ export class JitCompilerFactory implements CompilerFactory {
// from the app providers // from the app providers
defaultEncapsulation: opts.defaultEncapsulation, defaultEncapsulation: opts.defaultEncapsulation,
missingTranslation: opts.missingTranslation, missingTranslation: opts.missingTranslation,
enableLegacyTemplate: opts.enableLegacyTemplate,
preserveWhitespaces: opts.preserveWhitespaces, preserveWhitespaces: opts.preserveWhitespaces,
}); });
}, },
@ -200,7 +198,6 @@ function _mergeOptions(optionsArr: CompilerOptions[]): CompilerOptions {
defaultEncapsulation: _lastDefined(optionsArr.map(options => options.defaultEncapsulation)), defaultEncapsulation: _lastDefined(optionsArr.map(options => options.defaultEncapsulation)),
providers: _mergeArrays(optionsArr.map(options => options.providers !)), providers: _mergeArrays(optionsArr.map(options => options.providers !)),
missingTranslation: _lastDefined(optionsArr.map(options => options.missingTranslation)), missingTranslation: _lastDefined(optionsArr.map(options => options.missingTranslation)),
enableLegacyTemplate: _lastDefined(optionsArr.map(options => options.enableLegacyTemplate)),
preserveWhitespaces: _lastDefined(optionsArr.map(options => options.preserveWhitespaces)), preserveWhitespaces: _lastDefined(optionsArr.map(options => options.preserveWhitespaces)),
}; };
} }

View File

@ -112,7 +112,6 @@ export declare type CompilerOptions = {
defaultEncapsulation?: ViewEncapsulation; defaultEncapsulation?: ViewEncapsulation;
providers?: StaticProvider[]; providers?: StaticProvider[];
missingTranslation?: MissingTranslationStrategy; missingTranslation?: MissingTranslationStrategy;
enableLegacyTemplate?: boolean;
preserveWhitespaces?: boolean; preserveWhitespaces?: boolean;
}; };