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, but updating our user interface to support the new functionality will be a little more involved.
Update the Services
Add the following methods to the
ChecklistItemService
:
update(id: string, editedItem: AddChecklistItem) {
const newItems = this.checklistItems$.value.map((item) =>
item.id === id ? { ...item, title: editedItem.title } : item
);
this.checklistItems$.next(newItems);
}
remove(id: string) {
const modifiedItems = this.checklistItems$.value.filter(
(item) => item.id !== id
);
this.checklistItems$.next(modifiedItems);
}
removeAllItemsForChecklist(checklistId: string) {
const modifiedItems = this.checklistItems$.value.filter(
(item) => item.checklistId !== checklistId
);
this.checklistItems$.next(modifiedItems);
}
The first two methods here are pretty similar to what we have been seeing so far. To update
a checklist item we take the existing values, map them, and if we find an item matching the id
passed in we update it with the values from editedItem
(otherwise we return the item
unchanged).
To remove a checklist item we filter
the current values to remove any item
that matches the id
passed in.
The removeAllItemsForChecklist
method is also quite similar, but its purpose might not be immediately obvious. Our ChecklistItemService
keeps track of checklist items independently of the actual checklist itself. We then “join” these items to their checklist by using the checklistId
field. If you are familiar with relational databases, we are basically using a foreign key to create a many to one relationship.
When we delete a checklist (which we will handle in the ChecklistService
) we also want to remove all of the items for that checklist, otherwise the checklist items will remain in storage/memory but since they don’t belong to a checklist anymore they would never be accessible. To deal with this situation, we will call out removeAllItemsForChecklist
method whenever we delete a checklist.
Add the following methods to the
ChecklistService
:
remove(id: string) {
const modifiedChecklists = this.checklists$.value.filter(
(checklist) => checklist.id !== id
);
this.checklistItemService.removeAllItemsForChecklist(id);
this.checklists$.next(modifiedChecklists);
}
update(id: string, editedData: AddChecklist) {
const modifiedChecklists = this.checklists$.value.map((checklist) =>
checklist.id === id
? { ...checklist, title: editedData.title }
: checklist
);
this.checklists$.next(modifiedChecklists);
}
NOTE: The ChecklistService
utilises the ChecklistItemService
so you will need to make sure to inject that through the constructor
of your service in order for the code above to work. I will leave that for you to implement.
Again, same ideas as usual here - the only key difference being that we are making use of that removeAllItemsForChecklist
method.
User Interface for Deleting Checklist
Deleting a checklist or checklist item is going to be easier than editing, so let’s start with that. The general approach we are going to take here is to have our ChecklistList
component emit an event when the delete button is clicked on a particular item - just like we did with the toggle
event. Then our parent component can listen for that event, and trigger the method in our service as a result.
We will need a delete button of some kind inside of our ChecklistList
dumb component, and we know that we are going to need an edit button soon as well, so we have to think about how we want to incorporate that.
Ionic has a really nice component called ion-item-sliding
that allows us to easily implement a sliding button with action buttons underneath. You slide the item to the left (or right depending on how it is configured) to reveal action buttons for that item. We are going to use that.
Add the following outputs to the
ChecklistList
component:
@Output() delete = new EventEmitter<string>();
@Output() edit = new EventEmitter<Checklist>();
Modify the template of the
ChecklistList
to reflect the following:
<ion-list lines="none">
<ion-item-sliding
*ngFor="let checklist of checklists; trackBy: trackByFn"
>
<ion-item
data-test="checklist-item"
button
routerLink="/checklist/{{ checklist.id }}"
routerDirection="forward"
>
<ion-label>{{ checklist.title }}</ion-label>
</ion-item>
<ion-item-options side="end">
<ion-item-option color="light" (click)="edit.emit(checklist)">
<ion-icon name="pencil-outline" slot="icon-only"></ion-icon>
</ion-item-option>
<ion-item-option (click)="delete.emit(checklist.id)" color="danger">
<ion-icon name="trash" slot="icon-only"></ion-icon>
</ion-item-option>
</ion-item-options>
</ion-item-sliding>
</ion-list>
There are actually multiple components that make up the functionality of ion-item-sliding
. We have the ion-item-sliding
component that contains everything, and then inside of that we define our ion-item
and the ion-item-options
which will be the action buttons hidden underneath the item. To make things easier for us later we have also already added the output for edit
as well as delete
.
Now in the parent HomeComponent
we need to respond to the delete
event.
Modify the
app-checklist-list
component in theHomeComponent
template to reflect the following:
<app-checklist-list
*ngIf="checklists$ | async as checklists"
[checklists]="checklists"
(delete)="deleteChecklist($event)"
></app-checklist-list>
Add the following method to the
HomeComponent
class:
deleteChecklist(id: string) {
this.checklistService.remove(id);
}
You should now be able to delete a checklist by sliding the checklist to the left, and choosing the delete button!
User Interface for Deleting Checklist Items
Now we are going to do pretty much the same thing for deleting checklist items.
Add the following outputs to the
ChecklistItemList
component:
@Output() delete = new EventEmitter<string>();
@Output() edit = new EventEmitter<ChecklistItem>();
Modify the
ChecklistItemList
template to reflect the following:
<ion-list lines="none">
<ion-item-sliding
side="end"
*ngFor="let item of checklistItems; trackBy: trackByFn"
>
<ion-item (click)="toggle.emit(item.id)" color="success">
<ion-label>{{ item.title }}</ion-label>
<ion-checkbox
color="light"
slot="end"
[checked]="item.checked"
></ion-checkbox>
</ion-item>
<ion-item-options>
<ion-item-option color="light" (click)="edit.emit(item)">
<ion-icon name="pencil-outline" slot="icon-only"></ion-icon>
</ion-item-option>
<ion-item-option color="danger" (click)="delete.emit(item.id)">
<ion-icon name="trash" slot="icon-only"></ion-icon>
</ion-item-option>
</ion-item-options>
</ion-item-sliding>
</ion-list>