Skip to main content

Alert & Toast Services

These two services provide all user-facing feedback in the application: alertService for modal dialogs that require user interaction, and toastService for brief, non-blocking notifications and loading indicators. Together they cover the full spectrum from "we need the user to confirm something" to "let the user know something happened."

alertService

Introduction

alertService provides a simplified, consistent API for presenting modal alert dialogs. Unlike Ionic's built-in AlertController (which uses native-style alerts), alertService renders custom Angular components inside Ionic modals, supporting text inputs, checkboxes, radio groups, and a custom price-entry mode with virtual keyboard support. This makes alerts behave identically across all platforms and gives full control over styling and input handling.

When to use

  • Confirmation dialogs: "Are you sure you want to delete this item?"
  • Data entry prompts: asking the user for a name, a quantity, or a price.
  • Multi-input dialogs: combining text fields, checkboxes, and radio buttons in a single dialog.
  • Any situation where you need to pause the flow and wait for user input.

Do not use alertService for brief, non-blocking feedback -- use toastService for that.

How it works

  alertService.alert(options)


new AlertModalController(modalCtrl, options)

▼ .present()
Ionic ModalController.create({
component: modalAlertComponent,
componentProps: { data: options }
})


modalAlertComponent renders inputs based on options.inputs[]

▼ user interacts, clicks a button
button.handler() calls ctrl.dismiss(data)


onDidDismiss() resolves with the data

The key insight is that alertService.alert() returns an AlertModalController immediately (synchronously). You then call .present() to show the modal and .onDidDismiss() to wait for the result. This two-step pattern gives you a reference to the controller that you can pass into button handlers for dismissal.

import { alertService, AlertModalController, AlertOptions } from '@unpispas/upp-base';

constructor(private alertCtrl: alertService) {}

AlertOptions

AlertOptions is typed as any | null and typically follows this shape:

const options = {
header: 'Confirm action', // dialog title
message: 'Are you sure?', // body text (optional)
backdropDismiss: true, // whether tapping the backdrop closes the modal
inputs: [ // array of input definitions (optional)
{ name: 'username', type: 'text', value: '', placeholder: 'Username' },
{ name: 'remember', type: 'checkbox', value: false },
{ name: 'role', type: 'radiogroup', value: 'admin', options: [
{ name: 'role', type: 'radiobutton', value: 'admin', label: 'Admin' },
{ name: 'role', type: 'radiobutton', value: 'user', label: 'User' }
]}
],
buttons: [ // array of button definitions
{ text: 'Cancel', role: 'cancel' },
{ text: 'OK', handler: (alertCtrl) => alertCtrl.dismiss(data) }
]
};

Supported input types:

  • text, password, number, price -- rendered via <upp-modal-input>.
  • checkbox -- rendered as an Ionic ion-checkbox.
  • radiogroup / radiobutton -- rendered as ion-radio inside an ion-radio-group.

AlertModalController

Returned by alertService.alert(). Controls the lifecycle of a single modal.

MethodSignatureDescription
present()(): Promise<void>Creates and presents the Ionic modal using modalAlertComponent.
dismiss(data)(data: any): Promise<void>Closes the modal and resolves the onDidDismiss() promise with the provided data. Also blurs the active element to dismiss any virtual keyboard.
onDidDismiss()(): Promise<any>Returns a promise that resolves with the dismissal data. Resolves once -- either from a dismiss() call or from the backdrop being tapped (in which case data is null).

modalInputComponent

The <upp-modal-input> component implements ControlValueAccessor for Angular forms integration and provides special behaviour for different input types:

  • text: standard text input.
  • password: password input with a show/hide toggle button.
  • number: allows only digits, comma, and period. The comma is normalised to a period.
  • price: interprets each keystroke as a digit in a cents value. Typing 1, 2, 3, 4 produces 12.34. Backspace removes the last digit.
  • Kiosk support: when the platform is in kiosk mode, the input uses the virtual keyboard. The kiosk input property (default true) controls this.

Usage examples

Simple confirmation dialog

const ctrl = this.alertCtrl.alert({
header: 'Delete item?',
message: 'This action cannot be undone.',
buttons: [
{ text: 'Cancel', role: 'cancel' },
{ text: 'Delete', handler: () => ctrl.dismiss(true) }
]
});

await ctrl.present();
const result = await ctrl.onDidDismiss();

if (result === true) {
await this.deleteItem();
}

Dialog with text input

const ctrl = this.alertCtrl.alert({
header: 'Rename product',
inputs: [
{ name: 'productName', type: 'text', value: this.product.name, placeholder: 'Product name' }
],
buttons: [
{ text: 'Cancel', role: 'cancel' },
{ text: 'Save', handler: () => ctrl.dismiss(ctrl._data) }
]
});

await ctrl.present();
const result = await ctrl.onDidDismiss();

if (result?.data?.productName) {
this.product.name = result.data.productName;
await this.save();
}

Dialog with price input

const ctrl = this.alertCtrl.alert({
header: 'Set price',
inputs: [
{ name: 'price', type: 'price', value: '0', placeholder: '0.00' }
],
buttons: [
{ text: 'Cancel', role: 'cancel' },
{ text: 'OK', handler: () => ctrl.dismiss(ctrl._data) }
]
});

await ctrl.present();
const result = await ctrl.onDidDismiss();

if (result?.data?.price) {
this.product.price = parseFloat(result.data.price);
}

Dialog with radio group selection

