Rive for Angular (Grandgular)

April 26, 2026 Β· View on GitHub

Primary npm packages: @grandgular/rive-angular-webgl2 (recommended) and @grandgular/rive-angular-canvas. The legacy package @grandgular/rive-angular is not maintained in this repo; migrate to one of the two packages (see CHANGELOG).

npm webgl2 npm canvas Angular License: MIT

Modern Angular wrapper for Rive animations with reactive state management, built with Angular signals and zoneless architecture.

The public API follows Semantic Versioning.

Why 2.0.0 for new package names?
@grandgular/rive-angular-webgl2 and @grandgular/rive-angular-canvas are separate npm packages (each has its own version line in the registry), but we ship them as 2.0.0 to mark one product-level major: the old single package @grandgular/rive-angular is no longer built here, you must pick a renderer package and change import paths and peer dependencies β€” that is a breaking change for consumers, even where the Angular component API stays the same. Details: CHANGELOG (section 2.0.0) and Migration from @grandgular/rive-angular below.

Packages (WebGL2 vs Canvas)

Rive’s web runtimes are separate npm packages; this workspace mirrors the official Canvas vs WebGL2 and React model with renderer-specific Angular packages:

npm packageRive runtime (peer)Use case
@grandgular/rive-angular-webgl2@rive-app/webgl2Recommended β€” Rive Renderer, best quality/performance
@grandgular/rive-angular-canvas@rive-app/canvasSmaller bundle, simpler vector/raster work

Internal shared code is maintained in libs/rive-angular-core and embedded in each published package. Renderer implementation files are kept aligned between canvas and webgl2, with only the runtime adapter differing. After changing shared code or shared renderer logic, run: npm run sync:rive-angular-core

Migration from @grandgular/rive-angular

The npm package @grandgular/rive-angular is not built from this repository anymore. Move to either @grandgular/rive-angular-webgl2 (with @rive-app/webgl2) or @grandgular/rive-angular-canvas (with @rive-app/canvas) and change your import path; the public API (components, RiveFileService, provideRiveRuntime, etc.) is unchanged. See CHANGELOG (v2.0.0).

What is Rive?

Rive is a real-time interactive design and animation tool. It allows designers and developers to create animations that respond to different states and user inputs. Rive animations are lightweight, interactive, and can be used in apps, games, and websites.

Why use these packages?

This library provides a modern, Angular-native way to integrate Rive animations into your Angular applications:

  • πŸš€ Modern Angular: Built with Angular 18+ signals, standalone components, and zoneless architecture
  • ⚑ Performance-first: Runs outside Angular zone, uses OnPush change detection, and IntersectionObserver for automatic rendering optimization
  • 🎯 Type-safe: Full TypeScript support with strict typing
  • πŸ”„ Reactive: Signal-based API for reactive state management
  • 🌐 SSR-ready: Full server-side rendering support
  • 🧹 Automatic cleanup: Proper resource management and lifecycle handling
  • πŸ“¦ File caching: Built-in service for preloading and caching .riv files
  • πŸ› οΈ Developer Experience: Built-in debug mode, validation, and detailed error codes

Comparison with alternatives

vs. ng-rive (unmaintained)

ng-rive was the previous Angular wrapper for Rive, but it has been unmaintained since 2021 and is incompatible with modern Angular versions:

Featureng-rive@grandgular/rive-angular-webgl2
Angular version9-12 (legacy)18+ (modern)
ArchitectureModules, Zone.jsSignals, standalone
Maintenance❌ Abandoned (3+ years)βœ… Active
TypeScriptPartialStrict typing
SSR support⚠️ Limitedβœ… Full
PerformanceStandardOptimized (zoneless)
File caching❌ Manualβœ… Built-in service
Validation❌ Noneβœ… Built-in

vs. rive-react

This library follows the design principles of the official rive-react library but adapts them to Angular's reactive paradigm:

