Skip to content

Commit 9b39e49

Browse files
tboschhansl
authored andcommitted
fix(core): support components without a selector (angular#10331)
Components without a selector now get the selector `ng-component`. Directives without a selector will throw an error message. Closes angular#3464 Closes angular#10216
1 parent a67cc82 commit 9b39e49

File tree

8 files changed

+61
-7
lines changed

8 files changed

+61
-7
lines changed

modules/@angular/compiler-cli/src/codegen.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import * as path from 'path';
1717
import * as ts from 'typescript';
1818

1919
import {CompileMetadataResolver, DirectiveNormalizer, DomElementSchemaRegistry, HtmlParser, Lexer, NgModuleCompiler, Parser, StyleCompiler, TemplateParser, TypeScriptEmitter, ViewCompiler} from './compiler_private';
20+
import {Console} from './core_private';
2021
import {ReflectorHost, ReflectorHostContext} from './reflector_host';
2122
import {StaticAndDynamicReflectionCapabilities} from './static_reflection_capabilities';
2223
import {StaticReflector, StaticSymbol} from './static_reflector';
@@ -135,13 +136,15 @@ export class CodeGenerator {
135136
});
136137
const normalizer = new DirectiveNormalizer(xhr, urlResolver, htmlParser, config);
137138
const expressionParser = new Parser(new Lexer());
138-
const tmplParser = new TemplateParser(
139-
expressionParser, new DomElementSchemaRegistry(), htmlParser,
140-
/*console*/ null, []);
139+
const elementSchemaRegistry = new DomElementSchemaRegistry();
140+
const console = new Console();
141+
const tmplParser =
142+
new TemplateParser(expressionParser, elementSchemaRegistry, htmlParser, console, []);
141143
const resolver = new CompileMetadataResolver(
142144
new compiler.NgModuleResolver(staticReflector),
143145
new compiler.DirectiveResolver(staticReflector), new compiler.PipeResolver(staticReflector),
144-
new compiler.ViewResolver(staticReflector), config, /*console*/ null, staticReflector);
146+
new compiler.ViewResolver(staticReflector), config, console, elementSchemaRegistry,
147+
staticReflector);
145148
const offlineCompiler = new compiler.OfflineCompiler(
146149
resolver, normalizer, tmplParser, new StyleCompiler(urlResolver), new ViewCompiler(config),
147150
new NgModuleCompiler(), new TypeScriptEmitter(reflectorHost));

modules/@angular/compiler-cli/src/core_private.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,7 @@ export var ReflectorReader: typeof t.ReflectorReader = r.ReflectorReader;
1414
export type ReflectionCapabilities = t.ReflectionCapabilities;
1515
export var ReflectionCapabilities: typeof t.ReflectionCapabilities = r.ReflectionCapabilities;
1616

17+
export type Console = t.Console;
18+
export var Console: typeof t.Console = r.Console;
19+
1720
export var reflector: typeof t.reflector = r.reflector;

modules/@angular/compiler-cli/src/extract_i18n.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import {ViewEncapsulation} from '@angular/core';
2323

2424
import {StaticReflector} from './static_reflector';
2525
import {CompileMetadataResolver, HtmlParser, DirectiveNormalizer, Lexer, Parser, DomElementSchemaRegistry, TypeScriptEmitter, MessageExtractor, removeDuplicates, ExtractionResult, Message, ParseError, serializeXmb,} from './compiler_private';
26+
import {Console} from './core_private';
2627

2728
import {ReflectorHost} from './reflector_host';
2829
import {StaticAndDynamicReflectionCapabilities} from './static_reflection_capabilities';
@@ -146,10 +147,13 @@ class Extractor {
146147
});
147148
const normalizer = new DirectiveNormalizer(xhr, urlResolver, htmlParser, config);
148149
const expressionParser = new Parser(new Lexer());
150+
const elementSchemaRegistry = new DomElementSchemaRegistry();
151+
const console = new Console();
149152
const resolver = new CompileMetadataResolver(
150153
new compiler.NgModuleResolver(staticReflector),
151154
new compiler.DirectiveResolver(staticReflector), new compiler.PipeResolver(staticReflector),
152-
new compiler.ViewResolver(staticReflector), config, /*console*/ null, staticReflector);
155+
new compiler.ViewResolver(staticReflector), config, console, elementSchemaRegistry,
156+
staticReflector);
153157

154158
// TODO(vicb): handle implicit
155159
const extractor = new MessageExtractor(htmlParser, expressionParser, [], {});

modules/@angular/compiler/src/metadata_resolver.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {DirectiveResolver} from './directive_resolver';
2121
import {Identifiers, identifierToken} from './identifiers';
2222
import {NgModuleResolver} from './ng_module_resolver';
2323
import {PipeResolver} from './pipe_resolver';
24+
import {ElementSchemaRegistry} from './schema/element_schema_registry';
2425
import {getUrlScheme} from './url_resolver';
2526
import {MODULE_SUFFIX, ValueTransformer, sanitizeIdentifier, visitValue} from './util';
2627
import {ViewResolver} from './view_resolver';
@@ -38,6 +39,7 @@ export class CompileMetadataResolver {
3839
private _ngModuleResolver: NgModuleResolver, private _directiveResolver: DirectiveResolver,
3940
private _pipeResolver: PipeResolver, private _viewResolver: ViewResolver,
4041
private _config: CompilerConfig, private _console: Console,
42+
private _schemaRegistry: ElementSchemaRegistry,
4143
private _reflector: ReflectorReader = reflector) {}
4244

