fix(ivy): should support components without selector (#27169)
PR Close #27169
This commit is contained in:
@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ConstantPool, CssSelector, Expression, R3ComponentMetadata, R3DirectiveMetadata, SelectorMatcher, Statement, TmplAstNode, WrappedNodeExpr, compileComponentFromMetadata, makeBindingParser, parseTemplate} from '@angular/compiler';
|
||||
import {ConstantPool, CssSelector, DomElementSchemaRegistry, ElementSchemaRegistry, Expression, R3ComponentMetadata, R3DirectiveMetadata, SelectorMatcher, Statement, TmplAstNode, WrappedNodeExpr, compileComponentFromMetadata, makeBindingParser, parseTemplate} from '@angular/compiler';
|
||||
import * as path from 'path';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
@ -41,6 +41,7 @@ export class ComponentDecoratorHandler implements
|
||||
private resourceLoader: ResourceLoader, private rootDirs: string[]) {}
|
||||
|
||||
private literalCache = new Map<Decorator, ts.ObjectLiteralExpression>();
|
||||
private elementSchemaRegistry = new DomElementSchemaRegistry();
|
||||
|
||||
|
||||
detect(node: ts.Declaration, decorators: Decorator[]|null): Decorator|undefined {
|
||||
@ -74,8 +75,9 @@ export class ComponentDecoratorHandler implements
|
||||
|
||||
// @Component inherits @Directive, so begin by extracting the @Directive metadata and building
|
||||
// on it.
|
||||
const directiveResult =
|
||||
extractDirectiveMetadata(node, decorator, this.checker, this.reflector, this.isCore);
|
||||
const directiveResult = extractDirectiveMetadata(
|
||||
node, decorator, this.checker, this.reflector, this.isCore,
|
||||
this.elementSchemaRegistry.getDefaultComponentElementName());
|
||||
if (directiveResult === undefined) {
|
||||
// `extractDirectiveMetadata` returns undefined when the @Directive has `jit: true`. In this
|
||||
// case, compilation of the decorator is skipped. Returning an empty object signifies
|
||||
|
@ -93,7 +93,7 @@ export class DirectiveDecoratorHandler implements
|
||||
*/
|
||||
export function extractDirectiveMetadata(
|
||||
clazz: ts.ClassDeclaration, decorator: Decorator, checker: ts.TypeChecker,
|
||||
reflector: ReflectionHost, isCore: boolean): {
|
||||
reflector: ReflectionHost, isCore: boolean, defaultSelector: string | null = null): {
|
||||
decorator: Map<string, ts.Expression>,
|
||||
metadata: R3DirectiveMetadata,
|
||||
decoratedElements: ClassMember[],
|
||||
@ -154,7 +154,7 @@ export function extractDirectiveMetadata(
|
||||
}
|
||||
|
||||
// Parse the selector.
|
||||
let selector = '';
|
||||
let selector = defaultSelector;
|
||||
if (directive.has('selector')) {
|
||||
const expr = directive.get('selector') !;
|
||||
const resolved = staticallyResolve(expr, reflector, checker);
|
||||
|
@ -561,6 +561,62 @@ describe('compiler compliance', () => {
|
||||
expectEmit(source, OtherDirectiveDefinition, 'Incorrect OtherDirective.ngDirectiveDef');
|
||||
});
|
||||
|
||||
it('should support components without selector', () => {
|
||||
const files = {
|
||||
app: {
|
||||
'spec.ts': `
|
||||
import {Component, Directive, NgModule} from '@angular/core';
|
||||
|
||||
@Directive({})
|
||||
export class EmptyOutletDirective {}
|
||||
|
||||
@Component({template: '<router-outlet></router-outlet>'})
|
||||
export class EmptyOutletComponent {}
|
||||
|
||||
@NgModule({declarations: [EmptyOutletComponent]})
|
||||
export class MyModule{}
|
||||
`
|
||||
}
|
||||
};
|
||||
|
||||
// EmptyOutletDirective definition should be:
|
||||
const EmptyOutletDirectiveDefinition = `
|
||||
…
|
||||
EmptyOutletDirective.ngDirectiveDef = $r3$.ɵdefineDirective({
|
||||
type: EmptyOutletDirective,
|
||||
selectors: [],
|
||||
factory: function EmptyOutletDirective_Factory(t) { return new (t || EmptyOutletDirective)(); }
|
||||
});
|
||||
`;
|
||||
|
||||
// EmptyOutletComponent definition should be:
|
||||
const EmptyOutletComponentDefinition = `
|
||||
…
|
||||
EmptyOutletComponent.ngComponentDef = $r3$.ɵdefineComponent({
|
||||
type: EmptyOutletComponent,
|
||||
selectors: [["ng-component"]],
|
||||
factory: function EmptyOutletComponent_Factory(t) { return new (t || EmptyOutletComponent)(); },
|
||||
consts: 1,
|
||||
vars: 0,
|
||||
template: function EmptyOutletComponent_Template(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵelement(0, "router-outlet");
|
||||
}
|
||||
},
|
||||
encapsulation: 2
|
||||
});
|
||||
`;
|
||||
|
||||
const result = compile(files, angularFiles);
|
||||
const source = result.source;
|
||||
|
||||
expectEmit(
|
||||
source, EmptyOutletDirectiveDefinition,
|
||||
'Incorrect EmptyOutletDirective.ngDirectiveDefDef');
|
||||
expectEmit(
|
||||
source, EmptyOutletComponentDefinition, 'Incorrect EmptyOutletComponent.ngComponentDef');
|
||||
});
|
||||
|
||||
it('should not treat ElementRef, ViewContainerRef, or ChangeDetectorRef specially when injecting',
|
||||
() => {
|
||||
const files = {
|
||||
|
Reference in New Issue
Block a user