Creating a Checklist Service
In this lesson, we are going to take the data entered into our form and save it using a service. The first thing we are going to do is create the service itself.
Create a file at
src/app/shared/data-access/checklist.service.ts
and add the following:
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
import { Checklist } from '../interfaces/checklist';
@Injectable({
providedIn: 'root',
})
export class ChecklistService {
private checklists$ = new BehaviorSubject<Checklist[]>([]);
getChecklists() {
return this.checklists$.asObservable();
}
add(checklist: Pick<Checklist, 'title'>) {
const newChecklist = {
...checklist,
id: this.generateSlug(checklist.title),
};
this.checklists$.next([...this.checklists$.value, newChecklist]);
}
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$.value.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;
}
}
This is quite similar to some stuff we have seen before, especially in relation to how we are using a BehaviorSubject
. Specifically, we create this stream:
private checklists$ = new BehaviorSubject<Checklist[]>([]);
Which will allow us to emit new checklist values by triggering its next
method, and we expose this stream as a simple observable to the rest of the application:
getChecklists() {
return this.checklists$.asObservable();
}
This way, other parts of the application will not be able to trigger the next
method on the stream. Since our BehaviorSubject
is private
only code within the service call will be able to access it to emit new values.
There are also some strange things going on here that we haven’t seen before. Like this:
add(checklist: Pick<Checklist, 'title'>) {
const newChecklist = {
...checklist,
id: this.generateSlug(checklist.title),
};
this.checklists$.next([...this.checklists$.value, newChecklist]);
}
This is similar to what we did in the todo application. We pass in a checklist
, and then we use the spread syntax to emit this value:
[...this.checklists$.value, newChecklist]
This gets the array of all current checklists in the stream, and spreads them out into a new array, and then we add out newChecklist
onto the end of all of those. The result is a new single array that contains all of the old checklists and our new one. But the strangest thing about this method is perhaps this:
add(checklist: Pick<Checklist, 'title'>)
We are using the TypeScript Pick
utility type. This isn’t strictly needed, but it can make our lives easier. What Pick
allows us to do is take an existing type, Checklist
in this case, and pick the properties we want to apply from that type. If you remember, a Checklist
is made up of two properties:
export interface Checklist {
id: string;
title: string;
}
But, we don’t want to supply the unique id
ourselves when we create the checklist - we want this to be auto generated. So, we use Pick
to modify this type to only include the title
property.
Then, we trigger this method to generate the id
for us:
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$.value.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;
}