4345
private sanitizeTokenName(token: any): string {
@@ -124,6 +126,7 @@ export class CompileMetadataResolver {
124126
var viewProviders: Array<cpl.CompileProviderMetadata|cpl.CompileTypeMetadata|any[]> = [];
125127
var moduleUrl = staticTypeModuleUrl(directiveType);
126128
var entryComponentTypes: cpl.CompileTypeMetadata[] = [];
129+
let selector = dirMeta.selector;
127130
if (dirMeta instanceof ComponentMetadata) {
128131
var cmpMeta = <ComponentMetadata>dirMeta;
129132
var viewMeta = this._viewResolver.resolve(directiveType);
@@ -155,6 +158,14 @@ export class CompileMetadataResolver {
155158
flattenArray(cmpMeta.entryComponents)
156159
.map((cmp) => this.getTypeMetadata(cmp, staticTypeModuleUrl(cmp)));
157160
}
161+
if (!selector) {
162+
selector = this._schemaRegistry.getDefaultComponentElementName();
163+
}
164+
} else {
165+
if (!selector) {
166+
throw new BaseException(
167+
`Directive ${stringify(directiveType)} has no selector, please add it!`);
168+
}
158169
}
159170

160171
var providers: Array<cpl.CompileProviderMetadata|cpl.CompileTypeMetadata|any[]> = [];
@@ -170,7 +181,7 @@ export class CompileMetadataResolver {
170181
viewQueries = this.getQueriesMetadata(dirMeta.queries, true, directiveType);
171182
}
172183
meta = cpl.CompileDirectiveMetadata.create({
173-
selector: dirMeta.selector,
184+
selector: selector,
174185
exportAs: dirMeta.exportAs,
175186
isComponent: isPresent(templateMeta),
176187
type: this.getTypeMetadata(directiveType, moduleUrl),

modules/@angular/compiler/src/schema/dom_element_schema_registry.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,4 +313,6 @@ export class DomElementSchemaRegistry extends ElementSchemaRegistry {
313313
var mappedPropName = StringMapWrapper.get(attrToPropMap, propName);
314314
return isPresent(mappedPropName) ? mappedPropName : propName;
315315
}
316+
317+
getDefaultComponentElementName(): string { return 'ng-component'; }
316318
}

modules/@angular/compiler/src/schema/element_schema_registry.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,5 @@ export abstract class ElementSchemaRegistry {
1212
abstract hasProperty(tagName: string, propName: string, schemaMetas: SchemaMetadata[]): boolean;
1313
abstract securityContext(tagName: string, propName: string): any;
1414
abstract getMappedPropName(propName: string): string;
15+
abstract getDefaultComponentElementName(): string;
1516
}

modules/@angular/compiler/testing/schema_registry_mock.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,6 @@ export class MockSchemaRegistry implements ElementSchemaRegistry {
2929
var result = this.attrPropMapping[attrName];
3030
return isPresent(result) ? result : attrName;
3131
}
32+
33+
getDefaultComponentElementName(): string { return 'ng-component'; }
3234
}

modules/@angular/core/test/linker/integration_spec.ts

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {isPresent, stringify, isBlank,} from '../../src/facade/lang';
1414
import {BaseException} from '../../src/facade/exceptions';
1515
import {PromiseWrapper, EventEmitter, ObservableWrapper, PromiseCompleter,} from '../../src/facade/async';
1616

17-
import {Injector, Injectable, forwardRef, OpaqueToken, Inject, Host, SkipSelf, SkipSelfMetadata, OnDestroy, ReflectiveInjector} from '@angular/core';
17+
import {Injector, Injectable, forwardRef, OpaqueToken, Inject, Host, SkipSelf, SkipSelfMetadata, OnDestroy, ReflectiveInjector, Compiler} from '@angular/core';
1818

1919
import {NgIf, NgFor, AsyncPipe} from '@angular/common';
2020

@@ -1477,6 +1477,34 @@ function declareTests({useJit}: {useJit: boolean}) {
14771477
async.done();
14781478
});
14791479
}));
1480+
1481+
it('should throw when using directives without selector',
1482+
inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {
1483+
@Directive({})
1484+
class SomeDirective {
1485+
}
1486+
1487+
@Component({selector: 'comp', template: '', directives: [SomeDirective]})
1488+
class SomeComponent {
1489+
}
1490+
1491+
expect(() => tcb.createSync(SomeComponent))
1492+
.toThrowError(
1493+
`Directive ${stringify(SomeDirective)} has no selector, please add it!`);
1494+
}));
1495+
1496+
it('should use a default element name for components without selectors',
1497+
inject([Compiler, Injector], (compiler: Compiler, injector: Injector) => {
1498+
@Component({template: ''})
1499+
class SomeComponent {
1500+
}
1501+
1502+
const compFactory = compiler.compileComponentSync(SomeComponent);
1503+
expect(compFactory.selector).toBe('ng-component');
1504+
expect(
1505+
getDOM().nodeName(compFactory.create(injector).location.nativeElement).toLowerCase())
1506+
.toEqual('ng-component');
1507+
}));
14801508
});
14811509

14821510
describe('error handling', () => {

0 commit comments

Comments
 (0)