refactor(ivy): generate ngFactoryDef for injectables (#32433)

With #31953 we moved the factories for components, directives and pipes into a new field called `ngFactoryDef`, however I decided not to do it for injectables, because they needed some extra logic. These changes set up the `ngFactoryDef` for injectables as well.

For reference, the extra logic mentioned above is that for injectables we have two code paths:

1. For injectables that don't configure how they should be instantiated, we create a `factory` that proxies to `ngFactoryDef`:

```
// Source
@Injectable()
class Service {}

// Output
class Service {
  static ngInjectableDef = defineInjectable({
    factory: () => Service.ngFactoryFn(),
  });

  static ngFactoryFn: (t) => new (t || Service)();
}
```

2. For injectables that do configure how they're created, we keep the `ngFactoryDef` and generate the factory based on the metadata:

```
// Source
@Injectable({
  useValue: DEFAULT_IMPL,
})
class Service {}

// Output
export class Service {
  static ngInjectableDef = defineInjectable({
    factory: () => DEFAULT_IMPL,
  });

  static ngFactoryFn: (t) => new (t || Service)();
}
```

PR Close #32433
This commit is contained in:
crisbeto
2019-09-01 12:26:04 +02:00
committed by atscott
parent 2729747225
commit 4e35e348af
33 changed files with 695 additions and 295 deletions

View File

@ -8,7 +8,9 @@
import {R3InjectableMetadataFacade, getCompilerFacade} from '../../compiler/compiler_facade';
import {Type} from '../../interface/type';
import {NG_FACTORY_DEF} from '../../render3/fields';
import {getClosureSafeProperty} from '../../util/property';
import {resolveForwardRef} from '../forward_ref';
import {Injectable} from '../injectable';
import {NG_INJECTABLE_DEF} from '../interface/defs';
import {ClassSansProvider, ExistingSansProvider, FactorySansProvider, ValueProvider, ValueSansProvider} from '../interface/provider';
@ -23,59 +25,45 @@ import {convertDependencies, reflectDependencies} from './util';
* `ngInjectableDef` onto the injectable type.
*/
export function compileInjectable(type: Type<any>, srcMeta?: Injectable): void {
let def: any = null;
let ngInjectableDef: any = null;
let ngFactoryDef: any = null;
// if NG_INJECTABLE_DEF is already defined on this class then don't overwrite it
if (type.hasOwnProperty(NG_INJECTABLE_DEF)) return;
Object.defineProperty(type, NG_INJECTABLE_DEF, {
get: () => {
if (def === null) {
// Allow the compilation of a class with a `@Injectable()` decorator without parameters
const meta: Injectable = srcMeta || {providedIn: null};
const hasAProvider = isUseClassProvider(meta) || isUseFactoryProvider(meta) ||
isUseValueProvider(meta) || isUseExistingProvider(meta);
const compilerMeta: R3InjectableMetadataFacade = {
name: type.name,
type: type,
typeArgumentCount: 0,
providedIn: meta.providedIn,
ctorDeps: reflectDependencies(type),
userDeps: undefined,
};
if ((isUseClassProvider(meta) || isUseFactoryProvider(meta)) && meta.deps !== undefined) {
compilerMeta.userDeps = convertDependencies(meta.deps);
if (!type.hasOwnProperty(NG_INJECTABLE_DEF)) {
Object.defineProperty(type, NG_INJECTABLE_DEF, {
get: () => {
if (ngInjectableDef === null) {
ngInjectableDef = getCompilerFacade().compileInjectable(
angularCoreDiEnv, `ng:///${type.name}/ngInjectableDef.js`,
getInjectableMetadata(type, srcMeta));
}
if (!hasAProvider) {
// In the case the user specifies a type provider, treat it as {provide: X, useClass: X}.
// The deps will have been reflected above, causing the factory to create the class by
// calling
// its constructor with injected deps.
compilerMeta.useClass = type;
} else if (isUseClassProvider(meta)) {
// The user explicitly specified useClass, and may or may not have provided deps.
compilerMeta.useClass = meta.useClass;
} else if (isUseValueProvider(meta)) {
// The user explicitly specified useValue.
compilerMeta.useValue = meta.useValue;
} else if (isUseFactoryProvider(meta)) {
// The user explicitly specified useFactory.
compilerMeta.useFactory = meta.useFactory;
} else if (isUseExistingProvider(meta)) {
// The user explicitly specified useExisting.
compilerMeta.useExisting = meta.useExisting;
} else {
// Can't happen - either hasAProvider will be false, or one of the providers will be set.
throw new Error(`Unreachable state.`);
return ngInjectableDef;
},
});
}
// if NG_FACTORY_DEF is already defined on this class then don't overwrite it
if (!type.hasOwnProperty(NG_FACTORY_DEF)) {
Object.defineProperty(type, NG_FACTORY_DEF, {
get: () => {
if (ngFactoryDef === null) {
const metadata = getInjectableMetadata(type, srcMeta);
ngFactoryDef = getCompilerFacade().compileFactory(
angularCoreDiEnv, `ng:///${type.name}/ngFactoryDef.js`, {
name: metadata.name,
type: metadata.type,
typeArgumentCount: metadata.typeArgumentCount,
deps: reflectDependencies(type),
injectFn: 'inject',
isPipe: false
});
}
def = getCompilerFacade().compileInjectable(
angularCoreDiEnv, `ng:///${type.name}/ngInjectableDef.js`, compilerMeta);
}
return def;
},
});
return ngFactoryDef;
},
// Leave this configurable so that the factories from directives or pipes can take precedence.
configurable: true
});
}
}
type UseClassProvider = Injectable & ClassSansProvider & {deps?: any[]};
@ -98,3 +86,32 @@ function isUseFactoryProvider(meta: Injectable): meta is Injectable&FactorySansP
function isUseExistingProvider(meta: Injectable): meta is Injectable&ExistingSansProvider {
return (meta as ExistingSansProvider).useExisting !== undefined;
}
function getInjectableMetadata(type: Type<any>, srcMeta?: Injectable): R3InjectableMetadataFacade {
// Allow the compilation of a class with a `@Injectable()` decorator without parameters
const meta: Injectable = srcMeta || {providedIn: null};
const compilerMeta: R3InjectableMetadataFacade = {
name: type.name,
type: type,
typeArgumentCount: 0,
providedIn: meta.providedIn,
userDeps: undefined,
};
if ((isUseClassProvider(meta) || isUseFactoryProvider(meta)) && meta.deps !== undefined) {
compilerMeta.userDeps = convertDependencies(meta.deps);
}
if (isUseClassProvider(meta)) {
// The user explicitly specified useClass, and may or may not have provided deps.
compilerMeta.useClass = resolveForwardRef(meta.useClass);
} else if (isUseValueProvider(meta)) {
// The user explicitly specified useValue.
compilerMeta.useValue = resolveForwardRef(meta.useValue);
} else if (isUseFactoryProvider(meta)) {
// The user explicitly specified useFactory.
compilerMeta.useFactory = meta.useFactory;
} else if (isUseExistingProvider(meta)) {
// The user explicitly specified useExisting.
compilerMeta.useExisting = resolveForwardRef(meta.useExisting);
}
return compilerMeta;
}