angular/angular · error · RuntimeError

650

650

Error message

'${instruction}' value must be a string of CSS classes or an animation function, got ${stringify(value)}

What it means

assertAnimationTypes guards the value given to the class-based animation instructions animate.enter/animate.leave: it must be a string of CSS classes or a function (the AnimationClassBindingFn receiving the class list and element). A null/undefined or non-string/non-function value throws RuntimeError 650 because the animation runner can neither parse classes nor call it.

Source

Thrown at packages/core/src/animation/utils.ts:48

  (typeof ngServerMode === 'undefined' || !ngServerMode) &&
  typeof document !== 'undefined' &&
  // tslint:disable-next-line:no-toplevel-property-access
  typeof document?.documentElement?.getAnimations === 'function';

/**
 * Helper function to check if animations are disabled via injection token
 */
export function areAnimationsDisabled(lView: LView): boolean {
  const injector = lView[INJECTOR]!;
  return injector.get(ANIMATIONS_DISABLED, DEFAULT_ANIMATIONS_DISABLED);
}

/**
 * Asserts a value passed in is actually an animation type and not something else
 */
export function assertAnimationTypes(value: string | Function, instruction: string) {
  if (value == null || (typeof value !== 'string' && typeof value !== 'function')) {
    throw new RuntimeError(
      RuntimeErrorCode.ANIMATE_INVALID_VALUE,
      `'${instruction}' value must be a string of CSS classes or an animation function, got ${stringify(value)}`,
    );
  }
}

/**
 * Asserts a given native element is an actual Element node and not something like a comment node.
 */
export function assertElementNodes(nativeElement: Element, instruction: string) {
  if ((nativeElement as Node).nodeType !== Node.ELEMENT_NODE) {
    throw new RuntimeError(
      RuntimeErrorCode.ANIMATE_INVALID_VALUE,
      `'${instruction}' can only be used on an element node, got ${stringify((nativeElement as Node).nodeType)}`,
    );
  }
}

View on GitHub (pinned to 51cb07e980)

Solutions

  1. Ensure the value is a CSS class string, e.g. 'fade-in scale', or a function like (classes, element) => 'anim-' + classes.join(' ').
  2. Initialize the component field used by the binding to a valid string instead of leaving it undefined.
  3. Guard async config: fall back to a default class string until loaded.
  4. If you meant to disable the animation conditionally, use the ANIMATIONS_DISABLED injection token rather than nulling the value.

Example fix

// before
animate.enter(undefined);
host: { 'class.enter': 'this.config.classes' }  // config not loaded yet

// after
animate.enter('fade-in');
host: { 'class.enter': 'this.config?.classes ?? "fade-in"' }
Defensive patterns

Strategy: type-guard

Validate before calling

// Before binding or passing a value to animate.enter/animate.leave:
function safeAnimClasses(v: unknown, fallback = ''): string {
  return typeof v === 'string' ? v : fallback;
}
// template: [class.enter]="safeAnimClasses(cfg?.classes, 'fade-in')"

Type guard

export function isAnimationClassValue(
  v: unknown,
): v is string | ((classes: string[], element: Element) => string) {
  return typeof v === 'string' || typeof v === 'function';
}

Prevention

When it happens

Trigger: Calling animate.enter(...) or animate.leave(...) (in host bindings, directive code, or templates) with a value that is not a string or function: animate.enter(null), a number, or a component field that is undefined because it was never initialized or arrived async.

Common situations: Config values fetched from a server and not yet loaded when the binding evaluates; typed config objects where a typo yields undefined; passing an object literal of options instead of a class string; SSR-safe code that nulls out bindings.

Related errors


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