The @NgModule factory reads @Optional, @Self, @SkipSelf, @Host and @Attribute on constructor parameters, but not @Inject. The token comes from the type annotation instead:
- type with no token (
unknown, an interface, a primitive) → ɵɵinvalidFactoryDep(i), and the module throws NG0202 when it is created;
- class type → the class is injected instead of the
@Inject token, with no error.
Same result for an aliased import (import {Inject as Inj}). @Injectable, @Pipe and the other handlers read @Inject fine.
Repro
import {Inject, InjectionToken, NgModule} from '@angular/core';
export const TOKEN = new InjectionToken<unknown>('TOKEN');
@NgModule({})
export class M {
constructor(@Inject(TOKEN) x: unknown) {}
}
import {Inject, Injectable, NgModule} from '@angular/core';
@Injectable({providedIn: 'root'}) export class Real {}
@Injectable({providedIn: 'root'}) export class Other {}
@NgModule({})
export class M {
constructor(@Inject(Other) x: Real) {}
}
OXC (#504 tip and main, same output)
static ɵfac = function M_Factory(__ngFactoryType__) {
return new (__ngFactoryType__ || M)(i0.ɵɵinvalidFactoryDep(0));
};
// at runtime: NG0202: This constructor is not compatible with Angular Dependency Injection
// because its dependency at index 0 of the parameter list is invalid.
return new (__ngFactoryType__ || M)(i0.ɵɵinject(Real)); // should be Other
ngtsc 22.1.7
return new (__ngFactoryType__ || M)(i0.ɵɵinject(TOKEN));
return new (__ngFactoryType__ || M)(i0.ɵɵinject(Other));
Where
crates/oxc_angular_compiler/src/ng_module/decorator.rs, extract_param_dependency (tip ~L467, main ~L444): the decorator match has no "Inject" arm (it falls into _ => {}), and the token always comes from extract_param_token (the type annotation). Compare directive/decorator.rs (tip ~L357) or pipe/decorator.rs (tip ~L307), which read the @Inject argument first.
On main?
Yes.
Direction
Read the @Inject(...) argument as the token, like the pipe extractor does, and fall back to the type only when there is no @Inject. The five extract_param_dependency copies (component, directive, pipe, injectable, ng_module) could share one helper so they can't drift again.
The
@NgModulefactory reads@Optional,@Self,@SkipSelf,@Hostand@Attributeon constructor parameters, but not@Inject. The token comes from the type annotation instead:unknown, an interface, a primitive) →ɵɵinvalidFactoryDep(i), and the module throws NG0202 when it is created;@Injecttoken, with no error.Same result for an aliased import (
import {Inject as Inj}).@Injectable,@Pipeand the other handlers read@Injectfine.Repro
OXC (#504 tip and main, same output)
ngtsc 22.1.7
Where
crates/oxc_angular_compiler/src/ng_module/decorator.rs,extract_param_dependency(tip ~L467, main ~L444): the decoratormatchhas no"Inject"arm (it falls into_ => {}), and the token always comes fromextract_param_token(the type annotation). Comparedirective/decorator.rs(tip ~L357) orpipe/decorator.rs(tip ~L307), which read the@Injectargument first.On main?
Yes.
Direction
Read the
@Inject(...)argument as the token, like the pipe extractor does, and fall back to the type only when there is no@Inject. The fiveextract_param_dependencycopies (component, directive, pipe, injectable, ng_module) could share one helper so they can't drift again.