const ctrl = this.alertCtrl.alert({
header: 'Select category',
inputs: [
{ name: 'category', type: 'radiogroup', value: 'food', options: [
{ name: 'category', type: 'radiobutton', value: 'food', label: 'Food' },
{ name: 'category', type: 'radiobutton', value: 'drinks', label: 'Drinks' },
{ name: 'category', type: 'radiobutton', value: 'desserts', label: 'Desserts' }
]}
],
buttons: [
{ text: 'Cancel', role: 'cancel' },
{ text: 'Select', handler: () => ctrl.dismiss(ctrl._data) }
]
});

await ctrl.present();
const result = await ctrl.onDidDismiss();
// result.data.category is 'food', 'drinks', or 'desserts'

Gotchas and tips

  • ctrl._data contains the input values: the modalAlertComponent collects input values into its _data property (keyed by input name). You typically pass ctrl._data to dismiss() in the button handler. Note the underscore prefix -- this is an internal property exposed for button handlers.
  • onDidDismiss() resolves once: it uses firstValueFrom, so you can only await it once per modal instance.
  • Backdrop dismiss returns null: if the user taps outside the modal and backdropDismiss is true, the dismissal data is null. Always check for this.
  • Price mode divides by 100: the price input type stores cents as an integer and divides by 100 for display. Typing "1234" shows "12.34". This is intentional for touch-screen environments where entering a decimal point is cumbersome.

toastService

Introduction

toastService wraps Ionic's ToastController and LoadingController to provide two kinds of user feedback: coloured toast notifications for brief messages, and a translucent loading indicator for long-running operations. It is used throughout the application -- adhocService uses it to show error messages, download flows use it to show the loading spinner, and features use it for success/warning messages.

When to use

  • Success feedback: "Item saved successfully."
  • Error feedback: "Network error" (shown automatically by adhocService on failure).
  • Warning feedback: "Session will expire in 5 minutes."
  • Loading indicator: wrap any async operation that might take more than a few hundred milliseconds.

How it works

Toasts are Ionic overlays that appear at the bottom of the screen, last for a configurable duration, and dismiss themselves. The loading indicator is a full-screen translucent overlay with a spinner and message.

The loading indicator has a debounce mechanism: HideWait() delays dismissal by 500 ms to prevent flicker when an operation completes almost immediately after ShowWait() is called. Only one loading indicator is shown at a time -- calling ShowWait() while one is already visible is a no-op.

import { toastService, toastType } from '@unpispas/upp-base';

constructor(private toast: toastService) {}

Types

type toastType = 'primary' | 'success' | 'warning' | 'danger';

These map to Ionic's colour system:

  • primary: blue, for informational messages.
  • success: green, for positive outcomes.
  • warning: yellow/orange, for cautions.
  • danger: red, for errors.

Methods

MethodSignatureDescription
ShowAlert(type, text, duration?)(type: toastType, text: string, duration?: number): Promise<void>Displays a coloured toast notification. Default duration: 5,000 ms. Multiple toasts can stack.
ShowWait(message?, timeout?)(message?: string | null, timeout?: number): Promise<void>Presents a translucent loading indicator. Default message: the translation of @loading_wait. Default timeout: 5,000 ms (auto-dismiss if HideWait() is not called). Only one at a time.
HideWait()(): voidDismisses the current loading indicator after a 500 ms delay to prevent flicker.

Usage examples

Show success feedback after saving

await this.saveProduct();
this.toast.ShowAlert('success', 'Product saved successfully');

Wrap an async operation with a loading indicator

this.toast.ShowWait();
try {
await this.syncAllData();
this.toast.ShowAlert('success', 'Sync complete');
} catch (error) {
this.toast.ShowAlert('danger', 'Sync failed');
} finally {
this.toast.HideWait();
}

Show a custom loading message with a longer timeout

this.toast.ShowWait('Generating report...', 30000);
try {
const report = await this.generateReport();
// ...
} finally {
this.toast.HideWait();
}

Error notification with a longer display time

this.toast.ShowAlert('danger', 'Payment processing failed. Please try again.', 10000);

Common patterns

Loading indicator in adhocService.Download

The Download method in adhocService demonstrates the standard pattern: ShowWait() before the operation, HideWait() in the finally block. This ensures the indicator is always dismissed, even on errors.

Automatic error toasts from adhocService

When adhocService.DoRequest() fails with a non-zero status and showtoast is true (the default), it calls this.toast.ShowAlert('danger', ...) automatically. You do not need to show error toasts yourself for standard API calls.

Gotchas and tips

  • Only one loading indicator at a time: if you call ShowWait() while one is already displayed, the call is silently ignored. This prevents stacking spinners.
  • HideWait() has a 500 ms delay: this is intentional to prevent flicker. If the operation completes in under 500 ms, the user briefly sees the loading indicator and then it disappears smoothly.
  • The loading timeout is a safety net: the default 5,000 ms timeout auto-dismisses the loading indicator if HideWait() is never called. For long operations, increase the timeout.
  • Toast messages are not translated automatically: pass already-translated strings. Use languageService.tr() if needed: this.toast.ShowAlert('success', this.lang.tr('@item_saved')).
ServiceRelationship
adhocServiceShows error toasts automatically on HTTP failures. Uses ShowWait/HideWait during downloads.
languageServiceShowWait translates the default loading message via languageService.tr().
alertServiceFor situations that need user input rather than just a notification.