Notifications
The event contract the library uses to surface a message, without importing a UI it has no business knowing about.
The library sometimes needs to tell the user something — a session that failed, an operation that did not go through. It has no components and no toast library, so it does the only thing a headless package can: it dispatches a DOM event and lets the host decide what that looks like.
import { notify } from "@sinequa/atomic";
notify.error("Could not save your settings.");
// → dispatches a CustomEvent("notification") on window, with the detailsConcept — the notification contract
notify.* never renders anything. It dispatches CustomEvent("notification") whose detail is a
NotificationEvent. Your application listens once, and routes it to whatever toast component it uses. Nothing
happens if nobody listens — which is a design decision, not a bug.
Listening once
import type { NotificationEvent } from "@sinequa/atomic";
/**
* One listener for the whole application. The library dispatches on `window`, so
* this is the only place that has to know both the contract and your toast API.
*/
export function listenForNotifications(show: (event: NotificationEvent) => void) {
const handler = (event: Event) => {
// A CustomEvent carrying a NotificationEvent as its detail.
const { detail } = event as CustomEvent<NotificationEvent>;
if (detail) show(detail);
};
window.addEventListener("notification", handler);
return () => window.removeEventListener("notification", handler);
}
// Wire it to whatever renders messages in your application.
const stop = listenForNotifications(({ type, message, options }) => {
console.log(`[${type}] ${message}`, options?.description ?? "");
// `options.action` is a label and a callback — render it as a button.
if (options?.action) console.log(`action available: ${options.action.label}`);
});
// On teardown:
stop();import { DestroyRef, inject, provideEnvironmentInitializer } from "@angular/core";
import type { NotificationEvent } from "@sinequa/atomic";
/**
* Registers one listener for the whole application, torn down with the injector so
* a hot reload does not stack duplicates.
*/
export function provideNotifications(show: (event: NotificationEvent) => void) {
return provideEnvironmentInitializer(() => {
const destroyRef = inject(DestroyRef);
const handler = (event: Event) => {
const { detail } = event as CustomEvent<NotificationEvent>;
if (detail) show(detail);
};
// The library dispatches on `window`.
window.addEventListener("notification", handler);
destroyRef.onDestroy(() => window.removeEventListener("notification", handler));
});
}import type { NotificationEvent } from "@sinequa/atomic";
import { useEffect } from "react";
/**
* Mount once, high in the tree. The library dispatches on `window`, so this is the
* only place that has to know both the contract and your toast API.
*/
export function useNotifications(show: (event: NotificationEvent) => void) {
useEffect(() => {
const handler = (event: Event) => {
const { detail } = event as CustomEvent<NotificationEvent>;
if (detail) show(detail);
};
window.addEventListener("notification", handler);
return () => window.removeEventListener("notification", handler);
}, [show]);
}The payload
Prop
Type
Notifications versus errors
Two channels, and they are not alternatives:
| Use it for | |
|---|---|
notify.* and the DOM event | A message meant for a user |
requestFailed / unauthorized events | Reacting in code to a failed request |
A failed request does not automatically produce a notification: deciding whether the user should be told is your
application's judgement, not the library's. The usual wiring is a requestFailed handler that calls your own
notification layer — see Errors.