Skip to content

fix(ng_module): ɵfac ignores @Inject(TOKEN) on @NgModule constructor parameters #519

Description

@Brooooooklyn

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions