{"record":{"id":"683b32c4c1ef857f","repo":"angular/angular","slug":"importing-a-type-only-declaration-of-type-ts-syn","errorCode":null,"errorMessage":"Importing a type-only declaration of type ${ts.SyntaxKind[ref.node.kind]} in a value position is not allowed.","messagePattern":"Importing a type-only declaration of type (.+?) in a value position is not allowed\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/compiler-cli/src/ngtsc/imports/src/emitter.ts","lineNumber":349,"sourceCode":"\n  emit(\n    ref: Reference,\n    context: ts.SourceFile,\n    importFlags: ImportFlags,\n  ): ReferenceEmitResult | null {\n    if (ref.bestGuessOwningModule === null) {\n      // There is no module name available for this Reference, meaning it was arrived at via a\n      // relative path.\n      return null;\n    } else if (!isDeclaration(ref.node)) {\n      // It's not possible to import something which isn't a declaration.\n      throw new Error(\n        `Debug assert: unable to import a Reference to non-declaration of type ${\n          ts.SyntaxKind[ref.node.kind]\n        }.`,\n      );\n    } else if ((importFlags & ImportFlags.AllowTypeImports) === 0 && isTypeDeclaration(ref.node)) {\n      throw new Error(\n        `Importing a type-only declaration of type ${\n          ts.SyntaxKind[ref.node.kind]\n        } in a value position is not allowed.`,\n      );\n    }\n\n    // Try to find the exported name of the declaration, if one is available.\n    const {specifier, resolutionContext} = ref.bestGuessOwningModule;\n    const exports = this.getExportsOfModule(specifier, resolutionContext);\n    if (exports.module === null) {\n      return {\n        kind: ReferenceEmitKind.Failed,\n        ref,\n        context,\n        reason: `The module '${specifier}' could not be found.`,\n      };\n    } else if (exports.exportMap === null || !exports.exportMap.has(ref.node)) {\n      return {","sourceCodeStart":331,"sourceCodeEnd":367,"githubUrl":"https://github.com/angular/angular/blob/51cb07e98081ab7e4e84a9e0949266a8e19cca84/packages/compiler-cli/src/ngtsc/imports/src/emitter.ts#L331-L367","documentation":"A reference-emit strategy hit a type-only declaration (interface, type alias, etc. — isTypeDeclaration) in a context where a value import is required and AllowTypeImports was not among the import flags, and threw a plain internal Error. Practically: Angular code generation tried to use a type as a runtime value. Types erased at runtime cannot be imported into emitted value positions.","triggerScenarios":"Using an interface as a DI token (constructor parameter typed as an interface with no @Inject of a real token); passing a type alias to providers/useClass or another value slot in Angular metadata; templates or generated code referencing type-only symbols as values.","commonSituations":"Interface-based dependency injection without InjectionToken; config objects typed by interfaces and used as tokens; verbatimModuleSyntax/isolatedModules setups exposing accidental value-position use of types.","solutions":["Replace the type-only symbol with a runtime value: a class or an InjectionToken","For interface-typed dependencies, add @Inject(MY_TOKEN) with a real InjectionToken<T>","Keep interfaces and type aliases out of providers arrays, useClass/useExisting/useFactory values, and other value positions in Angular metadata"],"exampleFix":"// before\ninterface Config { url: string }\nconstructor(private config: Config) {} // type used as DI token\n\n// after\nconst CONFIG = new InjectionToken<Config>('CONFIG');\nconstructor(@Inject(CONFIG) private config: Config) {}","handlingStrategy":"validation","validationCode":"import ts from 'typescript';\n\n// Flag DI constructor params whose type resolves to an interface/type alias (not a class/token)\nfunction isTypeOnlyDeclaration(node: ts.Node): boolean {\n  return ts.isInterfaceDeclaration(node) || ts.isTypeAliasDeclaration(node);\n}","typeGuard":"function isRuntimeInjectable(symbol: {declarations?: readonly ts.Declaration[]}): boolean {\n  return (symbol.declarations ?? []).some(\n    d => ts.isClassDeclaration(d) || ts.isEnumDeclaration(d) || ts.isVariableDeclaration(d),\n  );\n}","tryCatchPattern":null,"preventionTips":["Use InjectionToken<T> for every interface-typed dependency and @Inject(token) at the injection site","Never put interfaces or type aliases in providers, useClass, or template value positions","With verbatimModuleSyntax, audit value imports that only reference types"],"tags":["angular","ngtsc","type-only-import","di","internal-error"],"backgroundTag":"type-used-as-value","analyzedSha":"51cb07e98081ab7e4e84a9e0949266a8e19cca84","analyzedAt":"2026-08-22T07:04:54.531Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}