{"record":{"id":"3e64ca5115d7ff23","repo":"angular/angular","slug":"local-compilation-unsupported-expression","errorCode":"LOCAL_COMPILATION_UNSUPPORTED_EXPRESSION","errorMessage":"In ${compilationModeName} mode, host directive cannot be an expression. Use an identifier instead","messagePattern":"In (.+?) mode, host directive cannot be an expression\\. Use an identifier instead","errorType":"exception","errorClass":"FatalDiagnosticError","httpStatus":null,"severity":"error","filePath":"packages/compiler-cli/src/ngtsc/annotations/directive/src/shared.ts","lineNumber":2154,"sourceCode":"      }\n    }\n\n    let directive: Reference<ClassDeclaration> | Expression | ExternalReference;\n    let nameForErrors = (fieldName: string) => '@Directive.hostDirectives';\n    if (compilationMode === CompilationMode.LOCAL && hostReference instanceof DynamicValue) {\n      // At the moment in local compilation we only support simple array for host directives, i.e.,\n      // an array consisting of the directive identifiers. We don't support forward refs or other\n      // expressions applied on externally imported directives. The main reason is simplicity, and\n      // that almost nobody wants to use host directives this way (e.g., what would be the point of\n      // forward ref for imported symbols?!)\n      if (\n        !ts.isIdentifier(hostReference.node) &&\n        !ts.isPropertyAccessExpression(hostReference.node)\n      ) {\n        const compilationModeName = emitDeclarationOnly\n          ? 'experimental declaration-only emission'\n          : 'local compilation';\n        throw new FatalDiagnosticError(\n          ErrorCode.LOCAL_COMPILATION_UNSUPPORTED_EXPRESSION,\n          hostReference.node,\n          `In ${compilationModeName} mode, host directive cannot be an expression. Use an identifier instead`,\n        );\n      }\n      directive = new WrappedNodeExpr(hostReference.node);\n    } else if (hostReference instanceof Reference) {\n      directive = hostReference as Reference<ClassDeclaration>;\n      nameForErrors = (fieldName: string) =>\n        `@Directive.hostDirectives.${\n          (directive as Reference<ClassDeclaration>).node.name.text\n        }.${fieldName}`;\n    } else {\n      throw new Error('Impossible state');\n    }\n\n    const meta: HostDirectiveMeta = {\n      directive,","sourceCodeStart":2136,"sourceCodeEnd":2172,"githubUrl":"https://github.com/angular/angular/blob/51cb07e98081ab7e4e84a9e0949266a8e19cca84/packages/compiler-cli/src/ngtsc/annotations/directive/src/shared.ts#L2136-L2172","documentation":"In local compilation mode (tsconfig `angularCompilerOptions.compilationMode: \"local\"`, or experimental declaration-only emission) each file is compiled without full-program context, so hostDirectives entries must be plain identifiers or property-access chains resolvable locally. An entry that is an arbitrary expression — typically `forwardRef(() => Dir)` — cannot be processed and is rejected with LOCAL_COMPILATION_UNSUPPORTED_EXPRESSION at that expression node.","triggerScenarios":"`@Directive({hostDirectives: [forwardRef(() => MenuHost)]})` compiled with compilationMode 'local' or emitDeclarationOnly; also IIFE/call expressions or conditional expressions inside the hostDirectives array.","commonSituations":"Libraries opting into local/partial compilation for faster builds; forwardRef copied from View-Engine-era code to break circular imports; declaration-only builds (ngtsc emitDeclarationOnly) hitting the same restriction.","solutions":["Import the directive class and reference it directly: `hostDirectives: [MenuHost]`","Break the circular import that motivated forwardRef (restructure files, or use `import type` for type-only usage)","Fall back to full compilation mode if the expression form is genuinely required"],"exampleFix":"// before\n@Directive({selector: 'menu', hostDirectives: [forwardRef(() => MenuHost)]})\nclass Menu {}\n\n// after\nimport {MenuHost} from './menu-host';\n\n@Directive({selector: 'menu', hostDirectives: [MenuHost]})\nclass Menu {}","handlingStrategy":"validation","validationCode":"const HOSTDIR_EXPR = /hostDirectives\\s*:\\s*\\[[^\\]]*(forwardRef|\\bfunction\\b|\\(\\s*\\(\\s*\\))/;\n\n// CI gate: fail when compilationMode is local (or emitDeclarationOnly) and the\n// hostDirectives array contains anything but identifiers/property accesses:\n//   rg -n \"hostDirectives.*forwardRef\" src/\n//   rg -n '\"compilationMode\"\\s*:\\s*\"local\"' tsconfig*","typeGuard":null,"tryCatchPattern":null,"preventionTips":["In local/declaration-only compilation, hostDirectives entries must be plain (property-access) identifiers — import the class and reference it directly","Eliminate forwardRef in hostDirectives by fixing the circular import (split files, or use import type for type-only references)","Grep for forwardRef inside hostDirectives whenever enabling compilationMode local"],"tags":["angular","ngtsc","host-directives","local-compilation","forwardref","build-optimization"],"backgroundTag":"angular-local-compilation-limitation","analyzedSha":"51cb07e98081ab7e4e84a9e0949266a8e19cca84","analyzedAt":"2026-08-22T07:04:54.531Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}