Record SDK for Angular

On this page
  1. Install the package
  2. Provide the service
  3. Add a record button
  4. Open the studio from code
  5. Other ways to provide the service
  6. Use the web component
  7. Custom recorder screens
  8. Troubleshooting

@dadan-io/record-sdk-angular adds Dadan recording to an Angular app through DadanRecorderService. The service opens the built-in recording studio from your button, runs your callbacks inside Angular’s zone and cleans up when its injector is destroyed. It supports Angular 15 to 22 and has been verified on Angular 18 to 22.

Before you start, read the Record SDK overview and get a public app ID with your development origin allowlisted.

bash
npm install @dadan-io/record-sdk-angular react@^18 react-dom@^18

The studio interface is built with React, so it needs react and react-dom even though you will not write any React code. npm 7 and later install them automatically, but choose React 19 unless you pin a version. React 18 is smaller and is the version the studio is tested with. Yarn and pnpm (without auto-install-peers) do not install them for you.

If your tsconfig.json sets "skipLibCheck": false, also run npm install -D @types/react. Recent Angular CLI projects use "moduleResolution": "bundler", which is what the package needs.

Add your public app ID to your environment files:

typescript
// src/environments/environment.ts
export const environment = {
  production: false,
  dadanPublicAppId: "app_your_public_id",
};

The public app ID is safe in frontend code. Never put your Dadan API key in an environment file that ships to the browser.

DadanRecorderService is a plain class, so you can provide it without decorators. Provide it on the component that uses it, and pass NgZone so SDK callbacks run inside change detection:

typescript
import { NgZone } from "@angular/core";
import { provideDadanRecorder } from "@dadan-io/record-sdk-angular";

export const recorderProviders = [provideDadanRecorder({ ngZone: NgZone })];

Providing it at component level gives each component its own studio. When the component is destroyed, Angular calls the service’s ngOnDestroy, which releases the camera, the microphone and every listener.

typescript
import {
  AfterViewInit,
  Component,
  ElementRef,
  NgZone,
  ViewChild,
  inject,
  signal,
} from "@angular/core";
import {
  DadanRecorderService,
  provideDadanRecorder,
  type DadanVideo,
} from "@dadan-io/record-sdk-angular";
import { environment } from "../environments/environment";

@Component({
  selector: "app-record-button",
  standalone: true,
  providers: [provideDadanRecorder({ ngZone: NgZone })],
  template: `
    <button #recordButton type="button" [disabled]="!ready()">
      Record a video
    </button>
    @if (video(); as v) {
      <p>
        Uploaded.
        <a [href]="v.sharedUrl" target="_blank" rel="noopener">
          Watch your recording
        </a>
      </p>
    }
    @if (failed()) {
      <p role="alert">Recording failed. Please try again.</p>
    }
  `,
})
export class RecordButtonComponent implements AfterViewInit {
  @ViewChild("recordButton") private button!: ElementRef<HTMLButtonElement>;
  private readonly recorder = inject(DadanRecorderService);
  private readonly zone = inject(NgZone);

  readonly ready = signal(false);
  readonly failed = signal(false);
  readonly video = signal<DadanVideo | null>(null);

  async ngAfterViewInit() {
    await this.recorder.init({
      mode: "custom",
      publicAppId: environment.dadanPublicAppId,
      videoTitle: "Support request",
      folderId: "123456",
    });

    this.recorder
      .configureButton(this.button.nativeElement, (video) => {
        this.failed.set(false);
        this.video.set(video);
      })
      .on("error", (error) => {
        this.zone.run(() => this.failed.set(true));
        console.error(error.code, error.message);
      });

    this.ready.set(true);
  }
}

What happens here:

  1. init() creates the studio with your public app ID. Call it once per service instance.
  2. configureButton() makes the button open the studio. Its callback receives the DadanVideo after the upload finishes and the studio closes. Because the service has NgZone, the callback runs inside the zone.
  3. configureButton() returns the studio’s button object, so you can chain more listeners such as error and cancel. Those run outside the zone, which is why the example wraps the update in zone.run.

The example uses signals and the @if block (Angular 17 and later). On Angular 15 or 16, use plain properties and *ngIf instead.

typescript
this.recorder.open();
this.recorder.open({ mode: "camera" });
this.recorder.close();

Passing a mode skips the mode picker. Available modes are both (screen and camera), screen, camera and audio.

All of these work:

typescript
import {
  DadanRecorderService,
  provideDadanRecorder,
} from "@dadan-io/record-sdk-angular";

export const optionA = [DadanRecorderService];
export const optionB = [
  { provide: DadanRecorderService, useClass: DadanRecorderService },
];
export const optionC = [provideDadanRecorder()];

Without provideDadanRecorder({ ngZone: NgZone }), attach the zone after injection with inject(DadanRecorderService).setZone(inject(NgZone)). If you create the service yourself with new DadanRecorderService(), call teardown() in your component’s ngOnDestroy.

You can also place <dadan-recorder> in a template. Register the element once, then allow custom elements in the component:

typescript
import { CUSTOM_ELEMENTS_SCHEMA, Component, inject } from "@angular/core";
import {
  DadanRecorderService,
  provideDadanRecorder,
} from "@dadan-io/record-sdk-angular";
import { environment } from "../environments/environment";

@Component({
  selector: "app-answer-recorder",
  standalone: true,
  providers: [provideDadanRecorder()],
  schemas: [CUSTOM_ELEMENTS_SCHEMA],
  template: `
    <dadan-recorder
      [attr.public-app-id]="appId"
      video-title="Interview answer"
      (dadan-insert)="onInsert($event)"
    ></dadan-recorder>
  `,
})
export class AnswerRecorderComponent {
  readonly appId = environment.dadanPublicAppId;

  constructor() {
    inject(DadanRecorderService).defineElement();
  }

  onInsert(event: Event) {
    const video = (event as CustomEvent).detail;
    console.info(video.sharedUrl);
  }
}

The element dispatches dadan-insert, dadan-upload-complete, dadan-cancel and dadan-error events. See Customize the recording studio for all its attributes.

To build every screen yourself, use DadanRecorder, which this package re-exports. Create it in ngOnInit or ngAfterViewInit, call destroy() in ngOnDestroy, and follow Build a custom recorder with DadanRecorder.

  • NG0204 when injecting the service. Provide the class itself or use provideDadanRecorder(). Do not add a constructor parameter in a subclass without decorating it.
  • “Call init() before configureButton()”. configureButton ran before init() resolved. Await init() first, as in the example.
  • The view does not update after recording. Provide the service with provideDadanRecorder({ ngZone: NgZone }), and wrap updates from other listeners in zone.run.

Ask AI about this article

What should the AI help you with?

Are you still facing issues?

Reach out to our support team for further assistance.

dadan