angular/angular · error · Error

Importing a type-only declaration of type ${ts.SyntaxKind[re

Error message

Importing a type-only declaration of type ${ts.SyntaxKind[ref.node.kind]} in a value position is not allowed.

What it means

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.

Source

Thrown at packages/compiler-cli/src/ngtsc/imports/src/emitter.ts:349

  emit(
    ref: Reference,
    context: ts.SourceFile,
    importFlags: ImportFlags,
  ): ReferenceEmitResult | null {
    if (ref.bestGuessOwningModule === null) {
      // There is no module name available for this Reference, meaning it was arrived at via a
      // relative path.
      return null;
    } else if (!isDeclaration(ref.node)) {
      // It's not possible to import something which isn't a declaration.
      throw new Error(
        `Debug assert: unable to import a Reference to non-declaration of type ${
          ts.SyntaxKind[ref.node.kind]
        }.`,
      );
    } else if ((importFlags & ImportFlags.AllowTypeImports) === 0 && isTypeDeclaration(ref.node)) {
      throw new Error(
        `Importing a type-only declaration of type ${
          ts.SyntaxKind[ref.node.kind]
        } in a value position is not allowed.`,
      );
    }

    // Try to find the exported name of the declaration, if one is available.
    const {specifier, resolutionContext} = ref.bestGuessOwningModule;
    const exports = this.getExportsOfModule(specifier, resolutionContext);
    if (exports.module === null) {
      return {
        kind: ReferenceEmitKind.Failed,
        ref,
        context,
        reason: `The module '${specifier}' could not be found.`,
      };
    } else if (exports.exportMap === null || !exports.exportMap.has(ref.node)) {
      return {

View on GitHub (pinned to 51cb07e980)

Solutions

  1. Replace the type-only symbol with a runtime value: a class or an InjectionToken
  2. For interface-typed dependencies, add @Inject(MY_TOKEN) with a real InjectionToken<T>
  3. Keep interfaces and type aliases out of providers arrays, useClass/useExisting/useFactory values, and other value positions in Angular metadata

Example fix

// before
interface Config { url: string }
constructor(private config: Config) {} // type used as DI token

// after
const CONFIG = new InjectionToken<Config>('CONFIG');
constructor(@Inject(CONFIG) private config: Config) {}
Defensive patterns

Strategy: validation

Validate before calling

import ts from 'typescript';

// Flag DI constructor params whose type resolves to an interface/type alias (not a class/token)
function isTypeOnlyDeclaration(node: ts.Node): boolean {
  return ts.isInterfaceDeclaration(node) || ts.isTypeAliasDeclaration(node);
}

Type guard

function isRuntimeInjectable(symbol: {declarations?: readonly ts.Declaration[]}): boolean {
  return (symbol.declarations ?? []).some(
    d => ts.isClassDeclaration(d) || ts.isEnumDeclaration(d) || ts.isVariableDeclaration(d),
  );
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


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