feat(compiler): Add a `enableLegacyTemplate` option to support `<template>`

When the `enableLegacyTemplate` is set to `false`, `<template>` tags and the
`template` attribute are no more used to define angular templates but are
treated as regular tag and attribute.

The default value is `true`.

In order to define a template, you have to use the `<ng-template>` tag.

This option applies to your application and all the libraries it uses. That is
you should make sure none of them rely on the legacy way to defined templates
when this option is turned off (`false`).
This commit is contained in:
Victor Berchet 2017-01-10 19:07:03 -08:00 committed by Igor Minar
parent bf8eb41248
commit e99d721612
10 changed files with 85 additions and 19 deletions

View File

@ -75,7 +75,8 @@ export class CodeGenerator {
debug: options.debug === true,
translations: transContent,
i18nFormat: cliOptions.i18nFormat,
locale: cliOptions.locale
locale: cliOptions.locale,
enableLegacyTemplate: options.enableLegacyTemplate !== false,
});
return new CodeGenerator(options, program, tsCompilerHost, aotCompiler, ngCompilerHost);
}

View File

@ -61,7 +61,8 @@ export function createAotCompiler(compilerHost: AotCompilerHost, options: AotCom
defaultEncapsulation: ViewEncapsulation.Emulated,
logBindingUpdate: false,
useJit: false,
useViewEngine: options.useViewEngine
useViewEngine: options.useViewEngine,
enableLegacyTemplate: options.enableLegacyTemplate !== false,
});
const normalizer = new DirectiveNormalizer(
{get: (url: string) => compilerHost.loadResource(url)}, urlResolver, htmlParser, config);

View File

@ -12,4 +12,5 @@ export interface AotCompilerOptions {
i18nFormat?: string;
translations?: string;
useViewEngine?: boolean;
enableLegacyTemplate?: boolean;
}

View File

