Question presented to candidate:
"Here are two versions of the same component: one takes Logger and Api as constructor parameters, the other has logger = inject(Logger) fields. Explain what each one compiles to, where inject() is allowed to run, and what changes when the class extends a base class."
What a strong answer should cover:
- Constructor injection: the Angular compiler reads parameter types at build time and generates a factory calling
ɵɵdirectiveInject(Type, flags)per parameter; decorators like@Optional()and@SkipSelf()become bit flags. inject(): the factory takes no arguments; theinject()calls run inside the class while Angular is constructing it, using the same resolution machinery and the same options (optional,self,skipSelf,host).- Injection context:
inject()only works during construction (field initializers, constructor), in provider factories, in functional guards/interceptors/initializers, or insiderunInInjectionContext; elsewhere it throws NG0203. - Inheritance: with constructor DI every subclass must repeat and forward the parent's dependencies through
super(...); withinject()the base class injects for itself andsuper()takes no arguments. - Field-initializer ordering:
inject()fields can be used by later fields; a field that reads a constructor parameter property is a TypeScript error (TS2729). - Without the Angular compiler (plain transpile), constructor parameter types are lost and DI fails with NG0202;
inject()does not depend on type metadata.
Clarifying questions expected:
- "Do you want the generated code, or just the behavioural differences?"
Code / implementation expected: Yes — the same class in both styles, ideally with a base class, and the compiled factory lines.