fix(ivy): allow abstract directives to have an invalid constructor (#32987)

For abstract directives, i.e. directives without a selector, it may
happen that their constructor is called explicitly from a subclass,
hence its parameters are not required to be valid for Angular's DI
purposes. Prior to this commit however, having an abstract directive
with a constructor that has parameters that are not eligible for
Angular's DI would produce a compilation error.

A similar scenario may occur for `@Injectable`s, where an explicit
`use*` definition allows for the constructor to be irrelevant. For
example, the situation where `useFactory` is specified allows for the
constructor to be called explicitly with any value, so its constructor
parameters are not required to be valid. For `@Injectable`s this is
handled by generating a DI factory function that throws.

This commit implements the same solution for abstract directives, such
that a compilation error is avoided while still producing an error at
runtime if the type is instantiated implicitly by Angular's DI
mechanism.

Fixes #32981

PR Close #32987
This commit is contained in:
JoostK
2019-10-03 21:54:49 +02:00
committed by Andrew Kushnir
parent e4e8dbdee0
commit 8d15bfa6ee
28 changed files with 307 additions and 117 deletions

View File

@ -56,6 +56,7 @@ export {
ɵɵcontainerRefreshStart,
ɵɵdirectiveInject,
ɵɵinvalidFactory,
ɵɵelement,
ɵɵelementContainer,

View File

@ -59,3 +59,21 @@ export function ɵɵdirectiveInject<T>(
export function ɵɵinjectAttribute(attrNameToInject: string): string|null {
return injectAttributeImpl(getPreviousOrParentTNode(), attrNameToInject);
}
/**
* Throws an error indicating that a factory function could not be generated by the compiler for a
* particular class.
*
* This instruction allows the actual error message to be optimized away when ngDevMode is turned
* off, saving bytes of generated code while still providing a good experience in dev mode.
*
* The name of the class is not mentioned here, but will be in the generated factory function name
* and thus in the stack trace.
*
* @codeGenApi
*/
export function ɵɵinvalidFactory(): never {
const msg =
ngDevMode ? `This constructor was not compatible with Dependency Injection.` : 'invalid';
throw new Error(msg);
}

View File

@ -165,9 +165,12 @@ function addDirectiveFactoryDef(type: Type<any>, metadata: Directive | Component
get: () => {
if (ngFactoryDef === null) {
const meta = getDirectiveMetadata(type, metadata);
ngFactoryDef = getCompilerFacade().compileFactory(
angularCoreEnv, `ng:///${type.name}/ɵfac.js`,
{...meta.metadata, injectFn: 'directiveInject', isPipe: false});
const compiler = getCompilerFacade();
ngFactoryDef = compiler.compileFactory(angularCoreEnv, `ng:///${type.name}/ɵfac.js`, {
...meta.metadata,
injectFn: 'directiveInject',
target: compiler.R3FactoryTarget.Directive
});
}
return ngFactoryDef;
},

View File

@ -42,6 +42,7 @@ export const angularCoreEnv: {[name: string]: Function} =
'ɵɵgetInheritedFactory': r3.ɵɵgetInheritedFactory,
'ɵɵinject': ɵɵinject,
'ɵɵinjectAttribute': r3.ɵɵinjectAttribute,
'ɵɵinvalidFactory': r3.ɵɵinvalidFactory,
'ɵɵinjectPipeChangeDetectorRef': r3.ɵɵinjectPipeChangeDetectorRef,
'ɵɵtemplateRefExtractor': r3.ɵɵtemplateRefExtractor,
'ɵɵNgOnChangesFeature': r3.ɵɵNgOnChangesFeature,

View File

@ -22,9 +22,10 @@ export function compilePipe(type: Type<any>, meta: Pipe): void {
get: () => {
if (ngFactoryDef === null) {
const metadata = getPipeMetadata(type, meta);
ngFactoryDef = getCompilerFacade().compileFactory(
const compiler = getCompilerFacade();
ngFactoryDef = compiler.compileFactory(
angularCoreEnv, `ng:///${metadata.name}/ɵfac.js`,
{...metadata, injectFn: 'directiveInject', isPipe: true});
{...metadata, injectFn: 'directiveInject', target: compiler.R3FactoryTarget.Pipe});
}
return ngFactoryDef;
},