NgStoreGroups

Groups

Groups are collections of emitters, states, and other groups that are unified under a specific feature. They help organize related reactive sources into a cohesive structure, ensuring proper initialization and completion of all items as a single unit.

ℹ️

For more detailed information about using groups, refer to the Basic Groups Documentation.

List of Groups

  1. Async Signal Group

The async signal group manages the lifecycle of asynchronous actions, providing emitters for launching actions, handling success, dealing with failures, and managing the signal state of these actions.

It provides the same functionality as the AsyncGroup. However, it uses a signal state instead of a standard state that can be used in Angular’s effect or computed functions and in other places where you would normally use a signal.

import {computed} from '@angular/core';
import {switchMap} from 'rxjs';
import {transmit} from '@bitfiber/rx';
import {signalState, asyncSignalGroup} from '@bitfiber/ng/rx';
 
interface Product {
  id: number;
  name: string;
  price: number;
}
 
interface ProductsState {
  products: Product[];
  isLoading: boolean;
}
 
// Provides an async group for managing products loading process
const productsReq = asyncSignalGroup<number, Product[], Error>((group, {
  launch, success, fail, finish,
}) => {
  group
    // Keeps cached data for 120 seconds, with a maximum entry count of 10
    .useCache(120, 10);
 
  launch
    // Defines a side effect for loading products
    .effect(switchMap(page => productsService.get(`api/products?page=${page}`)
      .pipe(transmit(group))));
 
  success
    // Performs a tap callback each time the request succeeds
    .tap(data => console.log(data));
 
  fail
    // Performs a tap callback each time the request fails
    .tap(error => console.log(error));
 
  finish
    // Performs a tap callback each time the request either fails or succeeds
    .tap(() => console.log('Request has been finished'));
}, []);
 
// Provides the main state
const data = signalState<ProductsState>({products: [], isLoading: false}, s => s
  // Receives request success data
  .receive(productsReq.success, (products, state) => ({...state, products}))
);
 
// Provides the loading status state
const isLoading = computed(() => productsReq.state().inProgress);
 
// Starts the products loading process
productsReq.launch.emit(1);
  1. Route Group

The route group simplifies the reactive management of Angular’s route data, including params, query params, and the fragment, all within the current route.

Route states are represented as signal states, allowing them to be used in Angular’s reactive constructs such as effect and computed functions, as well as in other contexts where signals are commonly applied.

import {computed} from '@angular/core';
import {switchMap} from 'rxjs';
import {emitter} from '@bitfiber/rx';
import {routeGroup} from '@bitfiber/ng/rx';
 
interface RouteParams {
  id: number;
  type: string;
}
 
interface RouteQueryParams {
  page: number;
  search: string;
  groupId: number | null;
}
 
// Creates a route group for managing route data
const route = routeGroup<RouteQueryParams, RouteParams>({
  initialParams: {id: 0, type: 'all'},
  initialQueryParams: {search: '', page: 1, groupId: null},
  segments: params => [params.type, params.id],
  hasFragment: true,
}, ({params, queryParams, allParams, fragment}) => {
  params
    // Defines a side effect for the route params change
    .tap(data => console.log(data));
 
  queryParams
    // Defines a side effect for the query params change
    .tap(data => console.log(data));
 
  allParams
    // Defines a side effect for changes in either route or query params
    .tap(data => console.log(data));
 
  fragment
    // Defines a side effect for fragment changes
    .tap(fragment => console.log(fragment));
});
 
// Creates an emitter for triggering the products loading process
const productsReq = emitter<RouteQueryParams & RouteParams>(e => e
  // Reloads products when route params are updated
  .receive(route.allParams)
  // Defines a side effect for the products loading process
  .effect(switchMap(params => productsService.getAll(params))));
 
// Computes a CSS class based on the route type
const typeClass = computed(() => `bf-${route.params().type}`);
 
// Initializes the group and all its items
route.initialize();
 
// Updates the route params and their associated state
route.params.set({id: 2, type: 'new'});
 
// Updates the query params and their associated state
route.queryParams.update(state => ({...state, search: 'abc'}));
 
// Updates both params and query params in the route and their associated states
route.allParams.update(state => ({...state, type: 'old', search: 'abc'}));
 
// Updates the fragment in the route and its associated state
route.fragment.set('someFragment');
 
// Simultaneously updates all states and the route params, query params, and fragment
route.changeUrl({id: 2, type: 'new', search: 'abc'}, 'someFragment');
 
// Resets all states, params, query params, and fragment to their initial values
route.resetUrl();
 
// Completes the group and all its items
route.complete();
  1. Route Filters Group

The route filters group simplifies the reactive management of Angular’s form-based filters by linking these filters to the route.

The filters state is represented as a signal state, allowing it to be used in Angular’s reactive constructs such as effect and computed functions, as well as in other contexts where signals are commonly applied.

products.store.ts
import {computed, Injectable} from '@angular/core';
import {switchMap} from 'rxjs';
import {transmit} from '@bitfiber/rx';
import {asyncSignalGroup, routeFiltersGroup, signalState, NgStore} from '@bitfiber/ng/rx';
 
interface ProductsFilters {
  search: string;
  page: number;
}
 
interface Product {
  id: number;
  name: string;
  price: number;
}
 
interface ProductsState {
  products: Product[];
}
 
@Injectable()
class ProductsStore extends NgStore {
  // Synchronizes the filters state with the route and the filters form
  routeFilters = routeFiltersGroup<ProductsFilters>({
    initialQueryParams: {search: '', page: 1},
  });
 
  // Provides an async group for managing products loading process
  productsReq = asyncSignalGroup<ProductsFilters, Product[], Error>((productsReq, {launch}) => {
    launch
      // 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.productsReq.state().inProgress);
 
  // Provides the main store state
  data = signalState<ProductsState>({products: []}, s => s
    // Updates the state when the products request succeeds
    .receive(this.productsReq.success, products => ({products})),
  );
}
products.component.ts
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;
}
products.component.html
@if (store.isLoading()) {
  <div
    class="bf-filters"
    [formGroup]="form"
  >
    <input formControlName="search"/>
    <pagenator formControlName="page"/>
  </div>
 
  @for (product of store.data().products) {
    <div class="bf-product">
      {{product.name}}
    </div>
  }
} @else {
  Data is loading...
}