Building blocksInstallFramework guides
Angular
Standalone Angular component that loads the CDN bundle.
import { Component, OnDestroy, OnInit } from "@angular/core";
const CDN_URL = "https://widgets.medos.one/v2/unified.js";
const CONTAINER_ID = "medos-booking-inline";
type MedosBookingApi = {
init: (config: Record<string, unknown>) => void;
open: (config?: Record<string, unknown>) => void;
close: () => void;
};
@Component({
selector: "app-booking",
standalone: true,
template: `
<button (click)="open()">Book appointment</button>
<div id="${CONTAINER_ID}" style="min-height: 600px"></div>
`,
})
export class BookingComponent implements OnInit, OnDestroy {
private destroyed = false;
/** Read through `window` so a missing global is undefined, not a crash. */
private get medos(): MedosBookingApi | undefined {
return (window as unknown as { MedosBooking?: MedosBookingApi })
.MedosBooking;
}
ngOnInit() {
this.load()
.then(() => {
// The user may have navigated away while the bundle was downloading;
// initializing now would target a container that no longer exists.
if (this.destroyed) return;
this.medos?.init({
apiKey: "mk_your_publishable_key",
mode: "inline",
containerId: CONTAINER_ID,
});
})
.catch((err) => console.error(err));
}
ngOnDestroy() {
this.destroyed = true;
// Inline mode has no close(); clear the container so navigating back
// doesn't mount a second widget on top of the first.
const el = document.getElementById(CONTAINER_ID);
if (el) el.innerHTML = "";
}
open() {
this.medos?.open();
}
/** Reuses an existing tag so navigation doesn't re-download the bundle. */
private load(): Promise<void> {
if (this.medos) return Promise.resolve();
const existing = document.querySelector<HTMLScriptElement>(
`script[src="${CDN_URL}"]`,
);
if (existing) {
return new Promise((resolve) =>
existing.addEventListener("load", () => resolve(), { once: true }),
);
}
return new Promise((resolve, reject) => {
const script = document.createElement("script");
script.src = CDN_URL;
script.async = true;
script.onload = () => resolve();
script.onerror = () => reject(new Error("Medos Booking failed to load"));
document.head.appendChild(script);
});
}
}Don't reach for the bare global
declare const MedosBooking makes TypeScript happy but doesn't help at
runtime — if the bundle hasn't loaded, evaluating the identifier throws
ReferenceError, and MedosBooking?.open() will not protect you.
Optional chaining only guards null/undefined on a binding that exists.
Go through window as above.