By Md. Sabuj Sarker | 4/13/2018 | General |Beginners

Angular 5 To-Do List App - Part 10

Angular 5 To-Do List App - Part 10

In the previous article we learned how to highlight the filter match in a task's title. Previous to that we learned how to implement various features like canceling, un-canceling, editing, deleting, and clearing tasks. We also fixed various bugs that popped up during development. We tried hard to make sure our code didn’t go wild. Now, we have a working to-do application. But, we cannot call it full featured yet. Our application data is lost when we restart it—it is not saved anywhere. But, we will not work with that feature now as we have some Angular fundamentals to cover first. We need some more features that require more fundamental knowledge of Angular. You can also re-implement some features in better ways by learning Angular more after I cover more Angular fundamentals in future articles. So, we’ll keep those things for later and we will focus on improving our application with the help of the knowledge we already have.

The code of the previous article can be found in 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-9 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-10.

Making things work is not our ultimate goal. Our ultimate goal should be to make things work in the right way. In this article we will learn about fixing some lingering issues while keeping our application functional.

Issue of Data Duplication

We have two pieces of task arrays now, it might not be a problem when we have small number of tasks, but it may cause both performance and memory issues if our task list starts to grow larger. We need some way to unify both arrays, and to stop data duplication. But, wait, think for a moment: can you really prevent the duplication of the data? For example we need to keep an original title of tasks and make highlighted titles. So, we will need to keep the original title intact as the filter text will change from time to , and thus the we will create and save the highlighted title.

We will not save the highlighted title. We will create a Task class instead of the interface and use a getter called display_title that will return the highlighted title without duplicating any data. Removal of the duplication of tasks arrays will help us stay focused and will help us remove redundant code.

Issue of Task Number Tracking

Now, you may ask, what will happen to the f_idx? How will we keep track of task numbering? That was a big concern for us and we cannot lose the task numbering again. We can completely remove f_idx and depend on an index provided by *ngFor. But wait, we stopped using the index provided by *ngFor and if we reintroduce it how on earth will we be able to find a way to keep track of task numbering? To solve this problem we will introduce another boolean property of Task called displayable. We will only display those tasks whose displayable property is set to true. Problem solved!

Cluttered Template

Take a look a the following template code.

<td *ngIf="task.is_canceled; else elseTd" [innerHTML]="'<s>' + (task.f_idx + 1) + '.' + task.title + '</s>'">
</td>
<ng-template #elseTd>
   <td [innerHTML]="(task.f_idx + 1) + '.' + task.title">

   </td>
</ng-template>

Or look at this single line of code:

'<s>' + (task.f_idx + 1) + '.' + task.title + '</s>'

It’s doing many things. It is adding 1 to the index, adding a dot, then adding the title, and then it is adding an s tag around that result. Templates are not supposed to do a lot of things—templates should not do much logical or concatenation jobs. This should be done inside of the JavaScript (in our case TypeScript) code as much as possible.

Solving the Problems

To solve our problems we need to first modify our Task interface. But, this time we are not keeping it as an interface anymore—we are going to define a class in place of  it so that we can use getter/setter. Remove the Task interface and replace it with a class with the same name. The complete code of the class is as follows:

class Task{
 title: string;
 is_canceled: boolean = false;
 displayable: boolean = true;

 constructor(title: string, private component){
   this.title = title;
 }

 get display_title(){
   let filter_by = this.component.filter_by;
   var highlighted_title = this.title;

   if (filter_by !== ""){
     let title = this.title;
     let title_lower = title.toLowerCase();
     if (title_lower.includes(filter_by)){
       let filter_len = filter_by.length;
       let title_len = title.length;
 
       let start_idx = title_lower.indexOf(filter_by);
       let end_idx = start_idx + filter_len;
 
       let start = title.substr(0,  start_idx );
       let middle = title.substr(start_idx, filter_len);
       let end = title.substr(end_idx, title_len);
       highlighted_title = start + "<b>" + middle + "</b>" + end;
     }
   }

   if (this.is_canceled){
     highlighted_title = "<s>" + highlighted_title + "</s>"
   }

   return highlighted_title;
 }

 swapCancel(){
   if (this.is_canceled){
     this.is_canceled = false;
   }else{
     this.is_canceled = true;
   }
 }

