NgStore Class Abstract
The NgStore
class is a foundational component for implementing reactive state management and
handling asynchronous data flow in modules or entire applications. It serves as a central hub,
organizing and managing store items like emitters, states, and groups, ensuring seamless
interaction among them.
Stores can also include methods to trigger specific actions, making them a powerful and flexible tool for coordinating complex application logic. Their structured design simplifies the development of scalable, maintainable, and reactive applications, ensuring consistency and clarity in managing state and data flow.
By implementing the StoreHooks interface, the store provides lifecycle hooks for executing custom logic before and after key events, such as store initialization and completion.
The NgStore
class extends Store and serves as
an abstract foundation designed to serve as a base for specific store implementations in Angular
applications that define concrete collections of store items
API
abstract class NgStore extends Store {
initialize(beforeInit?: (store: this) => void): this;
complete(): void;
unregisterOnDestroy(): this;
protected markAsReady(): void;
}
Example
import {computed, Injectable} from '@angular/core';
import {switchMap} from 'rxjs';
import {emitter, transmit} from '@bitfiber/rx';
import {NgStore, asyncSignalGroup, routeFiltersGroup, signalState} from '@bitfiber/ng/rx';
interface Product {
id: number;
name: string;
price: number;
}
interface ProductCategory {
id: number;
name: string;
}
interface ProductsFilters {
search: string;
page: number;
}
interface ProductsState {
categories: ProductCategory[];
products: Product[];
}
@Injectable()
class ProductsStore extends NgStore {
// Provides the start of the first data loading process
start = emitter<void>();
// Provides the filters state that is synchronized with the route and the filters form
routeFilters = routeFiltersGroup<ProductsFilters>({
initialQueryParams: {search: '', page: 1},
}, ({filters}) => {
filters.useLazyEmission();
})
// Provides an async group for managing categories loading process
categoriesReq = asyncSignalGroup<void, ProductCategory[], Error>((categoriesReq, {launch}) => {
launch
// Triggers categories loading once the start emitter emits
.wait(this.start)
// Defines a side effect for loading categories
.effect(switchMap(() => categoriesService.get().pipe(transmit(categoriesReq))));
}, []);
// Provides an async group for managing products loading process
productsReq = asyncSignalGroup<ProductsFilters, Product[], Error>((productsReq, {launch}) => {
launch
// Triggers products loading after categories are successfully loaded
.wait(this.categoriesReq.success, () => this.routeFilters.filters())
// Reloads products when filters are updated
.receive(this.routeFilters.filters)
// Defines a side effect for loading products
.effect(switchMap(filters => productsService.get(filters).pipe(transmit(productsReq))));
}, []);
// Provides the loading status state
isLoading = computed(() => this.categoriesReq.state().inProgress
|| this.productsReq.state().inProgress);
// Provides the main store state
data = signalState<ProductsState>({categories: [], products: []}, s => s
// Combines data from categories and products into a single state
.select(
this.categoriesReq.success,
this.productsReq.success,
(categories, products) => ({categories, products}),
),
);
// Provides the store error handling
errors = emitter<Error>(e => e
// Collects errors from asynchronous actions
.receive(this.categoriesReq.fail, this.productsReq.fail)
// Handle errors with a side effect
.tap(error => console.log('Error:', error)));
// Marks the store as ready, indicating that all store items have been defined
#ready = this.markAsReady();
// Automatically starts the store after initialization
afterStoreInit(): void {
this.start.emit();
}
}
import {Component, inject} from '@angular/core';
import {ReactiveFormsModule} from '@angular/forms';
@Component({
selector: 'bf-products',
standalone: true,
templateUrl: './products.component.html',
imports: [ReactiveFormsModule],
providers: [ProductsStore],
})
export class ProductsComponent {
readonly store = inject(ProductsStore).initialize();
readonly form = this.store.routeFilters.form;
}
@if (!store.isLoading()) {
<div
class="bf-filters"
[formGroup]="form"
>
<input formControlName="search"/>
<pagenator formControlName="page"/>
</div>
@for (product of store.data().products; track product.id) {
<div class="bf-product">
{{product.name}} - {{product.price}}
</div>
}
} @else {
Data is loading...
}