Step 8 - Loading Todos on the Client
With the server set up we can now access the service from our Angular application. Note that there are a number of ways this can be done and for the sake of simplicity I start by putting the HTTP access code directly into the component. In another step we'll refactor the application and factor the service access code into a separate service class. For now, let's keep it simple.
We'll start in the component and add the loadTodos()
method:
loadTodos() {
this.http.get("http://localhost/todo/todos.td")
.subscribe( (response) => {
this.todos = response.json();
}, (response) => {
this.showError("failed to get todo items");
});
}
Additionally we need to satisfy the this.http
reference - there's no http
property defined on the component yet. We can use Angular's dependency injection to request the Http
service and create a property on the object at the same time.
Note that the call to .subscribe()
results in an asynchronous operation and the code following .subscribe()
runs immediately following the call. If your code depends on the results to perform further work, that code has to be moved into the success handler of the subscribe like the this.todos
assigningment above.
Dependency Injection
To do this we have to import the Http service:
import { Http } from '@angular/http';
and then inject it into our constructor like this:
constructor(private http:Http) { }
This says to Angular: Hand me an object of type Http
and store it into a property called http
. The property mappnig is specified by the visibility indicator of private
(you can also use public
). If you don't specify any visibility, no property is created and you can use the value directly. The above syntax is shortcut for:
// property
http:Http = null;
constructor(http:Http) {
this.http = http;
}
Http and RxJs
Angular uses Reactive extensions for all asynchronous operations like Http calls, DOM events and custom events that are published. RxJs works using Observables which publish events that a client can subscribe to. The HTTP service exposes an Observable event source which handles the Http callback when it completes or fails, and your code subscribes to these events (one each really in this case).
Your user code using the Http object does this:
this.http.get("http://localhost/todo/todos.td")
.subscribe( (response) => {
this.todos = response.json();
}, (response) => {
this.showError("failed to get todo items");
});
Observables have many useful features that allow for transformation and filtering of event messages, but for now we only want to subscribe to the results from our HTTP request. The raw Http generated Observable is of type Observable<response>
which means it returns a response object, that contains a .json()
method that can turn the response to an object, a ._body
property that contains the raw body and various HTTP related values like the status code and Http response message which is useful for error parsing.
For now we just capture the output and use response.json()
to turn the returned response into a value. In the Todos call we get back a JSON array of todo items so we can simply do:
(response) => {
this.todos = response.json();
}
And that's literally it! The value is set and the list is updated immediately with the new list of todo items.
If you want to load the list of todos on startup add the following to ngOnInit()
:
ngOnInit() {
this.loadTodos();
}
Displaying Errors
If an error occurs the second function is invoked and in our case I call a showError() function with a message. To make this work I added some HTML to the template to display a Bootstrap error message at the top of the page:
<div class="alert alert-warning" *ngIf="message">
<i class="fa fa-exclamation-triangle"></i>
{{message}}
</div>
The component then gets a new message
property that when set will display the value in the error box.
message = ""
The .showError()
method then displays the error by simply setting the property:
showError(msg){
this.message = msg;
}
Now I can call this.showError(msg)
anywhere in the component and get consistent error display.
Adding a new Todo
Now that we have all the plumbing out of the way it's easy to add additional Http calls. Let's add the add operation to send a new Todo to the server:
addTodo(todo,form) {
this.http.put("http://localhost/todo/todo.td", todo)
.subscribe((response)=> {
this.todos.splice(0, 0, todo);
this.activeTodo = new TodoItem();
form.reset();
},
(response)=> {
this.showError("Adding of todo failed.");
});
}
Here I'm using the HTTP verb of PUT
to push the todo object to the server. The object is passed as a value and encoded into json and sent to the server where it is added to the database. The server call doesn't return any data, so the call doesn't error due to a failure the update worked. If an HTTP error occurs the error handler kicks in and writes the message to the error box.
Deleting a Todo
The delete operation is nearly identical in how it works. Now we use the .delete
method from the Http object and pass the title of the todo to delete.
removeTodo(todo:TodoItem) {
this.http.delete("http://localhost/todo/todo.td?title=" + todo.title)
.subscribe(response => {
this.todos = this.todos.filter((t, i) => t.title != todo.title)
},
response=> {
this.showError("Failed to delete " + todo.title);
});
}
Note that in both the add and delete operations I've moved the code to update the this.todos
array into the callback. This is because these callbacks are asynchronous and we want to modify the todos only if the add or delete operation worked. Otherwise things should be left alone.
Ok we have all of our Todo functionality dialed. Now let's clean things up a bit.
Complete Component Code
import { Component, OnInit } from '@angular/core';
import { Http } from '@angular/http';
@Component({
//moduleId: module.id,
selector: 'todo',
styleUrls: ['./todo.css'],
templateUrl: './todo.html'
})
export class Todo implements OnInit {
constructor(private http:Http) { }
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();
formVisible = true;
message = "";
ngOnInit() {
this.loadTodos();
}
toggleCompleted(todo:TodoItem) {
todo.completed = !todo.completed;
}
removeTodo(todo:TodoItem) {
this.http.delete("http://localhost/todo/todo.td?title=" + todo.title)
.subscribe(response => {
this.todos = this.todos.filter((t, i) => t.title != todo.title)
},
response=> {
this.showError("Failed to delete " + todo.title);
});
}
addTodo(todo,form) {
this.http.put("http://localhost/todo/todo.td", todo)
.subscribe((response)=> {
this.todos.splice(0, 0, todo);
this.activeTodo = new TodoItem();
form.reset();
},
(response)=> {
this.showError("Adding of todo failed.");
});
}
loadTodos() {
this.http.get("http://localhost/todo/todos.td")
.subscribe( (response) => {
this.todos = response.json();
}, (response) => {
this.showError("failed to get todo items");
});
}
showError(msg){
this.message = msg;
}
}
export class TodoItem {
title:string = null;
description:string = null;
completed:boolean = false;
}
© West Wind Technologies, 1996-2024 • Updated: 09/19/2016
Comment or report problem with topic