Creating a Checklist Service
In this lesson, we are going to take the data entered into our form and save it using a service. This will be our first chance to put those state management concepts we have been talking about into practice.
The first thing we are going to do is add a few more types.
Modify
src/app/shared/interfaces/checklist.ts
to reflect the following:
export interface Checklist {
id: string;
title: string;
}
export type AddChecklist = Omit<Checklist, 'id'>;
export type EditChecklist = { id: Checklist['id']; data: AddChecklist };
export type RemoveChecklist = Checklist['id'];
We have added three types here: AddChecklist
, EditChecklist
, and
RemoveChecklist
. We are going to use different types of data for different
actions.
When we want to add a checklist we will only supply the title
as the id
is generated automatically. To handle this we create a new type using the Omit
utility type that allows us to remove a particular property from an existing
type.
When we edit a checklist we will supply the action in this format:
{
id: 'id of checklist being edited',
data: { /* data we want to update */ }
}
To do this, we create a new type using Checklist['id']
which will become
whatever the type of the id
property is in the Checklist
type. We do
a similar thing for our remove type too where we will only need to supply
the id
of the checklist we are removing.
Now let’s move on to creating the service.
Create a file at
src/app/shared/data-access/checklist.service.ts
and add the following:
import { Injectable, computed, signal } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { Subject } from 'rxjs';
import { AddChecklist, Checklist } from '../interfaces/checklist';
export interface ChecklistsState {
checklists: Checklist[];
}
@Injectable({
providedIn: 'root',
})
export class ChecklistService {
// state
private state = signal<ChecklistsState>({
checklists: [],
});
// selectors
checklists = computed(() => this.state().checklists);
// sources
add$ = new Subject<AddChecklist>();
constructor() {
// reducers
this.add$.pipe(takeUntilDestroyed()).subscribe((checklist) =>
this.state.update((state) => ({
...state,
checklists: [...state.checklists, this.addIdToChecklist(checklist)],
}))
);
}
private addIdToChecklist(checklist: AddChecklist) {
return {
...checklist,
id: this.generateSlug(checklist.title),
};
}
private generateSlug(title: string) {
// NOTE: This is a simplistic slug generator and will not handle things like special characters.
let slug = title.toLowerCase().replace(/\s+/g, '-');
// Check if the slug already exists
const matchingSlugs = this.checklists().find(
(checklist) => checklist.id === slug
);
// If the title is already being used, add a string to make the slug unique
if (matchingSlugs) {
slug = slug + Date.now().toString();
}
return slug;
}
}