 setDisplay(){
   // handles the logic whether the task should be displayed or not.
   let filter_by = this.component.filter_by;
   if (filter_by !== ""){
     let title = this.title;
     let title_lower = title.toLowerCase();
     if (title_lower.includes(filter_by)){
       this.displayable = true;
     }else{
       this.displayable = false;
     }
   }else{
     this.displayable = true;
   }
 }
}

display_title() is a getter that will return the tile by adding markup to it for highlighting and striking when necessary. setDisplay() will be responsible for properly setting the displayable property so that only the filtered tasks are shown. swapCancel is a convenient method for swapping the cancel status of a task. Through the constructor method we will pass the task title and a reference to the component class object so that Task objects can use the filter_by field from the component when necessary.

In the component class we will remove the filtered_tasks property and will also update methods when they require it. We will entirely remove the filterTasks() method and introduce another method named setTaskDisplays() that will be responsible for calling the setDisplay() method on every task object so the Task object can decide and set the displayable property of the tasks. We will call this method whenever we need to update the displayable state of the tasks, for example, in the editTask(), addFilter(), clearFilter() methods. We will not need to call it inside the deleteTask() and cancelTask() methods. So, our component class will look like below:

class AppComponent {
 tasks: Array<Task> = [
   new Task("Go home", this),
   new Task("Take a nap", this),
   new Task("Start learning Angular with Sabuj", this)
 ];

 filter_by: string = "";
 
 clearToDo(){
   let do_delete = confirm("Are you sure to delete all tasks?");
   if (do_delete){
     this.tasks.splice(0);
   }
 }

 addTask(input){
   let value = input.value;
   input.value = "";
   this.tasks.push(
     new Task(value, this)
   );
 }

 cancelTask(idx: number){
   this.tasks[idx].swapCancel();
 }

 deleteTask(idx: number){
   let do_delete = confirm("Are you sure to delete the task?");
   if (do_delete){
     this.tasks.splice(idx, 1);
   }
 }

 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.setTaskDisplays();
   }
 }

 private setTaskDisplays(){
   for(let task of this.tasks){
     task.setDisplay();
   }
 }

 addFilter(filter_input){
   let filter_by: string = filter_input.value;
   filter_by = filter_by.toLowerCase();
   this.filter_by = filter_by;
   this.setTaskDisplays();
 }

 clearFilter(filterInput){
   filterInput.value = "";
   this.filter_by = "";
   this.setTaskDisplays();
 }
}

Go to the browser to check if everything is working according to our expectation. It is working perfectly for me.

Conclusion

I hope you already have got the feeling that having the work done is not the final destination. A programmer always should continue to seek better ways of coding so that the application scales properly while limiting the possibility of introducing bugs in the future. Now, our data is stored only in one place and we can manage it better than when it is stored in multiple places. I wanted to teach you in the natural way. If I developed the application in this way previously you could not learn to think in different ways and could not develop the skill of solving problems properly. Also, I taught you small parts first and then moved to bigger and more complex parts. You will be lost in big projects if you do not get skilled in solving problems in different ways (dividing them in smaller parts) and try finding the best way. Keep practicing everyday to master Angular. If you have any questions, create an issue on the github repository Learn Angular with Sabuj and I will try my best to help you.

 

Previous article: Angular 5 to do list app - Part 9

Check back soon for more articles

 

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.

By Md. Sabuj Sarker | 4/13/2018 | General

{{CommentsModel.TotalCount}} Comments

Your Comment

{{CommentsModel.Message}}

Recent Stories

Top DiscoverSDK Experts

User photo
3355
Ashton Torrence
Web and Windows developer
GUI | Web and 11 more
View Profile
User photo
3220
Mendy Bennett
Experienced with Ad network & Ad servers.
Mobile | Ad Networks and 1 more
View Profile
User photo
3060
Karen Fitzgerald
7 years in Cross-Platform development.
Mobile | Cross Platform Frameworks
View Profile
Show All
X

Compare Products

Select up to three two products to compare by clicking on the compare icon () of each product.

{{compareToolModel.Error}}

Now comparing:

{{product.ProductName | createSubstring:25}} X
Compare Now