angular/angular · error · FatalDiagnosticError

VALUE_NOT_LITERAL

VALUE_NOT_LITERAL

Error message

Query "read" option expected a literal class reference.

What it means

The read option tells the query what to inject from each matched node, and the compiler must emit the token as a value reference. parseReadOption accepts, after unwrapping parentheses and as-expressions, either an identifier or a property access whose base is an identifier (e.g. ElementRef or core.ElementRef). Every other expression kind - call, element access, conditional, non-identifier base - throws VALUE_NOT_LITERAL at the value node.

Source

Thrown at packages/compiler-cli/src/ngtsc/annotations/directive/src/query_functions.ts:180

 * live outside of the class in the static class definition.
 */
function parseReadOption(value: ts.Expression): o.Expression {
  if (
    ts.isExpressionWithTypeArguments(value) ||
    ts.isParenthesizedExpression(value) ||
    ts.isAsExpression(value)
  ) {
    return parseReadOption(value.expression);
  }

  if (
    (ts.isPropertyAccessExpression(value) && ts.isIdentifier(value.expression)) ||
    ts.isIdentifier(value)
  ) {
    return new o.WrappedNodeExpr(value);
  }

  throw new FatalDiagnosticError(
    ErrorCode.VALUE_NOT_LITERAL,
    value,
    `Query "read" option expected a literal class reference.`,
  );
}

/** Parses the `descendants` option of a query. */
function parseDescendantsOption(value: ts.Expression): boolean {
  if (value.kind === ts.SyntaxKind.TrueKeyword) {
    return true;
  } else if (value.kind === ts.SyntaxKind.FalseKeyword) {
    return false;
  }
  throw new FatalDiagnosticError(
    ErrorCode.VALUE_HAS_WRONG_TYPE,
    value,
    `Expected "descendants" option to be a boolean literal.`,
  );

View on GitHub (pinned to 51cb07e980)

Solutions

  1. Pass the class token directly: {read: ViewContainerRef}
  2. For aliased/namespace imports, import the symbol so a bare identifier can be used: import {ElementRef} from '@angular/core' then {read: ElementRef}
  3. Choose among a fixed set of tokens with separate query declarations instead of computing the token expression

Example fix

// before
export class Row {
  vc = viewChild('row', {read: inject(ViewContainerRef)});
}

// after
export class Row {
  vc = viewChild('row', {read: ViewContainerRef});
}
Defensive patterns

Strategy: validation

Validate before calling

// read must be a bare identifier or Identifier.member access, never a call
const badRead = /read\s*:\s*[A-Za-z_$][\w$]*\s*\(|read\s*:\s*[^A-Za-z_{'\"\s]/;
if (badRead.test(src)) fail('read option must be a literal class reference');

Prevention

When it happens

Trigger: viewChild('row', {read: inject(ViewContainerRef)}); {read: tokens.get('row')}; {read: cond ? A : B}; {read: SomeObj.nested.Token} (non-identifier base). All fail because no value reference can be emitted.

Common situations: Trying to resolve read tokens dynamically (multi-token systems, registry lookups); deep property access into namespaces; wrapping tokens in helper functions during DI cleanups.

Related errors


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