angular/angular · error · FatalDiagnosticError

DECORATOR_ARG_NOT_LITERAL

DECORATOR_ARG_NOT_LITERAL

Error message

@Service argument must be an object literal

What it means

When @Service is called with one argument, that argument must be a literal object expression. The handler checks ts.isObjectLiteralExpression on decorator.args[0]; a variable reference, string, or any other expression throws DECORATOR_ARG_NOT_LITERAL. Like other Angular annotation handlers, the compiler statically reflects the literal's fields (autoProvided, factory) and cannot evaluate referenced configuration.

Source

Thrown at packages/compiler-cli/src/ngtsc/annotations/src/service.ts:237

    const typeArgumentCount = this.reflector.getGenericArityOfClass(clazz) || 0;
    if (decorator.args === null) {
      throw new FatalDiagnosticError(
        ErrorCode.DECORATOR_NOT_CALLED,
        decorator.node,
        '@Service must be called',
      );
    }
    if (decorator.args.length === 0) {
      return {
        name,
        type,
        typeArgumentCount,
      };
    } else if (decorator.args.length === 1) {
      const metaNode = decorator.args[0];

      if (!ts.isObjectLiteralExpression(metaNode)) {
        throw new FatalDiagnosticError(
          ErrorCode.DECORATOR_ARG_NOT_LITERAL,
          metaNode,
          '@Service argument must be an object literal',
        );
      }

      // Resolve the fields of the literal into a map of field name to expression.
      const meta = reflectObjectLiteral(metaNode);
      let autoProvided: boolean | undefined;

      if (meta.has('autoProvided')) {
        const value = meta.get('autoProvided')!;

        if (value.kind !== ts.SyntaxKind.TrueKeyword && value.kind !== ts.SyntaxKind.FalseKeyword) {
          throw new FatalDiagnosticError(
            ErrorCode.VALUE_HAS_WRONG_TYPE,
            metaNode,
            '`autoProvided` property must be a boolean literal',

View on GitHub (pinned to 51cb07e980)

Solutions

  1. Inline the options: @Service({autoProvided: true})
  2. Keep the call empty (@Service()) if you have no options to pass
  3. Ensure the argument is a plain object literal, not a reference, spread, or assertion

Example fix

// before
const opts = {autoProvided: true};
@Service(opts)
export class Cache {}

// after
@Service({autoProvided: true})
export class Cache {}
Defensive patterns

Strategy: validation

Validate before calling

import ts from 'typescript';

function serviceArgIsObjectLiteral(dec: ts.Decorator): boolean {
  if (!ts.isCallExpression(dec.expression)) return true;
  const [arg] = dec.expression.arguments;
  return arg === undefined || ts.isObjectLiteralExpression(arg);
}

Type guard

function isServiceMetaLiteral(arg: ts.Expression | undefined): arg is ts.ObjectLiteralExpression | undefined {
  return arg === undefined || ts.isObjectLiteralExpression(arg);
}

Prevention

When it happens

Trigger: @Service(SERVICE_OPTS) with a constant defined elsewhere; @Service('name') string form; @Service({...defaults}) spreads or assertion-wrapped values.

Common situations: Centralizing service configuration in constants; codemods extracting decorator options into variables; unfamiliarity with the literal-only requirement shared by all Angular decorators.

Related errors


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