fix(core): support components without a selector (#10331)
Components without a selector now get the selector `ng-component`. Directives without a selector will throw an error message. Closes #3464 Closes #10216
This commit is contained in:
parent
a67cc8229d
commit
9b39e499ac
|
@ -17,6 +17,7 @@ import * as path from 'path';
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
import {CompileMetadataResolver, DirectiveNormalizer, DomElementSchemaRegistry, HtmlParser, Lexer, NgModuleCompiler, Parser, StyleCompiler, TemplateParser, TypeScriptEmitter, ViewCompiler} from './compiler_private';
|
import {CompileMetadataResolver, DirectiveNormalizer, DomElementSchemaRegistry, HtmlParser, Lexer, NgModuleCompiler, Parser, StyleCompiler, TemplateParser, TypeScriptEmitter, ViewCompiler} from './compiler_private';
|
||||||
|
import {Console} from './core_private';
|
||||||
import {ReflectorHost, ReflectorHostContext} from './reflector_host';
|
import {ReflectorHost, ReflectorHostContext} from './reflector_host';
|
||||||
import {StaticAndDynamicReflectionCapabilities} from './static_reflection_capabilities';
|
import {StaticAndDynamicReflectionCapabilities} from './static_reflection_capabilities';
|
||||||
import {StaticReflector, StaticSymbol} from './static_reflector';
|
import {StaticReflector, StaticSymbol} from './static_reflector';
|
||||||
|
@ -135,13 +136,15 @@ export class CodeGenerator {
|
||||||
});
|
});
|
||||||
const normalizer = new DirectiveNormalizer(xhr, urlResolver, htmlParser, config);
|
const normalizer = new DirectiveNormalizer(xhr, urlResolver, htmlParser, config);
|
||||||
const expressionParser = new Parser(new Lexer());
|
const expressionParser = new Parser(new Lexer());
|
||||||
const tmplParser = new TemplateParser(
|
const elementSchemaRegistry = new DomElementSchemaRegistry();
|
||||||
expressionParser, new DomElementSchemaRegistry(), htmlParser,
|
const console = new Console();
|
||||||
/*console*/ null, []);
|
const tmplParser =
|
||||||
|
new TemplateParser(expressionParser, elementSchemaRegistry, htmlParser, console, []);
|
||||||
const resolver = new CompileMetadataResolver(
|
const resolver = new CompileMetadataResolver(
|
||||||
new compiler.NgModuleResolver(staticReflector),
|
new compiler.NgModuleResolver(staticReflector),
|
||||||
new compiler.DirectiveResolver(staticReflector), new compiler.PipeResolver(staticReflector),
|
new compiler.DirectiveResolver(staticReflector), new compiler.PipeResolver(staticReflector),
|
||||||
new compiler.ViewResolver(staticReflector), config, /*console*/ null, staticReflector);
|
new compiler.ViewResolver(staticReflector), config, console, elementSchemaRegistry,
|
||||||
|
staticReflector);
|
||||||
const offlineCompiler = new compiler.OfflineCompiler(
|
const offlineCompiler = new compiler.OfflineCompiler(
|
||||||
resolver, normalizer, tmplParser, new StyleCompiler(urlResolver), new ViewCompiler(config),
|
resolver, normalizer, tmplParser, new StyleCompiler(urlResolver), new ViewCompiler(config),
|
||||||
new NgModuleCompiler(), new TypeScriptEmitter(reflectorHost));
|
new NgModuleCompiler(), new TypeScriptEmitter(reflectorHost));
|
||||||
|
|
|
@ -14,4 +14,7 @@ export var ReflectorReader: typeof t.ReflectorReader = r.ReflectorReader;
|
||||||
export type ReflectionCapabilities = t.ReflectionCapabilities;
|
export type ReflectionCapabilities = t.ReflectionCapabilities;
|
||||||
export var ReflectionCapabilities: typeof t.ReflectionCapabilities = r.ReflectionCapabilities;
|
export var ReflectionCapabilities: typeof t.ReflectionCapabilities = r.ReflectionCapabilities;
|
||||||
|
|
||||||
|
export type Console = t.Console;
|
||||||
|
export var Console: typeof t.Console = r.Console;
|
||||||
|
|
||||||
export var reflector: typeof t.reflector = r.reflector;
|
export var reflector: typeof t.reflector = r.reflector;
|
||||||
|
|
|
@ -23,6 +23,7 @@ import {ViewEncapsulation} from '@angular/core';
|
||||||
|
|
||||||
import {StaticReflector} from './static_reflector';
|
import {StaticReflector} from './static_reflector';
|
||||||
import {CompileMetadataResolver, HtmlParser, DirectiveNormalizer, Lexer, Parser, DomElementSchemaRegistry, TypeScriptEmitter, MessageExtractor, removeDuplicates, ExtractionResult, Message, ParseError, serializeXmb,} from './compiler_private';
|
import {CompileMetadataResolver, HtmlParser, DirectiveNormalizer, Lexer, Parser, DomElementSchemaRegistry, TypeScriptEmitter, MessageExtractor, removeDuplicates, ExtractionResult, Message, ParseError, serializeXmb,} from './compiler_private';
|
||||||
|
import {Console} from './core_private';
|
||||||
|
|
||||||
import {ReflectorHost} from './reflector_host';
|
import {ReflectorHost} from './reflector_host';
|
||||||
import {StaticAndDynamicReflectionCapabilities} from './static_reflection_capabilities';
|
import {StaticAndDynamicReflectionCapabilities} from './static_reflection_capabilities';
|
||||||
|
@ -146,10 +147,13 @@ class Extractor {
|
||||||
});
|
});
|
||||||
const normalizer = new DirectiveNormalizer(xhr, urlResolver, htmlParser, config);
|
const normalizer = new DirectiveNormalizer(xhr, urlResolver, htmlParser, config);
|
||||||
const expressionParser = new Parser(new Lexer());
|
const expressionParser = new Parser(new Lexer());
|
||||||
|
const elementSchemaRegistry = new DomElementSchemaRegistry();
|
||||||
|
const console = new Console();
|
||||||
const resolver = new CompileMetadataResolver(
|
const resolver = new CompileMetadataResolver(
|
||||||
new compiler.NgModuleResolver(staticReflector),
|
new compiler.NgModuleResolver(staticReflector),
|
||||||
new compiler.DirectiveResolver(staticReflector), new compiler.PipeResolver(staticReflector),
|
new compiler.DirectiveResolver(staticReflector), new compiler.PipeResolver(staticReflector),
|
||||||
new compiler.ViewResolver(staticReflector), config, /*console*/ null, staticReflector);
|
new compiler.ViewResolver(staticReflector), config, console, elementSchemaRegistry,
|
||||||
|
staticReflector);
|
||||||
|
|
||||||
// TODO(vicb): handle implicit
|
// TODO(vicb): handle implicit
|
||||||
const extractor = new MessageExtractor(htmlParser, expressionParser, [], {});
|
const extractor = new MessageExtractor(htmlParser, expressionParser, [], {});
|
||||||
|
|
|
@ -21,6 +21,7 @@ import {DirectiveResolver} from './directive_resolver';
|
||||||
import {Identifiers, identifierToken} from './identifiers';
|
import {Identifiers, identifierToken} from './identifiers';
|
||||||
import {NgModuleResolver} from './ng_module_resolver';
|
import {NgModuleResolver} from './ng_module_resolver';
|
||||||
import {PipeResolver} from './pipe_resolver';
|
import {PipeResolver} from './pipe_resolver';
|
||||||
|
import {ElementSchemaRegistry} from './schema/element_schema_registry';
|
||||||
import {getUrlScheme} from './url_resolver';
|
import {getUrlScheme} from './url_resolver';
|
||||||
import {MODULE_SUFFIX, ValueTransformer, sanitizeIdentifier, visitValue} from './util';
|
import {MODULE_SUFFIX, ValueTransformer, sanitizeIdentifier, visitValue} from './util';
|
||||||
import {ViewResolver} from './view_resolver';
|
import {ViewResolver} from './view_resolver';
|
||||||
|
@ -38,6 +39,7 @@ export class CompileMetadataResolver {
|
||||||
private _ngModuleResolver: NgModuleResolver, private _directiveResolver: DirectiveResolver,
|
private _ngModuleResolver: NgModuleResolver, private _directiveResolver: DirectiveResolver,
|
||||||
private _pipeResolver: PipeResolver, private _viewResolver: ViewResolver,
|
private _pipeResolver: PipeResolver, private _viewResolver: ViewResolver,
|
||||||
private _config: CompilerConfig, private _console: Console,
|
private _config: CompilerConfig, private _console: Console,
|
||||||
|
private _schemaRegistry: ElementSchemaRegistry,
|
||||||
private _reflector: ReflectorReader = reflector) {}
|
private _reflector: ReflectorReader = reflector) {}
|
||||||
|
|
||||||
private sanitizeTokenName(token: any): string {
|
private sanitizeTokenName(token: any): string {
|
||||||
|
@ -124,6 +126,7 @@ export class CompileMetadataResolver {
|
||||||
var viewProviders: Array<cpl.CompileProviderMetadata|cpl.CompileTypeMetadata|any[]> = [];
|
var viewProviders: Array<cpl.CompileProviderMetadata|cpl.CompileTypeMetadata|any[]> = [];
|
||||||
var moduleUrl = staticTypeModuleUrl(directiveType);
|
var moduleUrl = staticTypeModuleUrl(directiveType);
|
||||||
var entryComponentTypes: cpl.CompileTypeMetadata[] = [];
|
var entryComponentTypes: cpl.CompileTypeMetadata[] = [];
|
||||||
|
let selector = dirMeta.selector;
|
||||||
if (dirMeta instanceof ComponentMetadata) {
|
if (dirMeta instanceof ComponentMetadata) {
|
||||||
var cmpMeta = <ComponentMetadata>dirMeta;
|
var cmpMeta = <ComponentMetadata>dirMeta;
|
||||||
var viewMeta = this._viewResolver.resolve(directiveType);
|
var viewMeta = this._viewResolver.resolve(directiveType);
|
||||||
|
@ -155,6 +158,14 @@ export class CompileMetadataResolver {
|
||||||
flattenArray(cmpMeta.entryComponents)
|
flattenArray(cmpMeta.entryComponents)
|
||||||
.map((cmp) => this.getTypeMetadata(cmp, staticTypeModuleUrl(cmp)));
|
.map((cmp) => this.getTypeMetadata(cmp, staticTypeModuleUrl(cmp)));
|
||||||
}
|
}
|
||||||
|
if (!selector) {
|
||||||
|
selector = this._schemaRegistry.getDefaultComponentElementName();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!selector) {
|
||||||
|
throw new BaseException(
|
||||||
|
`Directive ${stringify(directiveType)} has no selector, please add it!`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var providers: Array<cpl.CompileProviderMetadata|cpl.CompileTypeMetadata|any[]> = [];
|
var providers: Array<cpl.CompileProviderMetadata|cpl.CompileTypeMetadata|any[]> = [];
|
||||||
|
@ -170,7 +181,7 @@ export class CompileMetadataResolver {
|
||||||
viewQueries = this.getQueriesMetadata(dirMeta.queries, true, directiveType);
|
viewQueries = this.getQueriesMetadata(dirMeta.queries, true, directiveType);
|
||||||
}
|
}
|
||||||
meta = cpl.CompileDirectiveMetadata.create({
|
meta = cpl.CompileDirectiveMetadata.create({
|
||||||
selector: dirMeta.selector,
|
selector: selector,
|
||||||
exportAs: dirMeta.exportAs,
|
exportAs: dirMeta.exportAs,
|
||||||
isComponent: isPresent(templateMeta),
|
isComponent: isPresent(templateMeta),
|
||||||
type: this.getTypeMetadata(directiveType, moduleUrl),
|
type: this.getTypeMetadata(directiveType, moduleUrl),
|
||||||
|
|
|
@ -313,4 +313,6 @@ export class DomElementSchemaRegistry extends ElementSchemaRegistry {
|
||||||
var mappedPropName = StringMapWrapper.get(attrToPropMap, propName);
|
var mappedPropName = StringMapWrapper.get(attrToPropMap, propName);
|
||||||
return isPresent(mappedPropName) ? mappedPropName : propName;
|
return isPresent(mappedPropName) ? mappedPropName : propName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getDefaultComponentElementName(): string { return 'ng-component'; }
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,4 +12,5 @@ export abstract class ElementSchemaRegistry {
|
||||||
abstract hasProperty(tagName: string, propName: string, schemaMetas: SchemaMetadata[]): boolean;
|
abstract hasProperty(tagName: string, propName: string, schemaMetas: SchemaMetadata[]): boolean;
|
||||||
abstract securityContext(tagName: string, propName: string): any;
|
abstract securityContext(tagName: string, propName: string): any;
|
||||||
abstract getMappedPropName(propName: string): string;
|
abstract getMappedPropName(propName: string): string;
|
||||||
|
abstract getDefaultComponentElementName(): string;
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,4 +29,6 @@ export class MockSchemaRegistry implements ElementSchemaRegistry {
|
||||||
var result = this.attrPropMapping[attrName];
|
var result = this.attrPropMapping[attrName];
|
||||||
return isPresent(result) ? result : attrName;
|
return isPresent(result) ? result : attrName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getDefaultComponentElementName(): string { return 'ng-component'; }
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ import {isPresent, stringify, isBlank,} from '../../src/facade/lang';
|
||||||
import {BaseException} from '../../src/facade/exceptions';
|
import {BaseException} from '../../src/facade/exceptions';
|
||||||
import {PromiseWrapper, EventEmitter, ObservableWrapper, PromiseCompleter,} from '../../src/facade/async';
|
import {PromiseWrapper, EventEmitter, ObservableWrapper, PromiseCompleter,} from '../../src/facade/async';
|
||||||
|
|
||||||
import {Injector, Injectable, forwardRef, OpaqueToken, Inject, Host, SkipSelf, SkipSelfMetadata, OnDestroy, ReflectiveInjector} from '@angular/core';
|
import {Injector, Injectable, forwardRef, OpaqueToken, Inject, Host, SkipSelf, SkipSelfMetadata, OnDestroy, ReflectiveInjector, Compiler} from '@angular/core';
|
||||||
|
|
||||||
import {NgIf, NgFor, AsyncPipe} from '@angular/common';
|
import {NgIf, NgFor, AsyncPipe} from '@angular/common';
|
||||||
|
|
||||||
|
@ -1477,6 +1477,34 @@ function declareTests({useJit}: {useJit: boolean}) {
|
||||||
async.done();
|
async.done();
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
it('should throw when using directives without selector',
|
||||||
|
inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {
|
||||||
|
@Directive({})
|
||||||
|
class SomeDirective {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({selector: 'comp', template: '', directives: [SomeDirective]})
|
||||||
|
class SomeComponent {
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(() => tcb.createSync(SomeComponent))
|
||||||
|
.toThrowError(
|
||||||
|
`Directive ${stringify(SomeDirective)} has no selector, please add it!`);
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should use a default element name for components without selectors',
|
||||||
|
inject([Compiler, Injector], (compiler: Compiler, injector: Injector) => {
|
||||||
|
@Component({template: ''})
|
||||||
|
class SomeComponent {
|
||||||
|
}
|
||||||
|
|
||||||
|
const compFactory = compiler.compileComponentSync(SomeComponent);
|
||||||
|
expect(compFactory.selector).toBe('ng-component');
|
||||||
|
expect(
|
||||||
|
getDOM().nodeName(compFactory.create(injector).location.nativeElement).toLowerCase())
|
||||||
|
.toEqual('ng-component');
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('error handling', () => {
|
describe('error handling', () => {
|
||||||
|
|
Loading…
Reference in New Issue