{"record":{"id":"8f6940b093dc1abe","repo":"angular/angular","slug":"value-not-literal","errorCode":"VALUE_NOT_LITERAL","errorMessage":"Query \"read\" option expected a literal class reference.","messagePattern":"Query \"read\" option expected a literal class reference\\.","errorType":"exception","errorClass":"FatalDiagnosticError","httpStatus":null,"severity":"error","filePath":"packages/compiler-cli/src/ngtsc/annotations/directive/src/query_functions.ts","lineNumber":180,"sourceCode":" * live outside of the class in the static class definition.\n */\nfunction parseReadOption(value: ts.Expression): o.Expression {\n  if (\n    ts.isExpressionWithTypeArguments(value) ||\n    ts.isParenthesizedExpression(value) ||\n    ts.isAsExpression(value)\n  ) {\n    return parseReadOption(value.expression);\n  }\n\n  if (\n    (ts.isPropertyAccessExpression(value) && ts.isIdentifier(value.expression)) ||\n    ts.isIdentifier(value)\n  ) {\n    return new o.WrappedNodeExpr(value);\n  }\n\n  throw new FatalDiagnosticError(\n    ErrorCode.VALUE_NOT_LITERAL,\n    value,\n    `Query \"read\" option expected a literal class reference.`,\n  );\n}\n\n/** Parses the `descendants` option of a query. */\nfunction parseDescendantsOption(value: ts.Expression): boolean {\n  if (value.kind === ts.SyntaxKind.TrueKeyword) {\n    return true;\n  } else if (value.kind === ts.SyntaxKind.FalseKeyword) {\n    return false;\n  }\n  throw new FatalDiagnosticError(\n    ErrorCode.VALUE_HAS_WRONG_TYPE,\n    value,\n    `Expected \"descendants\" option to be a boolean literal.`,\n  );","sourceCodeStart":162,"sourceCodeEnd":198,"githubUrl":"https://github.com/angular/angular/blob/51cb07e98081ab7e4e84a9e0949266a8e19cca84/packages/compiler-cli/src/ngtsc/annotations/directive/src/query_functions.ts#L162-L198","documentation":"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.","triggerScenarios":"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.","commonSituations":"Trying to resolve read tokens dynamically (multi-token systems, registry lookups); deep property access into namespaces; wrapping tokens in helper functions during DI cleanups.","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"],"exampleFix":"// before\nexport class Row {\n  vc = viewChild('row', {read: inject(ViewContainerRef)});\n}\n\n// after\nexport class Row {\n  vc = viewChild('row', {read: ViewContainerRef});\n}","handlingStrategy":"validation","validationCode":"// read must be a bare identifier or Identifier.member access, never a call\nconst badRead = /read\\s*:\\s*[A-Za-z_$][\\w$]*\\s*\\(|read\\s*:\\s*[^A-Za-z_{'\\\"\\s]/;\nif (badRead.test(src)) fail('read option must be a literal class reference');","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Pass token classes directly (ViewContainerRef, TemplateRef, your component class)","Import namespaced symbols locally so a plain identifier can be used as read"],"tags":["angular","ngtsc","signals","queries","di","static-analysis","compile-time"],"backgroundTag":"non-literal-type-reference","analyzedSha":"51cb07e98081ab7e4e84a9e0949266a8e19cca84","analyzedAt":"2026-08-22T07:04:54.531Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}