Aspectrive-react@grandgular/rive-angular-webgl2
Component API<Rive> component<rive-canvas>
ReactivityHooks (useState, useEffect)Signals
File preloadinguseRiveFile hookRiveFileService
State accessHook return valuesPublic signals
LifecycleuseEffect cleanupDestroyRef

Both libraries provide similar features and follow the same philosophy of providing a thin, reactive wrapper around the core Rive runtime.

Migration from ng-rive

ng-rive has been unmaintained since 2021 and does not support Angular 13+. If you're upgrading your Angular project, here's how to migrate.

1. Replace the package

npm uninstall ng-rive
npm install @grandgular/rive-angular-webgl2 @rive-app/webgl2

2. Update imports

ng-rive@grandgular/rive-angular-webgl2
import { RiveModule } from 'ng-rive'import { RiveCanvasComponent } from '@grandgular/rive-angular-webgl2'
Add RiveModule to NgModule.importsAdd RiveCanvasComponent to imports of standalone component

3. Update templates

ng-rive:

<!-- NgModule-based, Zone.js -->
<canvas riv="my-animation" width="500" height="500">
  <riv-animation name="idle" [play]="true"></riv-animation>
</canvas>

@grandgular/rive-angular-webgl2:

<!-- Standalone, signals, zoneless -->
<rive-canvas
  src="assets/my-animation.riv"
  [autoplay]="true"
  style="width: 500px; height: 500px"
/>

4. State machines

ng-rive:

<canvas riv="my-animation">
  <riv-state-machine name="StateMachine" [play]="true">
    <riv-input name="isHover" [value]="hover"></riv-input>
  </riv-state-machine>
</canvas>

@grandgular/rive-angular-webgl2:

<rive-canvas
  src="assets/my-animation.riv"
  stateMachines="StateMachine"
  (loaded)="onLoaded()"
/>
onLoaded() {
  this.riveCanvas().setInput('StateMachine', 'isHover', this.hover);
}

Installation

npm install @grandgular/rive-angular-webgl2 @rive-app/webgl2

Or with yarn:

yarn add @grandgular/rive-angular-webgl2 @rive-app/webgl2

Quick Start

Basic usage

import { Component } from '@angular/core';
import { RiveCanvasComponent, Fit, Alignment } from '@grandgular/rive-angular-webgl2';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [RiveCanvasComponent],
  template: `
    <rive-canvas
      src="assets/animation.riv"
      [autoplay]="true"
      [fit]="Fit.Cover"
      [alignment]="Alignment.Center"
      (loaded)="onLoaded()"
      (loadError)="onError($event)"
    />
  `,
  styles: [`
    rive-canvas {
      width: 100%;
      height: 400px;
    }
  `]
})
export class AppComponent {
  Fit = Fit;
  Alignment = Alignment;

  onLoaded() {
    console.log('Animation loaded!');
  }

  onError(error: Error) {
    console.error('Failed to load animation:', error);
  }
}

With state machines

import { Component, viewChild } from '@angular/core';
import { RiveCanvasComponent } from '@grandgular/rive-angular-webgl2';

@Component({
  selector: 'app-interactive',
  standalone: true,
  imports: [RiveCanvasComponent],
  template: `
    <rive-canvas
      src="assets/interactive.riv"
      [stateMachines]="'StateMachine'"
      (loaded)="onLoaded()"
    />
    <button (click)="triggerAction()">Trigger</button>
  `
})
export class InteractiveComponent {
  riveCanvas = viewChild.required(RiveCanvasComponent);

  onLoaded() {
    // Set initial state
    this.riveCanvas().setInput('StateMachine', 'isActive', true);
  }

  triggerAction() {
    // Fire a trigger
    this.riveCanvas().fireTrigger('StateMachine', 'onClick');
  }
}

Text Runs

Rive text runs allow you to update text content at runtime. The library provides two approaches:

Declarative (Controlled Keys)

Use the textRuns input for reactive, template-driven text updates:

