Angular Integration
The @asyncflowstate/angular package provides Observable/BehaviorSubject bindings for Angular applications.
Installation
npm install @asyncflowstate/angular @asyncflowstate/coreCore Service (createFlow)
The createFlow feature generates an isolated flow state container instance. This instance acts as a fully self-contained state machine that ties directly into your component's template lifecycle using Angular observables.
Example
import { Component, OnDestroy } from "@angular/core";
import { createFlow } from "@asyncflowstate/angular";
@Component({
selector: "app-user",
template: `
<ng-container *ngIf="userFlow.state$ | async as state">
<button (click)="userFlow.execute('user-123')" [disabled]="state.loading">
{{ state.loading ? "Loading..." : "Fetch User" }}
</button>
<div *ngIf="state.error" class="error">
{{ state.error.message }}
</div>
<div *ngIf="state.data" class="user-card">
<h2>{{ state.data.name }}</h2>
<p>{{ state.data.email }}</p>
</div>
</ng-container>
`,
})
export class UserComponent implements OnDestroy {
userFlow = createFlow(
async (id: string) => {
const response = await fetch(`/api/users/${id}`);
return response.json();
},
{
onSuccess: (user) => console.log("Fetched:", user.name),
retry: { maxAttempts: 3, backoff: "exponential" },
},
);
ngOnDestroy() {
this.userFlow.destroy();
}
}Observable API (RxJS)
AsyncFlowState exposes the current state through an RxJS BehaviorSubject via the state$ property, allowing seamless native integration with Async Pipes and manual subscription logic.
Example
The flow exposes a state$ Observable (BehaviorSubject) that emits state updates:
interface FlowState<T> {
status: "idle" | "loading" | "success" | "error";
loading: boolean;
data: T | null;
error: Error | null;
}Using the Async Pipe
<ng-container *ngIf="flow.state$ | async as state">
<!-- Reactive template -->
</ng-container>Manual Subscription
export class MyComponent implements OnInit, OnDestroy {
flow = createFlow(fetchData);
private subscription: Subscription;
ngOnInit() {
this.subscription = this.flow.state$.subscribe((state) => {
if (state.status === "success") {
this.router.navigate(["/dashboard"]);
}
});
}
ngOnDestroy() {
this.subscription.unsubscribe();
this.flow.destroy();
}
}Form Integration
Easily trace form validations and seamlessly wrap Angular Template-Driven or Reactive Forms with flow states.
Example
@Component({
template: `
<form (ngSubmit)="onSubmit()">
<input [(ngModel)]="formData.name" name="name" />
<input [(ngModel)]="formData.email" name="email" type="email" />
<ng-container *ngIf="saveFlow.state$ | async as state">
<button type="submit" [disabled]="state.loading">
{{ state.loading ? "Saving..." : "Save" }}
</button>
<p *ngIf="state.error" class="error">{{ state.error.message }}</p>
</ng-container>
</form>
`,
})
export class ProfileFormComponent implements OnDestroy {
formData = { name: "", email: "" };
saveFlow = createFlow(async (data: typeof this.formData) => {
return await fetch("/api/profile", {
method: "POST",
body: JSON.stringify(data),
}).then((r) => r.json());
});
onSubmit() {
this.saveFlow.execute(this.formData);
}
ngOnDestroy() {
this.saveFlow.destroy();
}
}Cleanup
Always call this.flow.destroy() in ngOnDestroy() to prevent memory leaks from lingering subscriptions.
Dependency Injection
Create globally or feature-scoped async services by instantiating createFlow inside an @Injectable service, providing singleton flows across your app.
Example
@Injectable({ providedIn: "root" })
export class UserService {
private fetchFlow = createFlow(async (id: string) => api.getUser(id), {
retry: { maxAttempts: 2 },
});
get state$() {
return this.fetchFlow.state$;
}
fetchUser(id: string) {
return this.fetchFlow.execute(id);
}
destroy() {
this.fetchFlow.destroy();
}
}Best Practices
Building a professional async experience in Angular requires a focus on observables and lifecycle safety. Follow these patterns for the best results.
Observable Patterns
Prefer the Async Pipe
Whenever possible, use the | async pipe in your templates. This automatically handles subscription and unsubscription for you, reducing boilerplate and preventing potential memory leaks.
<div *ngIf="flow.state$ | async as state">
{{ state.loading ? '...' : 'Data Ready' }}
</div>Centralize in Services
Instead of creating flows directly in your components, move them to @Injectable() services. This allows multiple components to share the same flow engine and state results across different parts of your application.
Lifecycle Safety
The destroy() Call
Always ensure this.flow.destroy() is called in ngOnDestroy. Because Angular components are frequently created and destroyed, failing to clean up your flow engine can lead to orphaned subscriptions and performance degradation.
UX & Performance
Change Detection
AsyncFlowState uses BehaviorSubject internally, which integrates seamlessly with ChangeDetectionStrategy.OnPush. This ensures your components only re-render when the flow state actually changes, optimizing performance for complex dashboards.
Rule of 400ms
Next JS or React actions can feel "jittery" if they return too quickly, causing a loading spinner to flash for just a few frames. Use loading: { minDuration: 400 } to ensure your UI feels stable and predictable.
