angular/angular · error · FatalLinkerError

Unsupported `forwardRef(fn)` call, expected its argument to

Error message

Unsupported `forwardRef(fn)` call, expected its argument to be a function

What it means

Thrown by extractForwardRef() in the Angular partial-declaration linker when a forwardRef(...) call's single argument is not a function. The linker unwraps the forward reference by reading the wrapper function's return value (getFunctionReturnValue()); passing anything else (a string, class reference directly, object literal) makes unwrapping impossible and FatalLinkerError is thrown.

Source

Thrown at packages/compiler-cli/linker/src/file_linker/partial_linkers/util.ts:106

  const callee = expr.getCallee();
  if (callee.getSymbolName() !== 'forwardRef') {
    throw new FatalLinkerError(
      callee.expression,
      'Unsupported expression, expected a `forwardRef()` call or a type reference',
    );
  }

  const args = expr.getArguments();
  if (args.length !== 1) {
    throw new FatalLinkerError(
      expr,
      'Unsupported `forwardRef(fn)` call, expected a single argument',
    );
  }

  const wrapperFn = args[0] as AstValue<Function, TExpression>;
  if (!wrapperFn.isFunction()) {
    throw new FatalLinkerError(
      wrapperFn,
      'Unsupported `forwardRef(fn)` call, expected its argument to be a function',
    );
  }

  return createMayBeForwardRefExpression(
    wrapperFn.getFunctionReturnValue().getOpaque(),
    ForwardRefHandling.Unwrapped,
  );
}

const STANDALONE_IS_DEFAULT_RANGE = new semver.Range(`>= 19.0.0 || ${PLACEHOLDER_VERSION}`, {
  includePrerelease: true,
});

export function getDefaultStandaloneValue(version: string): boolean {
  return STANDALONE_IS_DEFAULT_RANGE.test(version);
}

View on GitHub (pinned to 51cb07e980)

Solutions

  1. Always pass a function: forwardRef(() => AppModule)
  2. If you do not need the forward reference, drop the wrapper entirely and use a plain type reference (providedIn: AppModule)
  3. Fix the generator that emits the non-function argument
  4. Restore the library's original published output instead of an edited copy

Example fix

// before
providedIn: forwardRef(AppModule)
// after
providedIn: forwardRef(() => AppModule)
Defensive patterns

Strategy: type-guard

Validate before calling

// Validate that forwardRef's argument is a function before emitting metadata
if (node.arguments[0]?.type !== 'ArrowFunctionExpression' && node.arguments[0]?.type !== 'FunctionExpression') {
  throw new Error('forwardRef argument must be a function, e.g. forwardRef(() => Token)');
}

Type guard

function isForwardRefFnArg(node: any): node is {type: 'ArrowFunctionExpression' | 'FunctionExpression'} {
  return node?.type === 'ArrowFunctionExpression' || node?.type === 'FunctionExpression';
}

Try / catch

try {
  fileLinker.linkPartialDeclaration(fnName, args, scope);
} catch (e) {
  if (e instanceof FatalLinkerError && /argument to be a function/.test(e.message)) {
    throw new Error(`${sourceUrl}: forwardRef must wrap a function returning the token.`);
  }
  throw e;
}

Prevention

When it happens

Trigger: `providedIn: forwardRef('AppModule')` or `providedIn: forwardRef(AppModule)` inside ɵɵngDeclare* metadata — forwardRef requires a lambda that returns the token, not the token itself. Typically hand-written declarations or codegen that 'simplifies' the wrapper away.

Common situations: Hand-authored partial declarations and linker test fixtures; code generators emitting the class reference instead of an arrow function; refactoring tools inlining what looks like a redundant wrapper.

Understand the failure class

Background: Invalid argument type errors: "must be of type string", "expected X, got Y", and ERR_INVALID_ARG_TYPE explained — this error's family across 15 libraries.

Related errors


AI-assisted analysis of angular/angular@51cb07e980 (2026-08-22). Data as JSON: /api/errors/01d94a7c90184926. Report an issue: GitHub.