Skip to content

DI: T | null constructor param types not narrowed for token resolution #285

@brandonroberts

Description

@brandonroberts

A constructor parameter typed as a class unioned with null (MyService | null) fails token resolution. OXC emits ɵɵinvalidFactoryDep, which throws at instantiation. This is a token-resolution bug — independent of optionality.

Repro (both fail)

// canonical optional-DI pattern — still broken
@Component({ selector: 'x', template: '' })
export class X { constructor(@Optional() private svc: MyService | null) {} }

// no decorator — also broken
export class Y { constructor(private svc: MyService | null) {} }

Actual

ɵfac = function X_Factory(t) { return new (t||X)(i0.ɵɵinvalidFactoryDep(0)); };
// setClassMetadata emits { type: undefined, decorators: [{ type: Optional }] }

Expected

The reference compiler filters null literal type nodes out of the union; if exactly one type remains, it becomes the token. Token resolution and @Optional() are independent — @Optional() only sets injection flag 8.

  • @Optional() svc: MyService | nullɵɵdirectiveInject(MyService, 8)
  • svc: MyService | nullɵɵdirectiveInject(MyService)

Reference: getConstructorParameters() in compiler-cli/src/ngtsc/reflection/src/typescript.ts — filters SyntaxKind.NullKeyword literal type nodes, narrows to the sole remaining type. undefined is intentionally not stripped.

Scope note: Genuinely ambiguous unions (A | B, two class types) should produce a compile diagnostic ("not supported as injection token"), not a silent runtime failure — tracked separately under diagnostics.

Impact

Hard runtime crash. Breaks the standard @Optional() x: T | null optional-DI pattern.


Researched and drafted with Claude Code, cross-checked against the Angular reference compiler source.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions