Angular video call component
Add a video call to an Angular app
@meeto/embed-angular is a standalone Angular component that puts a Meeto browser call, with lobby, camera, screen sharing and chat, into your template. It needs Angular 17 or newer and a public mk_live_… key locked to your site's origins, emits typed outputs for joins, leaves and errors, and runs the widget outside Angular's zone so the call does not trigger change detection.
Install the Angular package
Install the published package in the Angular app that will show the call. It brings @meeto/embed-core along and has no other runtime dependencies besides tslib.
npm install @meeto/embed-angularCreate the key before you test. In Settings → Website widget, add every origin that will host the component, for example http://localhost:4200 for ng serve and your production domain. A key that does not allow the current origin refuses to load the call.
Render an inline call room
Import MeetoRoomComponent into a standalone component and place <meeto-room> in its template. The call fills the element, so give it a display and a height.
import { Component } from "@angular/core";
import { MeetoRoomComponent } from "@meeto/embed-angular";
@Component({
selector: "app-support-call",
standalone: true,
imports: [MeetoRoomComponent],
template: `
<meeto-room
embedKey="mk_live_…"
roomName="support"
name="Visitor"
style="display:block;height:520px"
(joined)="onJoined($event.room)"
></meeto-room>
`,
})
export class SupportCallComponent {
onJoined(room: string) {
console.log("joined", room);
}
}roomName sends every visitor to one persistent room, such as support, and the key owner joins it as host from the dashboard. Pass room instead when your server already made an open room: POST /api/embed/rooms with the secret key returns its code on every plan, and such a room expires after 24 hours without activity (on Free, also once its free hour is used). Meetings scheduled through the REST API use password rooms, and the widget cannot pass the password, so share their joinUrl instead. With neither, the widget creates a room and reuses it for the browser tab.
Which inputs does the component take?
| Input | Behavior |
|---|---|
| embedKey | Required public key beginning with mk_live_, restricted to the origins you allow |
| roomName / room | A persistent named room, or the code of an existing room |
| mode | inline (default, inside the element) or bubble, a floating call button |
| position / label | Bubble corner (bottom-right or bottom-left) and the button's accessible label |
| name / lang / accent | Prefilled display name, interface language (42 available) and hex accent color |
| autoJoin | Bind it as [autoJoin]="true" to skip the lobby in open-door rooms; the browser still asks for camera and microphone |
| baseUrl | The Meeto address; leave it unset to use https://meeto.me |
Changing any of these inputs later destroys the iframe and builds a new one, which drops the visitor out of a running call. Set them before the call starts.
<meeto-room
embedKey="mk_live_…"
mode="bubble"
roomName="sales"
position="bottom-left"
label="Talk to sales"
></meeto-room>In bubble mode the element itself stays empty and the widget draws its own button and panel over the page. With a roomName, the button shows how many people are in the room. Closing the panel does not end the call.
Outputs, methods and the widget instance
Each widget event has its own output, and (meetoEvent) carries all of them as one stream. Note that the error output is named widgetError, not error.
import { Component, ViewChild } from "@angular/core";
import { MeetoRoomComponent } from "@meeto/embed-angular";
@Component({
selector: "app-consultation",
standalone: true,
imports: [MeetoRoomComponent],
template: `
<meeto-room
embedKey="mk_live_…"
[room]="roomCode"
style="display:block;height:520px"
(phase)="phase = $event.phase"
(participants)="count = $event.count"
(left)="onLeft($event.reason, $event.durationSec)"
(widgetError)="onError($event.code)"
></meeto-room>
<button type="button" (click)="call.setMicrophone(false)">Mute me</button>
<button type="button" (click)="call.leave()">Leave</button>
`,
})
export class ConsultationComponent {
@ViewChild(MeetoRoomComponent) call!: MeetoRoomComponent;
// An open room your server created with POST /api/embed/rooms
roomCode = "misty-fox-a18f493cb027fe823cb6";
phase = "prejoin";
count = 0;
onLeft(reason: string, durationSec: number) {
console.log("left", reason, durationSec);
}
onError(code: string) {
console.error("Meeto widget error", code);
}
}| Output | Payload |
|---|---|
| (ready) | The iframe loaded and finished its handshake |
| (phase) | phase: prejoin, room, finished or error |
| (joined) | room and identity of the visitor who entered |
| (left) | reason (left, ended, kicked, connection) and durationSec |
| (participants) | count of people in the call |
| (widgetError) | code and message, for example an origin the key does not allow |
Through @ViewChild(MeetoRoomComponent) you can call join(name?), leave(), setMicrophone(on) and setCamera(on). The meeto getter returns the underlying MeetoWidget for open() and close() of the bubble panel and for the current room code. In a room with knocking, a password or a lock, join() keeps the visitor in the lobby and the widget reports join_not_allowed.
Does it work with Angular server-side rendering?
Only in the browser. The component creates the call in ngOnInit, and the widget refuses to start outside a browser, so a server-rendered route must not render it on the server. Wrap it in a plain @defer block, without hydrate triggers: Angular renders the placeholder on the server and the call in the browser.
@defer {
<meeto-room embedKey="mk_live_…" roomName="support" style="display:block;height:520px"></meeto-room>
} @placeholder {
<div style="height:520px">Loading the call…</div>
}An isPlatformBrowser check around the element works too. Zone-wise you need nothing extra: the iframe and its message listener run outside the Angular zone, and outputs re-enter it only when they fire.
Production checklist and limits
Allow each origin
Register the exact scheme, host and port of production, staging and local builds. Preview domains are separate origins.
Give it a height
The iframe is 100% of the element. A meeto-room without a height squeezes the call into a strip about 150 px tall.
Test two browsers
Join from a second device or private window and check that your outputs see joined, participants and left.
| Plan | Limit |
|---|---|
| Free | Up to 10 participants and 60-minute calls once two people are in; no recording or AI summary |
| Pro | Up to 25 participants; no time limit for up to 2 simultaneous meetings per account; recording and 10 AI-summary hours a month |
| Every plan | The Powered by Meeto badge stays visible; the public key stays origin-locked |
Calls are encrypted in transit and run through Meeto's media servers; they are not end-to-end encrypted. Keep the server key mk_secret_… out of the Angular bundle; only the public key belongs there. See the website video call widget for the inline and bubble layouts in action.
Frequently asked questions
Which Angular versions does @meeto/embed-angular support?
The package declares @angular/core 17 or newer as a peer dependency and ships a standalone component, so there is no NgModule to import. It depends on @meeto/embed-core, which npm installs with it.
Can I use the Web Component instead of this package?
Yes. @meeto/embed-core exports registerMeetoElement(); call it once, for example in main.ts, and use <meeto-room> with CUSTOM_ELEMENTS_SCHEMA. Use one or the other: the Angular component and the custom element share the meeto-room tag, and both on one element would give it two lifecycles.
Does the component support the accelerated mode?
Not as an input. The package re-exports MeetoWidget from @meeto/embed-core, so a component that needs accelerated: true can create MeetoWidget itself in ngAfterViewInit and call destroy() in ngOnDestroy.
Do visitors need a Meeto account or an app?
No. Visitors join in their browser after allowing the camera and microphone. The account that owns the embed key is the host of named rooms and sets the plan limits for calls in them.
Put it to work
Open your Meeto account to set it up, or explore the working product first.