Skip to content

Editing and Deleting Data

The last key feature we need to focus on is the ability to edit and delete the checklists and checklist items. This will involve a reasonably simple extension in terms of the functionality in our services.

Update the Services

We will start by adding support for editing and deleting items in the ChecklistItemService. This is by no means easy or obvious, but this will serve well as an opportunity to implement a slightly more advanced feature with no guidance.

Before continuing, see if you can add new sources and reducers that allow deleting and editing specific checklist items. We have already created the appropriate types for EditChecklistItem and RemoveChecklistItem so you can use these as reference for the type of data you will be working with.

See how much progress you can make on this and then take a look at my solution below (don’t worry if you get stuck).

remove$ = new Subject<RemoveChecklistItem>();
edit$ = new Subject<EditChecklistItem>();
this.edit$.pipe(takeUntilDestroyed()).subscribe((update) =>
this.state.update((state) => ({
...state,
checklistItems: state.checklistItems.map((item) =>
item.id === update.id ? { ...item, title: update.data.title } : item
),
}))
);
this.remove$.pipe(takeUntilDestroyed()).subscribe((id) =>
this.state.update((state) => ({
...state,
checklistItems: state.checklistItems.filter((item) => item.id !== id),
}))
);

That’s all we need to do — we can now next the edit$ and remove$ sources to edit or remove checklist items.

Now we want to handle removing and editing checklists in the ChecklistService… but we have a bit of a surprise in store first.

Consider that if we want to remove a checklist, we will also want to remove all of the checklist items for that checklist. That means that our ChecklistItemService is going to need to know when the removal of a checklist is triggered, so that it can handle removing all of the items related to that checklist.

To handle this, we are going to create a shared source. We are actually going to add a checklistRemoved$ source to our ChecklistItemService. Our ChecklistItemService will react to that by removing all of the items. Then, in our ChecklistService we will use the same checklistRemoved$ source from ChecklistItemService and the ChecklistService will react to that same source emitting by removing the appropriate checklist. When we trigger the checklistRemoved$ source in the ChecklistItemService both our ChecklistItemService and our ChecklistService will automatically react (just in different ways).

checklistRemoved$ = new Subject<RemoveChecklist>();
this.checklistRemoved$.pipe(takeUntilDestroyed()).subscribe((checklistId) =>
this.state.update((state) => ({
...state,
checklistItems: state.checklistItems.filter(
(item) => item.checklistId !== checklistId
),
}))
);

Now when the checklistRemoved$ source emits we will remove all of the items for that checklistId.

Now we will utilise this same checklistRemoved$ source in our ChecklistService.

remove$ = this.checklistItemService.checklistRemoved$;

Now we inject the ChecklistItemService and utilise the same source for the removal in the ChecklistService. This is better than having two separate sources in each service, because we would need to make sure we trigger the sources in both services whenever we want to remove a checklist. This way, we can just trigger the one shared source.

We also need a source to handle editing in the ChecklistService as well.

edit$ = new Subject<EditChecklist>();
this.remove$.pipe(takeUntilDestroyed()).subscribe((id) =>
this.state.update((state) => ({
...state,
checklists: state.checklists.filter((checklist) => checklist.id !== id),
}))
);
this.edit$.pipe(takeUntilDestroyed()).subscribe((update) =>
this.state.update((state) => ({
...state,
checklists: state.checklists.map((checklist) =>
checklist.id === update.id
? { ...checklist, title: update.data.title }
: checklist
),
}))
);

Now we can handle editing and removing in our ChecklistService too!

User Interface for Deleting and Editing Checklists

Our services support editing and deleting now, but we have no way to do that in our user interface yet.

Let’s start by adding a way to edit and delete a particular checklist. We are going to start by adding buttons in our ChecklistListComponent that will trigger delete and edit events.

delete = output<RemoveChecklist>();
edit = output<Checklist>();
<li>
<a routerLink="/checklist/{{ checklist.id }}">
{{ checklist.title }}
</a>
<div>
<button (click)="edit.emit(checklist)">Edit</button>
<button (click)="delete.emit(checklist.id)">Delete</button>
</div>
</li>

