Skip to content

Toggling Item State

We have the ability to add items, but now we need to extend their functionality to allow for indicating that a particular item has been completed. We also want the ability to “reset” the completion state of a particular checklist.

Add the ability to toggle to the service

We will start with allowing a particular item in a checklist to be toggled between checked/unchecked.

The approach here will be the same as any other state change we would approach. Generally, we want to:

  1. Add a source for the action that will be nexted
  2. Subscribe to that source
  3. Have the reducer update the state signal

The reducer for this one is actually a bit tricky, but go ahead and see how much of this you can implement on your own in the ChecklistItemService.

toggle$ = new Subject<RemoveChecklistItem>();

It might strike you as a bit odd that the type is RemoveChecklistItem that it just because the toggle needs the same information — just the id of the item being toggled.

this.toggle$.pipe(takeUntilDestroyed()).subscribe((checklistItemId) =>
this.state.update((state) => ({
...state,
checklistItems: state.checklistItems.map((item) =>
item.id === checklistItemId
? { ...item, checked: !item.checked }
: item
),
}))
);

There is nothing new going on here — although the reducer step looks a little complex, the basic idea is that to overwrite the old checklistItems it maps through all of them and if it finds one matching the given checklistItemId it will toggle its checked value.

Add a checkbox to the checklist item

Now that we have support for toggling an item in the service, we just need to interact with it.

toggle = output<RemoveChecklistItem>();
@for (item of checklistItems(); track item.id){
<li>
<div>
@if (item.checked){
<span></span>
}
{{ item.title }}
</div>
<div>
<button (click)="toggle.emit(item.id)">Toggle</button>
</div>
</li>
} @empty {
<div>
<h2>Add an item</h2>
<p>Click the add button to add your first item to this quicklist</p>
</div>
}

Trigger the toggle

The last thing we need to do to get our toggle working is to react to that toggle event in our ChecklistComponent and trigger the next of the toggle$ source.

See if you can implement that.

<app-checklist-item-list
[checklistItems]="items()"
(toggle)="checklistItemService.toggle$.next($event)"
/>

Now we can see our toggle working!

Add reset functionality

Now we want to add the ability to change the checked state of all items for a particular checklist back to false.

Once again, this is going to follow almost the exact same process as the toggle$ source. See if you can implement this in the ChecklistItemService.

reset$ = new Subject<RemoveChecklist>();
this.reset$.pipe(takeUntilDestroyed()).subscribe((checklistId) =>
this.state.update((state) => ({
...state,
checklistItems: state.checklistItems.map((item) =>
item.checklistId === checklistId ? { ...item, checked: false } : item
),
}))
);

Now we need to add a button in our ChecklistHeader component that will emit when a “reset” button is clicked.

resetChecklist = output<RemoveChecklist>();
<header>
<a routerLink="/home">Back</a>
<h1>
{{ checklist().title }}
</h1>
<div>
<button (click)="resetChecklist.emit(checklist().id)">Reset</button>
<button (click)="addItem.emit()">Add item</button>
</div>
</header>
@if (checklist(); as checklist){
<app-checklist-header
[checklist]="checklist"
(addItem)="checklistItemBeingEdited.set({})"
(resetChecklist)="checklistItemService.reset$.next($event)"
/>
}

If you test the application now you should see that you are able to click this “reset” button to clear any items that are marked as complete! In the next lesson, we are going to handle making sure these state values stick around even after we refresh the application.