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
- Pass the class token directly: {read: ViewContainerRef}
- For aliased/namespace imports, import the symbol so a bare identifier can be used: import {ElementRef} from '@angular/core' then {read: ElementRef}
- 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
- Pass token classes directly (ViewContainerRef, TemplateRef, your component class)
- Import namespaced symbols locally so a plain identifier can be used as read
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
- VALUE_HAS_WRONG_TYPE
- VALUE_HAS_WRONG_TYPE
- INITIALIZER_API_DECORATOR_METADATA_COLLISION
- DECORATOR_ARG_NOT_LITERAL
- INITIALIZER_API_DISALLOWED_MEMBER_VISIBILITY
AI-assisted analysis of angular/angular@51cb07e980 (2026-08-22).
Data as JSON: /api/errors/8f6940b093dc1abe.
Report an issue: GitHub.