<rive-canvas
  src="assets/hello.riv"
  [textRuns]="{ greeting: userName(), subtitle: 'Welcome' }"
/>

Keys present in textRuns are controlled β€” the input is the source of truth and will override any imperative changes.

Imperative (Uncontrolled Keys)

Use methods for reading values or managing keys not in textRuns:

riveRef = viewChild.required(RiveCanvasComponent);

// Read current value
const greeting = this.riveRef().getTextRunValue('greeting');

// Set uncontrolled key
this.riveRef().setTextRunValue('dynamicText', 'New value');

Nested Text Runs

For text runs inside nested components, use the AtPath variants:

this.riveRef().setTextRunValueAtPath(
  'button_text',
  'Click Me',
  'NestedArtboard/ButtonComponent'
);

Controlled vs Uncontrolled

  • Controlled: Keys in textRuns input β€” managed by Angular, input is source of truth
  • Uncontrolled: Keys not in textRuns β€” managed imperatively via methods
  • Warning: Calling setTextRunValue() on a controlled key logs a warning and the change will be overwritten on next input update

Data Binding (ViewModel)

Rive's ViewModel system allows you to bind dynamic data (colors, numbers, strings, booleans, etc.) to your animations. ViewModels are created in the Rive editor and provide a structured way to control animation properties at runtime.

What is a ViewModel?

A ViewModel in Rive is a data structure that:

  • Is created by designers in the Rive editor
  • Contains typed properties (color, number, string, boolean, enum, trigger)
  • Can be bound to animation elements (fills, strokes, transforms, etc.)
  • Supports two-way data binding (changes in code affect animation, changes in animation can trigger events)

When to use Data Binding vs Text Runs?

  • Text Runs: Simple text updates, no ViewModel setup required in editor
  • Data Binding: Dynamic colors, numbers, complex data structures, two-way reactivity

Declarative (Controlled) Approach

Use the dataBindings input for reactive, template-driven data binding:

import { Component, signal } from '@angular/core';
import { RiveCanvasComponent } from '@grandgular/rive-angular-webgl2';

@Component({
  selector: 'app-data-binding',
  standalone: true,
  imports: [RiveCanvasComponent],
  template: `
    <rive-canvas
      src="assets/animation.riv"
      [dataBindings]="{
        backgroundColor: themeColor(),
        score: playerScore(),
        playerName: userName(),
        isActive: isGameActive()
      }"
      (dataBindingChange)="onDataChange($event)"
    />
    
    <button (click)="changeTheme()">Change Theme</button>
    <button (click)="incrementScore()">+10 Points</button>
  `
})
export class DataBindingComponent {
  themeColor = signal('#FF5733');
  playerScore = signal(0);
  userName = signal('Player 1');
  isGameActive = signal(true);

  changeTheme() {
    const colors = ['#FF5733', '#33FF57', '#3357FF', '#F333FF'];
    const randomColor = colors[Math.floor(Math.random() * colors.length)];
    this.themeColor.set(randomColor);
  }

  incrementScore() {
    this.playerScore.update(score => score + 10);
  }

  onDataChange(event: DataBindingChangeEvent) {
    console.log('Property changed from animation:', event);
    // event.path: property path
    // event.value: new value (for triggers, value is always true)
    // event.propertyType: 'color' | 'number' | 'string' | 'boolean' | 'enum' | 'trigger'
    
    if (event.propertyType === 'trigger') {
      console.log(`Trigger "${event.path}" fired from animation`);
      // Handle trigger event (e.g., show popup, play sound, etc.)
    }
  }
}

Imperative (Uncontrolled) Approach

Use methods for direct, programmatic control:

import { Component, viewChild } from '@angular/core';
import { RiveCanvasComponent } from '@grandgular/rive-angular-webgl2';

@Component({
  selector: 'app-imperative',
  standalone: true,
  imports: [RiveCanvasComponent],
  template: `
    <rive-canvas src="assets/animation.riv" />
    
    <button (click)="updateColor()">Update Color</button>
    <button (click)="updateScore()">Update Score</button>
    <button (click)="triggerAnimation()">Fire Trigger</button>
  `
})
export class ImperativeComponent {
  riveRef = viewChild.required(RiveCanvasComponent);

