Angular 5 To-Do List App - Part 8
In the previous article we solved the weird problem of updating the wrong task and the task numbering issue. In solving these we were not able to focus on other issues we have, or the other features we need. In this article we will focus on these things as much as we can. The code of the previous article can be found the article itself, and the complete project can be found in the Github repository Learn Angular With Sabuj. Browse to the branch named todo-app-part-7 to get the complete project code of the previous article. To get the complete project of the current article browse the branch named todo-app-part-8.
Case Insensitive Filtering
Start up the Angular development server, go to the browser, and search with go instead of Go; you will see no result on your to-do list application table. This happens because our application filter is case sensitive. We need to make it case insensitive. During filtering we can convert the filter_by and the title of the task to lowercase. But, I will convert the filter_by to lowercase when it is added to the component. So, addFielter() will look like below:
addFilter(filter_input){
let filter_by: string = filter_input.value;
filter_by = filter_by.toLowerCase();
this.filter_by = filter_by;
this.filterTasks();
}
We will also need to modify the filterTasks() method to make it behave like we want.
filterTasks(){
let filtered_tasks: Array<Task> = [];
for(let idx=0; idx < this.tasks.length; idx++){
let task = this.tasks[idx];
if (task.title.toLowerCase().includes(this.filter_by)){
filtered_tasks.push(
<Task>{
title: task.title,
is_canceled: task.is_canceled,
f_idx: idx
}
);
}
}
this.filtered_tasks = filtered_tasks;
}
The difference is only in the line if (task.title.toLowerCase().includes(this.filter_by)) ... —it converts the title to lowercase and checks if it contains filter_by.
Go to the browser to check if things are acting the way we want.
Clearing Filter the Proper Way
When we want to perform filtering with a different text or when want to see the full list of tasks we need to repeatedly hit the backspace and then hit filter to do that. If we had a Clear button beside the Filter button it would be very convenient. Again, having that button we will not need to click the filter button to make the changes happen on the display. Let's create the template markup first.
We want to add the Clear button just after the Filter button.
<button class="btn btn-info">Clear</button>
The button will have a click handler that we will create later inside the component class by name clearFilter()—it will accept the filterInput reference as the argument.
<button class="btn btn-info" (click)="clearFilter(filterInput)">Clear</button>
The final template markup for the filtering row should look like below:
<div class="row">
<div class="col-xs-8">
<input type="text" class="form-control" name="filter" placeholder="Filter Keyword(s)" #filterInput>
</div>
<div class="col-xs-4">
<button class="btn btn-info" (click)="addFilter(filterInput)">Filter</button>
<button class="btn btn-info" (click)="clearFilter(filterInput)">Clear</button>
</div>
</div>
Now we need to implement the clearFilter() inside the component class. This method will also be responsible for calling filterTasks() to update the UI properly.
clearFilter(filterInput){
filterInput.value = "";
this.filter_by = "";
this.filterTasks();
}
Go to the browser to check everything is working perfectly and is following our expectations.
The final code of our template is as follows:
<div class="container">
<div class="row">
<div class="col-xs-12">
<h1> To-Do Application </h1>
</div>
</div>
</div>
<div class="container">
<div class="row">
<div class="col-xs-8">
<input type="text" class="form-control" name="task" placeholder="Task" #taskInput>
</div>
<div class="col-xs-4">
<button class="btn btn-info" (click)="addTask(taskInput)">Add</button>
</div>
</div>
<hr>
<div class="row">
<div class="col-xs-8">
<input type="text" class="form-control" name="filter" placeholder="Filter Keyword(s)" #filterInput>
</div>
<div class="col-xs-4">
<button class="btn btn-info" (click)="addFilter(filterInput)">Filter</button>
<button class="btn btn-info" (click)="clearFilter(filterInput)">Clear</button>
</div>
</div>
<div class="row">
<div class="col-xs-12">
<table class="table table-striped">
<thead>
<tr>
<th>To-Do list</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let task of filtered_tasks;">
<td *ngIf="task.is_canceled; else elseTd">
<s>{{ task.f_idx + 1 }}. {{task.title}}</s>
</td>
<ng-template #elseTd>
<td>{{ task.f_idx + 1 }}. {{task.title}}</td>
</ng-template>
<td>
<button class="btn btn-primary btn-xs" (click)="editTask(task.f_idx)"> Edit </button>
</td>
<td>
<button class="btn btn-warning btn-xs" (click)="cancelTask(task.f_idx)"> Cancel </button>
</td>
<td>
<button class="btn btn-danger btn-xs" (click)="deleteTask(task.f_idx)"> Delete </button>
</td>
</tr>
</tbody>
<tfoot>
<tr>
<button class="btn btn-danger" (click)="clearToDo()">Clear</button>
</tr>
</tfoot>
</table>
</div>
</div>
</div>
And the final code of app.component.ts is as follows:
import { Component, TemplateRef, Input, ElementRef } from '@angular/core';
interface Task{
title: string,
is_canceled: boolean,
f_idx: number
}
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
tasks: Array<Task> = [
{
title: "Go home",
is_canceled: false,
f_idx: null
},
{
title:"Take a nap",
is_canceled: false,
f_idx: null
},
{
title: "Start learning Angular with Sabuj",
is_canceled: false,
f_idx: null
}
];
filtered_tasks: Array<Task> = [];
filter_by: string = "";
constructor(){
this.filterTasks();
}
clearToDo(){
let do_delete = confirm("Are you sure to delete all tasks?");
if (do_delete){
this.tasks.splice(0);
}
this.filterTasks();
}
addTask(input){
let value = input.value;
input.value = "";
this.tasks.push(
{
title: value,
is_canceled: false,
f_idx: null
});
this.filterTasks();
}
cancelTask(idx: number){
if (this.tasks[idx].is_canceled){
this.tasks[idx].is_canceled = false;
}else{
this.tasks[idx].is_canceled = true;
}
this.filterTasks();
}
deleteTask(idx: number){
let do_delete = confirm("Are you sure to delete the task?");
if (do_delete){
this.tasks.splice(idx, 1);
this.filterTasks();
}
}
editTask(idx: number){
let title = this.tasks[idx].title;
let result = prompt("Edit Task Title", title);
if (result !== null && result !== ""){
this.tasks[idx].title = result;
this.filterTasks();
}
}
filterTasks(){
let filtered_tasks: Array<Task> = [];
for(let idx=0; idx < this.tasks.length; idx++){
let task = this.tasks[idx];
if (task.title.toLowerCase().includes(this.filter_by)){
filtered_tasks.push(
<Task>{
title: task.title,
is_canceled: task.is_canceled,
f_idx: idx
}
);
}
}
this.filtered_tasks = filtered_tasks;
}
addFilter(filter_input){
let filter_by: string = filter_input.value;
filter_by = filter_by.toLowerCase();
this.filter_by = filter_by;
this.filterTasks();
}
clearFilter(filterInput){
filterInput.value = "";
this.filter_by = "";
this.filterTasks();
}
}
Conclusion
We have entered the harder parts of development gradually. We introduced code and features that will help us improve and scale our application, but using them without caution could result in all types of bugs. I understand that some of this code may seem tiresome sometime,but the journey is not over yet. We still need to make some improvements to our application. Keep practicing to master Angular. If you have any question, create an issue on the Github repository Learn Angular with Sabuj and I will try my best to help you.
About the Author
My name is Md. Sabuj Sarker. I am a Software Engineer, Trainer and Writer. I have over 10 years of experience in software development, web design and development, training, writing and some other cool stuff including few years of experience in mobile application development. I am also an open source contributor. Visit my github repository with username SabujXi.
Recent Stories
Top DiscoverSDK Experts
Compare Products
Select up to three two products to compare by clicking on the compare icon () of each product.
{{compareToolModel.Error}}
{{CommentsModel.TotalCount}} Comments
Your Comment