In View Engine code, components and directives can inherit from base classes which also have Angular decorators. Data passed to annotations for the decorators will inherit properly between base and derived classes. This works as the metadata/summary system retains enough global information to resolve inheritance structures at compile time.
Consider the following structure:
@Directive({...})
export class Base {
constructor(readonly dep: Dep) {}
}
@Directive({...})
export class Derived extends Base {}
Any process which attempts to instantiate Derived
must somehow deal with the fact that the constructor parameter types are defined on Base
, which could come from a different library or compilation unit. The goal is to generate a function that constructs Derived
by injecting its dependencies:
factory: () => new Derived(inject(Dep)),
To support this, it's essential to generate Base
in such a way that its factory can be used to construct Derived
. The proposed generated code is:
import {delegateDirectiveCtor} from '@angular/core';
export class Base {
static ngDirectiveDef = defineDirective({
factory: (derived?) => new (derived || Base)(inject(Dep)),
});
}
export class Derived extends Base {
static ngDirectiveDef = defineDirective({
factory: (derived?) => delegateDirectiveCtor(Base, derived || Derived),
});
}
When Base.factory()
is called directly, a new instance of Base
is created. When Derived.factory()
is called, it invokes Base.factory(Derived)
and a new instance of Derived
is constructed using Base
's constructor injection.
The runtime shape of ngDirectiveDef
objects is private API, and generated code cannot directly access it. A function wrapper is used to construct the Derived
class using the base class's factory function.
Deep inheritance is also supported by having Derived
's factory function also take a derived type argument.
For @Injectable
, it's a bit trickier. @Injectable
s have factory functions which don't always instantiate the given class. For example, an @Injectable
with useFactory
and separate deps
will declare a factory function as follows:
@Injectable({
useFactory: makeBase,
deps: [FactoryDep],
})
export class Base {
constructor(readonly dep: Dep) {}
static ngInjectableDef = defineInjectable({
factory: (derived?) => derived ? new Derived(inject(Dep)) : makeBase(inject(FactoryDep)),
});
}
In the case where a parameter of an @Injectable
constructor is unresolvable (for example, the factory function generated will call unresolvable()
instead of inject
for that parameter, which will throw a helpful error indicating that the parameter could not be resolved.