  updateColor() {
    // Set color using hex string
    this.riveRef().setColor('backgroundColor', '#00FF00');
    
    // Or using RGBA components
    this.riveRef().setColorRgba('backgroundColor', 0, 255, 0, 255);
    
    // Or change only opacity
    this.riveRef().setColorOpacity('backgroundColor', 0.5);
  }

  updateScore() {
    // Set any data binding value (auto-detects type)
    this.riveRef().setDataBinding('score', 100);
    this.riveRef().setDataBinding('playerName', 'Winner');
    this.riveRef().setDataBinding('isActive', false);
  }

  triggerAnimation() {
    // Fire a trigger property
    this.riveRef().fireViewModelTrigger('onComplete');
  }

  readValues() {
    // Read current values
    const color = this.riveRef().getColor('backgroundColor');
    // color: { r: 0, g: 255, b: 0, a: 255 }
    
    const score = this.riveRef().getDataBinding('score');
    // score: 100 (auto-detected as number)
  }
}

Color Utilities

The library exports color conversion utilities for advanced use cases:

import { parseRiveColor, riveColorToArgb, riveColorToHex } from '@grandgular/rive-angular-webgl2';

// Parse various color formats
const color1 = parseRiveColor('#FF5733');        // { r: 255, g: 87, b: 51, a: 255 }
const color2 = parseRiveColor('#FF573380');      // { r: 255, g: 87, b: 51, a: 128 }
const color3 = parseRiveColor(0x80FF5733);       // { r: 255, g: 87, b: 51, a: 128 }
const color4 = parseRiveColor({ r: 255, g: 0, b: 0, a: 255 });

// Convert to ARGB integer
const argb = riveColorToArgb({ r: 255, g: 0, b: 0, a: 255 }); // 0xFFFF0000

// Convert to hex string
const hex = riveColorToHex({ r: 255, g: 0, b: 0, a: 255 }); // '#FF0000FF'

Selecting a ViewModel

If your .riv file contains multiple ViewModels, specify which one to use:

<rive-canvas
  src="assets/animation.riv"
  viewModelName="GameViewModel"
  [dataBindings]="{ score: 42 }"
/>

If viewModelName is not provided, the default ViewModel for the artboard is used.

Controlled vs Uncontrolled

Same semantics as Text Runs:

  • Controlled: Keys in dataBindings input β€” managed by Angular, input is source of truth
  • Uncontrolled: Keys not in dataBindings β€” managed imperatively via methods
  • Warning: Calling setDataBinding() or setColor() on a controlled key logs a warning and the change will be overwritten on next input update

Validation and Error Handling

Imperative methods (setDataBinding, setColor, setColorOpacity, fireViewModelTrigger) emit validation errors via the loadError output when:

  • Property path doesn't exist in the ViewModel (RIVE_402)
  • Value type doesn't match property type (RIVE_403)
  • Color format is invalid (hex string, ARGB integer, or RiveColor object expected)
  • Opacity value is out of range (must be between 0.0 and 1.0)
<rive-canvas
  src="assets/animation.riv"
  (loadError)="handleError($event)"
/>

handleError(error: Error) {
  if (error instanceof RiveValidationError) {
    console.error('Validation error:', error.code, error.message);
  }
}

Advanced: Direct ViewModel Access

For advanced scenarios, access the ViewModel instance directly:

riveRef = viewChild.required(RiveCanvasComponent);

advancedUsage() {
  const vmi = this.riveRef().viewModelInstance();
  if (vmi) {
    // Direct access to ViewModel SDK methods
    const colorProp = vmi.color('backgroundColor');
    if (colorProp) {
      colorProp.rgba(255, 0, 0, 255);
    }
  }
}

Preloading files with RiveFileService

