Step 5 - Create the ToDo List Display
Let's start with the list of Todos to display.
We already have data in our Todo component from the last step where we created the static Javascript array of Todo items. Let's bind those to a list on the page.
Add the following below the header:
<div *ngFor="let todo of todos"
class="todo-item">
<div class="pull-left">
<i class="fa fa-bookmark "
style="cursor: pointer">
</i>
</div>
<div class="pull-right">
<i class="fa fa-remove"
style="color: darkred; cursor: pointer"></i>
</div>
<div class="todo-content">
<div class="todo-header">
{{ todo.title}}
</div>
<div>
{{todo.description}}
</div>
</div>
</div>
Create a related Stylesheet
This code relies on some CSS styling for various elements. I can add the CSS directly into this page, or I can create an external style sheet which is preferrable.
To do the latter let's create a new file todo.css
and add:
.row li {
margin-left: -10px;
}
#form1 input.ng-invalid.ng-touched, #form1 textarea.ng-invalid.ng-touched {
background-color: #ffced1;
}
#form1 input.ng-valid.ng-touched, #form1 textarea.ng-valid.ng-touched {
background-color: #baffc3;
}
.todo-item {
padding: 10px;
margin-left: 10px;
border-bottom: 1px solid silver;
transition: opacity 900ms ease-out;
}
.todo-header {
font-weight: bold;
font-size: 1.2em;
color: #457196;
}
.todo-content {
padding-left: 40px;
}
.todo-content .fa-check {
color: green !important;
font-weight: bold;
font-size: 1.2em;
}
.completed {
text-decoration: line-through;
font-style: italic;
opacity: 0.4;
}
and hook up the css with:
styleUrls: ['./todo.css'],
in the component meta data. This pulls in the stylesheet into the component and localizes the styles to this component.
Now lets go back to our page and we should see:
Make it more dynamic
Now that we have the list it might be nice to be able to mark ToDo Items as completed. Let's do this by adding a checkbox to the display.
Add the checkbox next to the remove button:
<div class="pull-right">
<input type="checkbox" title="check to complete todo"
[(ngModel)]="todo.completed" />
<i class="fa fa-remove" style="color: darkred; cursor: pointer"></i>
</div>
The key item here is the [(ngModel)]
setting which two-way binds the completed
property of the TodoItem to the checkbox. When rendered the TodoItem.completed
status determines whether the checkbox is checked.
If you run the page now you'll see:
Displaying Completed Status
Let's make it a bit more obvious when an item is completed. The CSS styles provided have a .completed
style that can be applied to change the font color and strike through the text when an item is complete. We can use the powerful [ngClass]
directive to conditionally apply this class to individual elements.
There are two places I'll apply this custom styling:
- for the entire item
- for the icon
Let's start with the item which we can define on the looping <div>
tag that contains the *ngFor
.
<div *ngFor="let todo of todos"
class="todo-item"
[ngClass]="{completed: todo.completed}">
This says: When todo.completed
is true
apply the completed
style to the class
attribute - so the new class setting will be class="todo-item completed"
.
Next I'll tweak the icon display so it displays a tag icon when open, and a check mark when it's completed:
<i class="fa"
[ngClass]="{'fa-bookmark-o': !todo.completed,'fa-check': todo.completed }"
style="cursor: pointer">
</i>
The syntax uses a map-like expression that specifies the name of the class as a property name, and the truthy expression as a value. Here if not completed we show the fa-bookmark-o
style, if completed we show the checkmark.
With the style updates in place we now get:
Capturing Checkbox Status
If you click on the checkbox you'll notice that it just works - when you click the status toggles between completed and uncompleted.
But wait? Don't we have to capture the click or change event or something? No - this is what's cool about two way databinding: the bound value (todo.completed
) changes simply because it is bound to the checkbox. When you toggle the checkbox, the todo item's value changes and the template immediately reflects that change!
There's no code to write to make this work!
Getting rid of the Checkbox
Ok now that you've seen how cool the checkbox binding is - let's get rid of it, because lets face it's ugly.
It would be much nicer to just click on the icon to toggle the state. To do this lets add a method to our component class:
toggleCompleted(todo:TodoItem) {
todo.completed = !todo.completed;
}
Then lets handle the click on the icon like this in the template:
<div class="pull-left"
(click)="toggleCompleted(todo)">
<i class="fa "
[ngClass]="{'fa-bookmark-o': !todo.completed,'fa-check': todo.completed }"
style="cursor: pointer">
</i>
</div>
The key here is the (click)="toggleCompleted(todo)
. The (click)
is an event binding around the click event. You can use parenthesis to bind any event of an element. Notice that I pass the todo
item as a parameter to the function called, which allows passung the appropriate context to the method so it knows which todo item to deal with in the list.
Again - it's very easy to do.
Removing items
We can use the same approach for the remove button.
We'll use this HTML:
<i class="fa fa-remove"
(click)="removeTodo(todo)"
style="color: darkred; cursor: pointer"></i>
and handle it like this:
removeTodo(todo:TodoItem){
this.todos = this.todos.filter((t) => t.title != todo.title)
}
The Array.filter()
function takes a delegate function that returns true or false on whether to return the item in question. So we look for any todo items that don't match the title and return the array. The effect is that we get a new array with the todo item removed.
If you run the sample again and click on the Remove button the item should go away.
Arrow Functions () =>
The ()=>
syntax use ES6/Typescript arrow functions - the part in the parenthesis are the parameter follow by a fat arrow and in this case an expression that returns a value (true or false). You can also write expressions as code blocks. The following does the same thing and is required if the code isn't a simple expression:
this.todos = this.todos.filter((t) => {
return t.title != todo.title;
});
Arrow functions are alternate function syntax. Traditionally you would write the above like:
this.todos = this.todos.filter(function(t) {
return t.title != todo.title;
});
Here the effect is the same. However, arrow function are subtly different in the way they deal with the notorious this
pointer. Arrow functions preserve the scope of this
from the parent context calling it so that you can usually use this
the way you would expect it to with Arrow functions, while with classic anonymous functions the this
context is unpredictable as the calling context determines its value. Suffice it to say, by convention Typescript and Angular prefer using Arrow functions.
Code so far
Here's the code for the component:
import { Component, OnInit } from '@angular/core';
@Component({
//moduleId: module.id,
selector: 'todo',
styleUrls: ['./todo.css'],
templateUrl: './todo.html'
})
export class Todo implements OnInit {
todos:TodoItem[] = [
{
title: "SWFox Angular Presentation",
description: "Try to show up on time this time",
completed: false
},
{
title: "Do FoxPro presentation",
description: "Should go good, let's hope I don't fail...",
completed: false
},
{
title: "Do raffle at the end of day one",
description: "Should go good, let's hope I don't fail...",
completed: false
},
{
title: "Arrive at conference",
description: "Arrive in Phoenix and catch a cab to hotel",
completed: true
}
];
activeTodo:TodoItem = new TodoItem();
constructor() { }
ngOnInit() {
}
toggleCompleted(todo:TodoItem) {
todo.completed = !todo.completed;
}
removeTodo(todo:TodoItem){
this.todos = this.todos.filter((t,i) => t.title != todo.title)
}
}
export class TodoItem {
title:string = null;
description:string = null;
completed:boolean = false;
}
Here's the HTML:
<div class="page-header-text">
<i class="fa fa-tag"></i>
ToDo List
</div>
<div class="todo-content">
<div *ngFor="let todo of todos"
class="todo-item" [ngClass]="{completed: todo.completed}">
<div class="pull-left" (click)="toggleCompleted(todo)">
<i class="fa "
[ngClass]="{'fa-bookmark-o': !todo.completed,'fa-check': todo.completed }"
style="cursor: pointer">
</i>
</div>
<div class="pull-right">
<!--<input type="checkbox"-->
<!--title="check to complete todo"-->
<!--[(ngModel)]="todo.completed" />-->
<i class="fa fa-remove"
(click)="removeTodo(todo)"
style="color: darkred; cursor: pointer"></i>
</div>
<div class="todo-content">
<div class="todo-header">
{{ todo.title}}
</div>
<div>
{{todo.description}}
</div>
</div>
</div>
</div>
And the CSS:
.row li {
margin-left: -10px;
}
#form1 input.ng-invalid.ng-touched, #form1 textarea.ng-invalid.ng-touched {
background-color: #ffced1;
}
#form1 input.ng-valid.ng-touched, #form1 textarea.ng-valid.ng-touched {
background-color: #baffc3;
}
.todo-item {
padding: 10px;
margin-left: 10px;
border-bottom: 1px solid silver;
transition: opacity 900ms ease-out;
}
.todo-header {
font-weight: bold;
font-size: 1.2em;
color: #457196;
}
.todo-content {
padding-left: 40px;
}
.todo-content .fa-check {
color: green !important;
font-weight: bold;
font-size: 1.2em;
}
.completed {
text-decoration: line-through;
font-style: italic;
opacity: 0.4;
}
© West Wind Technologies, 1996-2024 • Updated: 09/19/2016
Comment or report problem with topic