Button Interactions
Four interaction guards you would otherwise hand-roll on every project: promise-aware action with automatic loading and single-flight protection, clickGuard throttle/debounce, holdToConfirm for destructive actions and autoRepeat for spinner buttons. All of them funnel into the (clicked) output.
Async actions & loading
Bind a promise-returning function to action and the button manages the async lifecycle for you: it flips loading on, disables itself, silently ignores further clicks until the promise settles (single-flight), then emits actionDone with the resolved value or actionFailed with the error. No flags to manage in your component.
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { OgeButton } from '@oge-ui/buttons';
@Component({
selector: 'demo-root',
imports: [OgeButton],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<oge-button
text="Save changes"
severity="accent"
[action]="save"
(actionDone)="log('saved!')"
(actionFailed)="log('failed')"
/>
`,
})
export class Demo {
protected readonly save = () =>
new Promise<void>((resolve) => setTimeout(resolve, 1500));
protected log(message: string): void {
console.log(message);
}
}Click guard
Rate-limits the clicked output against double submissions and spam. true is a ready-made 500ms throttle (first click wins, the window swallows the rest); { mode: 'debounce', ms } waits for the clicks to stop and fires once. The guard also applies to hold completions and auto-repeat ticks.
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import { OgeButton } from '@oge-ui/buttons';
@Component({
selector: 'demo-root',
imports: [OgeButton],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<!-- true = throttle with the 500ms config default -->
<oge-button text="Submit once" [clickGuard]="true" (clicked)="count()" />
<!-- trailing debounce -->
<oge-button
text="Debounced"
[clickGuard]="{ mode: 'debounce', ms: 400 }"
(clicked)="count()"
/>
`,
})
export class Demo {
protected readonly clicks = signal(0);
protected count(): void {
this.clicks.update((value) => value + 1);
}
}Hold to confirm
Arms destructive actions behind an uninterrupted press: a fill sweeps across the button while held and brightens when the duration elapses — releasing then fires clicked. Quick taps do nothing. Escape, dragging away or pointer-cancel aborts; keyboard users hold Space or Enter. The fill is pure CSS (transform-only), so it costs no script time.
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { OgeButton } from '@oge-ui/buttons';
@Component({
selector: 'demo-root',
imports: [OgeButton],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<oge-button
text="Delete account"
severity="danger"
[holdToConfirm]="{ ms: 1200 }"
(clicked)="destroyEverything()"
/>
`,
})
export class Demo {
protected destroyEverything(): void {
console.warn('boom');
}
}Auto-repeat
For spinner and counter buttons: holding the button re-fires clicked — once immediately, then repeatedly after delayMs at intervalMs. Repeating stops on release, on pointer-cancel, or the moment the button becomes disabled. Mutually exclusive with holdToConfirm (hold wins).
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { OgeButton } from '@oge-ui/buttons';
@Component({
selector: 'demo-root',
imports: [OgeButton],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<oge-button
text="+"
hint="Increment"
[autoRepeat]="{ delayMs: 400, intervalMs: 80 }"
(clicked)="value = value + 1"
/>
`,
})
export class Demo {
protected value = 0;
}Notes
loadingis a two-way model — drive it manually or letactionmanage it. A loading button is disabled and exposesaria-busyplus a screen-reader label from the messages config (provideOgeButtonsConfig).- Guards compose: a hold completion or auto-repeat tick still passes through
clickGuardbefore emitting. holdToConfirmandautoRepeatare mutually exclusive; hold wins and dev mode logs an error.- In gesture modes native clicks are swallowed (and stopped from bubbling) so a quick tap can never trigger outer
(click)listeners.