For better performance, you can preload and cache .riv files:

import { Component, inject, DestroyRef } from '@angular/core';
import { RiveCanvasComponent, RiveFileService } from '@grandgular/rive-angular-webgl2';

@Component({
  selector: 'app-preload',
  standalone: true,
  imports: [RiveCanvasComponent],
  template: `
    @if (fileState().status === 'success') {
      <rive-canvas
        [riveFile]="fileState().riveFile"
        [autoplay]="true"
      />
    }
    @if (fileState().status === 'loading') {
      <p>Loading animation...</p>
    }
    @if (fileState().status === 'failed') {
      <p>Failed to load animation</p>
    }
  `
})
export class PreloadComponent {
  private riveFileService = inject(RiveFileService);
  private destroyRef = inject(DestroyRef);

  // Load and cache the file
  fileState = this.riveFileService.loadFile({
    src: 'assets/animation.riv'
  });

  constructor() {
    // Auto-release on component destroy
    this.destroyRef.onDestroy(() => {
      this.riveFileService.releaseFile({ src: 'assets/animation.riv' });
    });
  }
}

Debug Mode

The library provides a built-in debug mode to help you troubleshoot animations.

Global Configuration

Enable debug mode globally in your app.config.ts:

import { provideRiveDebug } from '@grandgular/rive-angular-webgl2';

export const appConfig: ApplicationConfig = {
  providers: [
    provideRiveDebug({ logLevel: 'debug' })
  ]
};

Available log levels: 'none' | 'error' | 'warn' | 'info' | 'debug'

Local Override

Enable debug mode for a specific component instance:

<rive-canvas
  src="assets/animation.riv"
  [debugMode]="true" 
/>

When debug mode is enabled, the library will log:

  • Loading progress and file details
  • Available artboards, animations, and state machines
  • Validation warnings (e.g., misspelled animation names)

Runtime Initialization (WASM)

Use provideRiveRuntime() to control when the Rive WASM runtime initializes.

Eager mode (default)

Initializes runtime on app startup:

import { ApplicationConfig } from '@angular/core';
import { provideRiveRuntime } from '@grandgular/rive-angular-webgl2';

export const appConfig: ApplicationConfig = {
  providers: [
    provideRiveRuntime({ wasmUrl: 'assets/rive/rive.v1.wasm' }),
  ],
};

Lazy mode

Initializes runtime only when first needed (first rive-canvas init or RiveFileService.loadFile() call):

import { ApplicationConfig } from '@angular/core';
import { provideRiveRuntime } from '@grandgular/rive-angular-webgl2';

export const appConfig: ApplicationConfig = {
  providers: [
    provideRiveRuntime({
      wasmUrl: 'assets/rive/rive.v1.wasm',
      lazy: true,
    }),
  ],
};

Migration from provideAppInitializer

If you used:

provideAppInitializer(() => RuntimeLoader.setWasmUrl('assets/rive/rive.v1.wasm'))

you can now use:

provideRiveRuntime({ wasmUrl: 'assets/rive/rive.v1.wasm' })

Error Handling & Validation

The library validates your configuration against the loaded Rive file and provides structured error codes.

Validation

Validation errors (e.g., missing artboard or animation) are non-fatal. They are emitted via the loadError output but do not crash the application.

<rive-canvas
  src="assets/anim.riv"
  [artboard]="'WrongName'"
  (loadError)="onError($event)"
/>

In this case, onError receives a RiveValidationError with code RIVE_201, and the library logs a warning with available artboard names.

Error Codes

