JoostK 8d15bfa6ee 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
2019-10-25 12:13:23 -07:00

80 lines
2.8 KiB
TypeScript

/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {InjectFlags, InjectionToken, resolveForwardRef} from '../../di';
import {ɵɵinject} from '../../di/injector_compatibility';
import {Type} from '../../interface/type';
import {getOrCreateInjectable, injectAttributeImpl} from '../di';
import {TContainerNode, TElementContainerNode, TElementNode} from '../interfaces/node';
import {getLView, getPreviousOrParentTNode} from '../state';
/**
* Returns the value associated to the given token from the injectors.
*
* `directiveInject` is intended to be used for directive, component and pipe factories.
* All other injection use `inject` which does not walk the node injector tree.
*
* Usage example (in factory function):
*
* ```ts
* class SomeDirective {
* constructor(directive: DirectiveA) {}
*
* static ɵdir = ɵɵdefineDirective({
* type: SomeDirective,
* factory: () => new SomeDirective(ɵɵdirectiveInject(DirectiveA))
* });
* }
* ```
* @param token the type or token to inject
* @param flags Injection flags
* @returns the value from the injector or `null` when not found
*
* @codeGenApi
*/
export function ɵɵdirectiveInject<T>(token: Type<T>| InjectionToken<T>): T;
export function ɵɵdirectiveInject<T>(token: Type<T>| InjectionToken<T>, flags: InjectFlags): T;
export function ɵɵdirectiveInject<T>(
token: Type<T>| InjectionToken<T>, flags = InjectFlags.Default): T|null {
token = resolveForwardRef(token);
const lView = getLView();
// Fall back to inject() if view hasn't been created. This situation can happen in tests
// if inject utilities are used before bootstrapping.
if (lView == null) return ɵɵinject(token, flags);
return getOrCreateInjectable<T>(
getPreviousOrParentTNode() as TElementNode | TContainerNode | TElementContainerNode, lView,
token, flags);
}
/**
* Facade for the attribute injection from DI.
*
* @codeGenApi
*/
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);
}