We have added both the edit and delete here, but for now we will focus on just handling the delete event in the HomeComponent.

<app-checklist-list
[checklists]="checklistService.checklists()"
(delete)="checklistService.remove$.next($event)"
(edit)="checklistBeingEdited.set($event)"
/>

Pretty nice right? All we need to do now is next the remove$ source, or in the case of editing we are setting the checklistBeingEdited to whatever checklist we want to edit.

There is still one small problem we need to handle. When we set the checklistBeingEdited it will open our form modal which is great… but the form does not contain the information for that particular checklist.

To deal with this, we are going to need to update our effect.

constructor() {
effect(() => {
const checklist = this.checklistBeingEdited();
if (!checklist) {
this.checklistForm.reset();
} else {
this.checklistForm.patchValue({
title: checklist.title,
});
}
});
}

Now as well as resetting the form, if there is a checklist set in the signal we will call the patchValue method on our form. This will take whatever values we supply to it and update the form with those values.

We are almost there now. However, if you try saving the checklist when attempting to edit it, you will notice that it actually creates a new checklist instead of editing this existing one. Our save binding is to blame for this:

<app-form-modal
[title]="
checklistBeingEdited()?.title
? checklistBeingEdited()!.title!
: 'Add Checklist'
"
[formGroup]="checklistForm"
(close)="checklistBeingEdited.set(null)"
(save)="checklistService.add$.next(checklistForm.getRawValue())"
/>

When we are saving a checklist we always next the add$ source, which is not what we want if we are trying to edit.

(save)="
checklistBeingEdited()?.id
? checklistService.edit$.next({
id: checklistBeingEdited()!.id!,
data: checklistForm.getRawValue()
})
: checklistService.add$.next(checklistForm.getRawValue())
"

Now we change what source we call based on whether an existing checklist has been set into the checklistBeingEdited signal or not.

Now editing our checklists works

User Interface for Editing and Deleting Checklist Items

Once again, we are now basically just going to duplicate all of what we just did for the ChecklistComponent in the ChecklistItemListComponent. See if you can set all of this up yourself:

  • Add the necessary outputs to ChecklistItemListComponent and add buttons to trigger them
  • React to the edit and delete events in the ChecklistComponent
  • Update the effect to patch the form
  • Update the save binding to call the correct source
delete = output<RemoveChecklistItem>();
edit = output<ChecklistItem>();
<div>
<button (click)="toggle.emit(item.id)">Toggle</button>
<button (click)="edit.emit(item)">Edit</button>
<button (click)="delete.emit(item.id)">Delete</button>
</div>
<app-checklist-item-list
[checklistItems]="items()"
(delete)="checklistItemService.remove$.next($event)"
(edit)="checklistItemBeingEdited.set($event)"
(toggle)="checklistItemService.toggle$.next($event)"
/>
constructor() {
effect(() => {
const checklistItem = this.checklistItemBeingEdited();
if (!checklistItem) {
this.checklistItemForm.reset();
} else {
this.checklistItemForm.patchValue({
title: checklistItem.title,
});
}
});
}
<app-form-modal
title="Create item"
[formGroup]="checklistItemForm"
(save)="
checklistItemBeingEdited()?.id
? checklistItemService.edit$.next({
id: checklistItemBeingEdited()!.id!,
data: checklistItemForm.getRawValue(),
})
: checklistItemService.add$.next({
item: checklistItemForm.getRawValue(),
checklistId: checklist()?.id!,
})
"
(close)="checklistItemBeingEdited.set(null)"
></app-form-modal>

…and that’s it! We can now edit and delete both checklists and checklist items.

One thing I think is really cool about what we’ve set up here is that as we are deleting and editing all of these items, we never have to worry about triggering a save to storage because of the effect we set up to automatically save whenever the state of the checklists or checklist items change.