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:
atscott
2019-08-12 14:56:30 -07:00
committed by Andrew Kushnir
parent bb3c684b98
commit cfed0c0cf1
19 changed files with 169 additions and 79 deletions

View File

@ -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);

View File

@ -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' ||

View File

@ -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 {

View File

@ -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);
});