@ -23,22 +23,28 @@ export const USE_VIEW_ENGINE = new InjectionToken<boolean>('UseViewEngine');
export class CompilerConfig {
public renderTypes: RenderTypes;
public defaultEncapsulation: ViewEncapsulation;
private _genDebugInfo: boolean;
private _logBindingUpdate: boolean;
// 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 useViewEngine: boolean;
public missingTranslation: MissingTranslationStrategy;
private _genDebugInfo: boolean;
private _logBindingUpdate: boolean;
constructor(
{renderTypes = new DefaultRenderTypes(), defaultEncapsulation = ViewEncapsulation.Emulated,
genDebugInfo, logBindingUpdate, useJit = true, missingTranslation, useViewEngine}: {
genDebugInfo, logBindingUpdate, useJit = true, missingTranslation, useViewEngine,
enableLegacyTemplate}: {
renderTypes?: RenderTypes,
defaultEncapsulation?: ViewEncapsulation,
genDebugInfo?: boolean,
logBindingUpdate?: boolean,
useJit?: boolean,
missingTranslation?: MissingTranslationStrategy,
useViewEngine?: boolean
useViewEngine?: boolean,
enableLegacyTemplate?: boolean,
} = {}) {
this.renderTypes = renderTypes;
this.defaultEncapsulation = defaultEncapsulation;
@ -47,6 +53,7 @@ export class CompilerConfig {
this.useJit = useJit;
this.missingTranslation = missingTranslation;
this.useViewEngine = true;
this.enableLegacyTemplate = enableLegacyTemplate !== false;
}
get genDebugInfo(): boolean {

View File

@ -104,7 +104,6 @@ export const COMPILER_PROVIDERS: Array<any|Type<any>|{[k: string]: any}|any[]> =
AnimationParser,
];
@CompilerInjectable()
export class JitCompilerFactory implements CompilerFactory {
private _defaultOptions: CompilerOptions[];
@ -136,7 +135,8 @@ export class JitCompilerFactory implements CompilerFactory {
// from the app providers
defaultEncapsulation: opts.defaultEncapsulation,
logBindingUpdate: opts.useDebug,
missingTranslation: opts.missingTranslation, useViewEngine
missingTranslation: opts.missingTranslation, useViewEngine,
enableLegacyTemplate: opts.enableLegacyTemplate,
});
},
deps: [USE_VIEW_ENGINE]

View File

@ -273,7 +273,7 @@ class TemplateParseVisitor implements html.Visitor {
let hasInlineTemplates = false;
const attrs: AttrAst[] = [];
const isTemplateElement = isTemplate(
element,
element, this.config.enableLegacyTemplate,
(m: string, span: ParseSourceSpan) => this._reportError(m, span, ParseErrorLevel.WARNING));
element.attrs.forEach(attr => {
@ -285,7 +285,7 @@ class TemplateParseVisitor implements html.Visitor {
let prefixToken: string|undefined;
let normalizedName = this._normalizeAttributeName(attr.name);
if (normalizedName == TEMPLATE_ATTR) {
if (this.config.enableLegacyTemplate && normalizedName == TEMPLATE_ATTR) {
this._reportError(
`The template attribute is deprecated. Use an ng-template element instead.`,
attr.sourceSpan, ParseErrorLevel.WARNING);
@ -905,16 +905,19 @@ function isEmptyExpression(ast: AST): boolean {
// `template` is deprecated in 4.x
function isTemplate(
el: html.Element, reportDeprecation: (m: string, span: ParseSourceSpan) => void): boolean {
el: html.Element, enableLegacyTemplate: boolean,
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;
}
if (enableLegacyTemplate && tagNoNs.toLowerCase() === TEMPLATE_ELEMENT) {
reportDeprecation(
`The <template> element is deprecated. Use <ng-template> instead`, el.sourceSpan);
return true;
}
return false;
}
return false;
}
}

View File

@ -5,6 +5,7 @@
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {CompilerConfig} from '@angular/compiler';
import {CompileAnimationEntryMetadata, CompileDiDependencyMetadata, CompileDirectiveMetadata, CompileDirectiveSummary, CompilePipeMetadata, CompilePipeSummary, CompileProviderMetadata, CompileTemplateMetadata, CompileTokenMetadata, CompileTypeMetadata, tokenReference} from '@angular/compiler/src/compile_metadata';
import {DomElementSchemaRegistry} from '@angular/compiler/src/schema/dom_element_schema_registry';
import {ElementSchemaRegistry} from '@angular/compiler/src/schema/element_schema_registry';
@ -44,8 +45,13 @@ export function main() {
function commonBeforeEach() {
beforeEach(() => {
console = new ArrayConsole();
TestBed.configureCompiler({providers: [{provide: Console, useValue: console}]});
TestBed.configureCompiler({
providers: [
{provide: Console, useValue: console},
],
});
});
beforeEach(inject([TemplateParser], (parser: TemplateParser) => {
const someAnimation = new CompileAnimationEntryMetadata('someAnimation', []);
const someTemplate = new CompileTemplateMetadata({animations: [someAnimation]});
@ -2023,6 +2029,47 @@ The pipe 'test' could not be found ("{{[ERROR ->]a | test}}"): TestComp@0:2`);
});
});
});
describe('Template Parser - opt-out `<template>` support', () => {
beforeEach(() => {
TestBed.configureCompiler({
providers: [{
provide: CompilerConfig,
useValue: new CompilerConfig({enableLegacyTemplate: false}),
}],
});
});
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'],
]);
});
});
}
function humanizeTplAst(

View File

@ -7,7 +7,6 @@
*/
import {Injectable, InjectionToken} from '../di';
import {stringify} from '../facade/lang';
import {MissingTranslationStrategy} from '../i18n/tokens';
import {ViewEncapsulation} from '../metadata';
import {Type} from '../type';
@ -99,6 +98,9 @@ export type CompilerOptions = {
defaultEncapsulation?: ViewEncapsulation,
providers?: any[],
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,
};
/**

View File

@ -78,6 +78,9 @@ interface Options extends ts.CompilerOptions {
// Whether to embed debug information in the compiled templates
debug?: boolean;
// Whether to enable support for <template> and the template attribute (true by default)
enableLegacyTemplate?: boolean;
}
export default Options;

View File

@ -234,6 +234,7 @@ export declare type CompilerOptions = {
defaultEncapsulation?: ViewEncapsulation;
providers?: any[];
missingTranslation?: MissingTranslationStrategy;
enableLegacyTemplate?: boolean;
};
/** @stable */