Skip to main content

withDataService()

withDataService() allows to connect a Data Service to the store:

This gives you a store for a CRUD use case:

export const SimpleFlightBookingStore = signalStore(
{ providedIn: 'root' },
withCallState(),
withEntities<Flight>(),
withDataService({
dataServiceType: FlightService,
filter: { from: 'Paris', to: 'New York' },
}),
withUndoRedo()
);

The features withCallState and withUndoRedo are optional, but when present, they enrich each other. Refer to the Undo-Redo section for more information.

The Data Service needs to implement the DataService interface:

@Injectable({
providedIn: 'root'
})
export class FlightService implements DataService<Flight, FlightFilter> {
loadById(id: EntityId): Promise<Flight> { ... }
load(filter: FlightFilter): Promise<Flight[]> { ... }

create(entity: Flight): Promise<Flight> { ... }
update(entity: Flight): Promise<Flight> { ... }
updateAll(entity: Flight[]): Promise<Flight[]> { ... }
delete(entity: Flight): Promise<void> { ... }
[...]
}

Once the store is defined, it gives its consumers numerous signals and methods they just need to delegate to:

@Component(...)
export class FlightSearchSimpleComponent {
private store = inject(SimpleFlightBookingStore);

from = this.store.filter.from;
to = this.store.filter.to;
flights = this.store.entities;
selected = this.store.selectedEntities;
selectedIds = this.store.selectedIds;

loading = this.store.loading;

canUndo = this.store.canUndo;
canRedo = this.store.canRedo;

async search() {
this.store.load();
}

undo(): void {
this.store.undo();
}

redo(): void {
this.store.redo();
}

updateCriteria(from: string, to: string): void {
this.store.updateFilter({ from, to });
}

updateBasket(id: number, selected: boolean): void {
this.store.updateSelected(id, selected);
}

}

DataService with Dynamic Properties

To avoid naming conflicts, the properties set up by withDataService and the connected features can be configured in a typesafe way:

export const FlightBookingStore = signalStore(
{ providedIn: 'root' },
withCallState({
collection: 'flight',
}),
withEntities({
entity: type<Flight>(),
collection: 'flight',
}),
withDataService({
dataServiceType: FlightService,
filter: { from: 'Graz', to: 'Hamburg' },
collection: 'flight',
}),
withUndoRedo({
collections: ['flight'],
})
);

This setup makes them use flight as part of the used property names. As these implementations respect the Type Script type system, the compiler will make sure these properties are used in a typesafe way:

@Component(...)
export class FlightSearchDynamicComponent {
private store = inject(FlightBookingStore);

from = this.store.flightFilter.from;
to = this.store.flightFilter.to;
flights = this.store.flightEntities;
selected = this.store.selectedFlightEntities;
selectedIds = this.store.selectedFlightIds;

loading = this.store.flightLoading;

canUndo = this.store.canUndo;
canRedo = this.store.canRedo;

async search() {
this.store.loadFlightEntities();
}

undo(): void {
this.store.undo();
}

redo(): void {
this.store.redo();
}

updateCriteria(from: string, to: string): void {
this.store.updateFlightFilter({ from, to });
}

updateBasket(id: number, selected: boolean): void {
this.store.updateSelectedFlightEntities(id, selected);
}

}