fix(ivy): Support selector-less directive as base classes (#32125)
Following #31379, this adds support for directives without a selector to Ivy. PR Close #32125
This commit is contained in:
@ -117,7 +117,7 @@ function hasSelectorScope<T>(component: Type<T>): component is Type<T>&
|
||||
* In the event that compilation is not immediate, `compileDirective` will return a `Promise` which
|
||||
* will resolve when compilation completes and the directive becomes usable.
|
||||
*/
|
||||
export function compileDirective(type: Type<any>, directive: Directive): void {
|
||||
export function compileDirective(type: Type<any>, directive: Directive | null): void {
|
||||
let ngDirectiveDef: any = null;
|
||||
Object.defineProperty(type, NG_DIRECTIVE_DEF, {
|
||||
get: () => {
|
||||
@ -125,7 +125,10 @@ export function compileDirective(type: Type<any>, directive: Directive): void {
|
||||
const name = type && type.name;
|
||||
const sourceMapUrl = `ng:///${name}/ngDirectiveDef.js`;
|
||||
const compiler = getCompilerFacade();
|
||||
const facade = directiveMetadata(type as ComponentType<any>, directive);
|
||||
// `directive` can be null in the case of abstract directives as a base class
|
||||
// that use `@Directive()` with no selector. In that case, pass empty object to the
|
||||
// `directiveMetadata` function instead of null.
|
||||
const facade = directiveMetadata(type as ComponentType<any>, directive || {});
|
||||
facade.typeSourceSpan = compiler.createParseSourceSpan('Directive', name, sourceMapUrl);
|
||||
if (facade.usesInheritance) {
|
||||
addBaseDefToUndecoratedParents(type);
|
||||
|
@ -185,6 +185,7 @@ function verifySemanticsOfNgModuleDef(
|
||||
});
|
||||
const exports = maybeUnwrapFn(ngModuleDef.exports);
|
||||
declarations.forEach(verifyDeclarationsHaveDefinitions);
|
||||
declarations.forEach(verifyDirectivesHaveSelector);
|
||||
const combinedDeclarations: Type<any>[] = [
|
||||
...declarations.map(resolveForwardRef),
|
||||
...flatten(imports.map(computeCombinedExports)).map(resolveForwardRef),
|
||||
@ -220,6 +221,14 @@ function verifySemanticsOfNgModuleDef(
|
||||
}
|
||||
}
|
||||
|
||||
function verifyDirectivesHaveSelector(type: Type<any>): void {
|
||||
type = resolveForwardRef(type);
|
||||
const def = getDirectiveDef(type);
|
||||
if (!getComponentDef(type) && def && def.selectors.length == 0) {
|
||||
errors.push(`Directive ${stringifyForError(type)} has no selector, please add it!`);
|
||||
}
|
||||
}
|
||||
|
||||
function verifyExportsAreDeclaredOrReExported(type: Type<any>) {
|
||||
type = resolveForwardRef(type);
|
||||
const kind = getComponentDef(type) && 'component' || getDirectiveDef(type) && 'directive' ||
|
||||
|
@ -1411,7 +1411,7 @@ function declareTests(config?: {useJit: boolean}) {
|
||||
expect(getDOM().querySelectorAll(fixture.nativeElement, 'script').length).toEqual(0);
|
||||
});
|
||||
|
||||
it('should throw when using directives without selector', () => {
|
||||
it('should throw when using directives without selector in NgModule declarations', () => {
|
||||
@Directive({})
|
||||
class SomeDirective {
|
||||
}
|
||||
@ -1425,6 +1425,38 @@ function declareTests(config?: {useJit: boolean}) {
|
||||
.toThrowError(`Directive ${stringify(SomeDirective)} has no selector, please add it!`);
|
||||
});
|
||||
|
||||
it('should not throw when using directives without selector as base class not in declarations',
|
||||
() => {
|
||||
@Directive({})
|
||||
abstract class Base {
|
||||
constructor(readonly injector: Injector) {}
|
||||
}
|
||||
|
||||
@Directive()
|
||||
abstract class EmptyDir {
|
||||
}
|
||||
|
||||
@Directive({inputs: ['a', 'b']})
|
||||
class TestDirWithInputs {
|
||||
}
|
||||
|
||||
@Component({selector: 'comp', template: ''})
|
||||
class SomeComponent extends Base {
|
||||
}
|
||||
|
||||
@Component({selector: 'comp2', template: ''})
|
||||
class SomeComponent2 extends EmptyDir {
|
||||
}
|
||||
|
||||
@Component({selector: 'comp3', template: ''})
|
||||
class SomeComponent3 extends TestDirWithInputs {
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule(
|
||||
{declarations: [MyComp, SomeComponent, SomeComponent2, SomeComponent3]});
|
||||
expect(() => TestBed.createComponent(MyComp)).not.toThrowError();
|
||||
});
|
||||
|
||||
it('should throw when using directives with empty string selector', () => {
|
||||
@Directive({selector: ''})
|
||||
class SomeDirective {
|
||||
|
@ -299,7 +299,7 @@ export class R3TestBedCompiler {
|
||||
this.pendingComponents.clear();
|
||||
|
||||
this.pendingDirectives.forEach(declaration => {
|
||||
const metadata = this.resolvers.directive.resolve(declaration) !;
|
||||
const metadata = this.resolvers.directive.resolve(declaration);
|
||||
this.maybeStoreNgDef(NG_DIRECTIVE_DEF, declaration);
|
||||
compileDirective(declaration, metadata);
|
||||
});
|
||||
|
Reference in New Issue
Block a user