CodeTypeDescription
RIVE_101LoadFile not found (404)
RIVE_102LoadInvalid .riv file format
RIVE_103LoadNetwork error
RIVE_201ValidationArtboard not found
RIVE_202ValidationAnimation not found
RIVE_203ValidationState machine not found
RIVE_204ValidationInput/Trigger not found in State Machine
RIVE_205ValidationText run not found
RIVE_301ConfigNo animation source provided
RIVE_302ConfigInvalid canvas element
RIVE_401Data BindingViewModel not found
RIVE_402Data BindingProperty not found in ViewModel
RIVE_403Data BindingType mismatch (value doesn't match property type)

API Reference

Runtime Provider

interface RiveRuntimeConfig {
  wasmUrl?: string;
  lazy?: true;
}
  • provideRiveRuntime(config?: RiveRuntimeConfig) - configures Rive runtime initialization strategy.
  • lazy omitted - eager initialization on startup.
  • lazy: true - deferred initialization at first runtime usage.

RiveCanvasComponent

Inputs

InputTypeDefaultDescription
srcstring-URL to the .riv file
bufferArrayBuffer-ArrayBuffer containing .riv file data
riveFileRiveFile-Preloaded RiveFile instance (from RiveFileService)
artboardstring-Name of the artboard to display
animationsstring | string[]-Animation(s) to play
stateMachinesstring | string[]-State machine(s) to use
autoplaybooleantrueAuto-play animations on load
fitFitFit.ContainHow the animation fits in the canvas
alignmentAlignmentAlignment.CenterAlignment of the animation
useOffscreenRendererbooleanfalseUse offscreen rendering
shouldUseIntersectionObserverbooleantrueAuto-pause when off-screen
shouldDisableRiveListenersbooleanfalseDisable Rive event listeners
automaticallyHandleEventsbooleanfalseAuto-handle Rive events (e.g., OpenUrlEvent)
debugModebooleanundefinedEnable verbose logging for this instance
textRunsRecord<string, string>-Declarative text run values. Keys present are controlled by input.
viewModelNamestring-ViewModel to use for data binding (if the .riv file defines ViewModels)
dataBindingsRecord<string, DataBindingValue>-Declarative ViewModel property values. Keys present are controlled by input.

Outputs

OutputTypeDescription
loadedvoidEmitted when animation loads successfully
loadErrorErrorEmitted when animation fails to load or validation errors occur
stateChangeRiveEventEmitted on state machine state changes
riveEventRiveEventEmitted for custom Rive events
riveReadyRiveEmitted when Rive instance is fully loaded and ready (after loaded)
dataBindingChangeDataBindingChangeEventEmitted when a ViewModel property changes (two-way binding)
animationPlayRiveEventEmitted when playback starts (type: EventType.Play)
animationPauseRiveEventEmitted when playback pauses (type: EventType.Pause)
animationStopRiveEventEmitted when playback stops (type: EventType.Stop)
animationLoopRiveEventEmitted when a loop iteration completes; event.data is a LoopEvent (animation name + LoopType)
animationAdvanceRiveEventEmitted every frame while advancing; high frequency β€” use sparingly. Not wrapped in NgZone.run; trigger change detection manually if the view must update

Animation lifecycle events

Listen for play/pause/stop/loop/advance without subscribing to the raw Rive instance:

import { Component } from '@angular/core';
import {
  RiveCanvasComponent,
  type RiveEvent,
  EventType,
  LoopType,
  type LoopEvent,
} from '@grandgular/rive-angular-webgl2';

@Component({
  imports: [RiveCanvasComponent],
  template: `
    <rive-canvas
      src="animation.riv"
      (animationPlay)="onAnimationPlay($event)"
      (animationPause)="onAnimationPause($event)"
      (animationStop)="onAnimationStop($event)"
      (animationLoop)="onAnimationLoop($event)"
    />
  `,
})
export class LifecycleDemoComponent {
  onAnimationPlay(_event: RiveEvent) {
    // event.type === EventType.Play
  }

  onAnimationPause(_event: RiveEvent) {
    // event.type === EventType.Pause
  }

  onAnimationStop(_event: RiveEvent) {
    // event.type === EventType.Stop
  }

  onAnimationLoop(event: RiveEvent) {
    const loopData = event.data as LoopEvent;
    // e.g. one-shot completion:
    if (loopData.type === LoopType.OneShot) {
      // animation finished its single run
    }
  }
}

LoopType and LoopEvent are re-exported from the package you install (@grandgular/rive-angular-webgl2 or @grandgular/rive-angular-canvas) for convenience.

Public Signals (Readonly)

All signals are readonly and cannot be mutated externally. Use the public methods to control the animation.

SignalTypeDescription
isPlayingSignal<boolean>Whether animation is playing
isPausedSignal<boolean>Whether animation is paused
isLoadedSignal<boolean>Whether animation is loaded
riveInstanceSignal<Rive | null>Direct access to Rive instance
viewModelInstanceSignal<ViewModelInstance | null>Active ViewModel instance after load, if the file uses ViewModels

Note: Signals are readonly to prevent external mutation. Use component methods (playAnimation(), pauseAnimation(), etc.) to control the animation state.

Public Methods

MethodDescription
playAnimation(animations?: string | string[])Play animation(s)
pauseAnimation(animations?: string | string[])Pause animation(s)
stopAnimation(animations?: string | string[])Stop animation(s)
reset()Reset animation to beginning
setInput(stateMachine: string, input: string, value: number | boolean)Set state machine input value
fireTrigger(stateMachine: string, trigger: string)Fire state machine trigger
getTextRunValue(name: string): string | undefinedGet text run value
setTextRunValue(name: string, value: string)Set text run value (warns if key is controlled)
getTextRunValueAtPath(name: string, path: string): string | undefinedGet nested text run value
setTextRunValueAtPath(name: string, value: string, path: string)Set nested text run

RiveFileService

Service for preloading and caching .riv files.

Methods

MethodDescription
loadFile(params: RiveFileParams): Signal<RiveFileState>Load and cache a .riv file
releaseFile(params: RiveFileParams): voidRelease cached file (decrements ref count)
clearCache(): voidClear all cached files

Types

interface RiveFileParams {
  src?: string;
  buffer?: ArrayBuffer;
  debug?: boolean;
}

interface RiveFileState {
  riveFile: RiveFile | null;
  status: 'idle' | 'loading' | 'success' | 'failed';
}

SSR Support

The library is fully compatible with Angular Universal and server-side rendering:

  • Canvas rendering is automatically disabled on the server
  • IntersectionObserver and ResizeObserver use safe fallbacks
  • No runtime errors in SSR environments

Performance Tips

  1. Use IntersectionObserver: Keep shouldUseIntersectionObserver enabled (default) to automatically pause animations when off-screen
  2. Preload files: Use RiveFileService to preload and cache .riv files for instant display
  3. Disable unnecessary listeners: Set shouldDisableRiveListeners to true for decorative animations without interactivity
  4. Use OnPush: The component already uses OnPush change detection for optimal performance
  5. Reactive updates: The component now reactively updates layout when fit or alignment change without full reload

Recent Improvements (v0.2.0)

Quality & Stability

  • βœ… Readonly signals prevent accidental state mutation
  • βœ… Dynamic DPR support for multi-monitor setups
  • βœ… Reactive configuration - all inputs trigger appropriate updates
  • βœ… Type-safe configuration - eliminated unsafe type assertions
  • βœ… Fixed race conditions in file service and cache management
  • βœ… Proper timing - riveReady emits after full load

Developer Experience

  • πŸ› οΈ Enhanced validation with detailed error messages
  • πŸ› Comprehensive debugging via provideRiveDebug()
  • πŸ“ Better error codes for programmatic error handling
  • πŸ§ͺ Improved testability with DI-based services

See CHANGELOG.md for complete details, migration guide, and all improvements.

Requirements

  • Angular 18.0.0 or higher
  • @rive-app/canvas 2.35.0 or higher
  • TypeScript 5.4 or higher

Contributing

Contributions are welcome! Please feel free to submit issues or pull requests.

License

MIT

Resources

Maintainer

This library is maintained by the community and is not officially supported by Rive. For official Rive support, please visit the Rive Community.