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 {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 {StaticAndDynamicReflectionCapabilities} from './static_reflection_capabilities'; | ||||
| import {StaticReflector, StaticSymbol} from './static_reflector'; | ||||
| @ -135,13 +136,15 @@ export class CodeGenerator { | ||||
|     }); | ||||
|     const normalizer = new DirectiveNormalizer(xhr, urlResolver, htmlParser, config); | ||||
|     const expressionParser = new Parser(new Lexer()); | ||||
|     const tmplParser = new TemplateParser( | ||||
|         expressionParser, new DomElementSchemaRegistry(), htmlParser, | ||||
|         /*console*/ null, []); | ||||
|     const elementSchemaRegistry = new DomElementSchemaRegistry(); | ||||
|     const console = new Console(); | ||||
|     const tmplParser = | ||||
|         new TemplateParser(expressionParser, elementSchemaRegistry, htmlParser, console, []); | ||||
|     const resolver = new CompileMetadataResolver( | ||||
|         new compiler.NgModuleResolver(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( | ||||
|         resolver, normalizer, tmplParser, new StyleCompiler(urlResolver), new ViewCompiler(config), | ||||
|         new NgModuleCompiler(), new TypeScriptEmitter(reflectorHost)); | ||||
|  | ||||
| @ -14,4 +14,7 @@ export var ReflectorReader: typeof t.ReflectorReader = r.ReflectorReader; | ||||
| export type ReflectionCapabilities = t.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; | ||||
|  | ||||
| @ -23,6 +23,7 @@ import {ViewEncapsulation} from '@angular/core'; | ||||
| 
 | ||||
| import {StaticReflector} from './static_reflector'; | ||||
| 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 {StaticAndDynamicReflectionCapabilities} from './static_reflection_capabilities'; | ||||
| @ -146,10 +147,13 @@ class Extractor { | ||||
|     }); | ||||
|     const normalizer = new DirectiveNormalizer(xhr, urlResolver, htmlParser, config); | ||||
|     const expressionParser = new Parser(new Lexer()); | ||||
|     const elementSchemaRegistry = new DomElementSchemaRegistry(); | ||||
|     const console = new Console(); | ||||
|     const resolver = new CompileMetadataResolver( | ||||
|         new compiler.NgModuleResolver(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
 | ||||
|     const extractor = new MessageExtractor(htmlParser, expressionParser, [], {}); | ||||
|  | ||||
| @ -21,6 +21,7 @@ import {DirectiveResolver} from './directive_resolver'; | ||||
| import {Identifiers, identifierToken} from './identifiers'; | ||||
| import {NgModuleResolver} from './ng_module_resolver'; | ||||
| import {PipeResolver} from './pipe_resolver'; | ||||
| import {ElementSchemaRegistry} from './schema/element_schema_registry'; | ||||
| import {getUrlScheme} from './url_resolver'; | ||||
| import {MODULE_SUFFIX, ValueTransformer, sanitizeIdentifier, visitValue} from './util'; | ||||
| import {ViewResolver} from './view_resolver'; | ||||
| @ -38,6 +39,7 @@ export class CompileMetadataResolver { | ||||
|       private _ngModuleResolver: NgModuleResolver, private _directiveResolver: DirectiveResolver, | ||||
|       private _pipeResolver: PipeResolver, private _viewResolver: ViewResolver, | ||||
|       private _config: CompilerConfig, private _console: Console, | ||||
|       private _schemaRegistry: ElementSchemaRegistry, | ||||
|       private _reflector: ReflectorReader = reflector) {} | ||||
| 
 | ||||
|   private sanitizeTokenName(token: any): string { | ||||
| @ -124,6 +126,7 @@ export class CompileMetadataResolver { | ||||
|       var viewProviders: Array<cpl.CompileProviderMetadata|cpl.CompileTypeMetadata|any[]> = []; | ||||
|       var moduleUrl = staticTypeModuleUrl(directiveType); | ||||
|       var entryComponentTypes: cpl.CompileTypeMetadata[] = []; | ||||
|       let selector = dirMeta.selector; | ||||
|       if (dirMeta instanceof ComponentMetadata) { | ||||
|         var cmpMeta = <ComponentMetadata>dirMeta; | ||||
|         var viewMeta = this._viewResolver.resolve(directiveType); | ||||
| @ -155,6 +158,14 @@ export class CompileMetadataResolver { | ||||
|               flattenArray(cmpMeta.entryComponents) | ||||
|                   .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[]> = []; | ||||
| @ -170,7 +181,7 @@ export class CompileMetadataResolver { | ||||
|         viewQueries = this.getQueriesMetadata(dirMeta.queries, true, directiveType); | ||||
|       } | ||||
|       meta = cpl.CompileDirectiveMetadata.create({ | ||||
|         selector: dirMeta.selector, | ||||
|         selector: selector, | ||||
|         exportAs: dirMeta.exportAs, | ||||
|         isComponent: isPresent(templateMeta), | ||||
|         type: this.getTypeMetadata(directiveType, moduleUrl), | ||||
|  | ||||
| @ -313,4 +313,6 @@ export class DomElementSchemaRegistry extends ElementSchemaRegistry { | ||||
|     var mappedPropName = StringMapWrapper.get(attrToPropMap, 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 securityContext(tagName: string, propName: string): any; | ||||
|   abstract getMappedPropName(propName: string): string; | ||||
|   abstract getDefaultComponentElementName(): string; | ||||
| } | ||||
|  | ||||
| @ -29,4 +29,6 @@ export class MockSchemaRegistry implements ElementSchemaRegistry { | ||||
|     var result = this.attrPropMapping[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 {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'; | ||||
| 
 | ||||
| @ -1477,6 +1477,34 @@ function declareTests({useJit}: {useJit: boolean}) { | ||||
|                      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', () => { | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user