Stepper
@xngui/components/stepper@xngui/components/stepperMulti-step flow — compose with x-step panels.
Preview
import { Component, computed, signal, viewChild } from '@angular/core';
import { form, FormField } from '@angular/forms/signals';
import { XAlert } from '@xngui/components/alert';
import { XButton } from '@xngui/components/button';
import { XCheckbox } from '@xngui/components/checkbox';
import { XForm } from '@xngui/components/form';
import { XFormField } from '@xngui/components/form-field';
import { XInput } from '@xngui/components/input';
import { XRadio } from '@xngui/components/radio';
import { XRadioGroup } from '@xngui/components/radio-group';
import { XStep, XStepper } from '@xngui/components/stepper';
import { email, required } from '@xngui/components/validation';
import type { XChangeEvent } from '@xngui/core';
type XWizardModel = {
name: string;
email: string;
plan: string;
acceptTerms: boolean;
};
const WIZARD_STEP_COUNT = 3;
protected readonly selectedIndex = signal(0);
protected readonly wizardSubmitted = signal(false);
protected readonly wizardModel = signal<XWizardModel>({
name: '',
email: '',
plan: 'starter',
acceptTerms: false,
});
protected readonly wizardForm = form(this.wizardModel, (path) => {
required(path.name);
required(path.email);
email(path.email);
required(path.acceptTerms);
});
private readonly wizardFormHost = viewChild<XForm<XWizardModel>>("wizardFormHost");
protected readonly isLastStep = computed(() => this.selectedIndex() === WIZARD_STEP_COUNT - 1);
protected goNext(stepper: XStepper): void {
if (!this.validateCurrentStep()) {
this.wizardFormHost()?.focusFirstInvalid();
return;
}
stepper.next();
}
protected goBack(stepper: XStepper): void {
this.wizardSubmitted.set(false);
stepper.previous();
}
protected canGoBack(): boolean {
return this.selectedIndex() > 0;
}
protected onWizardSubmit(value: XWizardModel): void {
this.wizardSubmitted.set(true);
}
protected onWizardInvalid(): void {
// focusFirstInvalid() already ran inside x-form
}
protected onWizardReset(): void {
this.wizardSubmitted.set(false);
this.selectedIndex.set(0);
}
protected onChanged(event: XChangeEvent<number>): void {
const { value, previousValue } = event;
if (value > previousValue && !this.validateBeforeLeavingStep(previousValue)) {
this.selectedIndex.set(previousValue);
this.wizardFormHost()?.focusFirstInvalid();
return;
}
}
private validateCurrentStep(): boolean {
return this.validateBeforeLeavingStep(this.selectedIndex());
}
private validateBeforeLeavingStep(stepIndex: number): boolean {
if (stepIndex !== 0) {
return true;
}
this.wizardForm.name().markAsTouched();
this.wizardForm.email().markAsTouched();
return !this.wizardForm.name().invalid() && !this.wizardForm.email().invalid();
}
Events
Interact with the preview — events will appear here.