MEAN Stack : Node ToDo List App with MongoDB II (more Angular)
In this article, we'll make another ToDo list.
mongodb-expressjs-angularjs-nodejs-ToDo.git
We can either pass an object with $http.verb ($http.get, $http.post).
<!DOCTYPE html> <html ng-app="app"> <head> <title><%= title %></title> <link rel='stylesheet' href='/stylesheets/style.css' /> </head> <body> <h1><%= title %></h1> <ng-view></ng-view> <!-- Libraries --> <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script> <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular-route.min.js"></script> <!-- Template --> <script type="text/ng-template" id="/todos.html"> Search: <input type="text" ng-model="search.name"> <ul> <li ng-repeat="todo in todos | filter: search"> <input type="checkbox" ng-model="todo.completed"> <a href="#/{{$index}}">{{todo.name}}</a> </li> </ul> </script> <script type="text/ng-template" id="/todoDetails.html"> <h1>{{ todo.name }}</h1> completed: <input type="checkbox" ng-model="todo.completed"> note: <textarea>{{ todo.note }}</textarea> </script> <script> angular.module('app', ['ngRoute']) //--------------- // Services //--------------- .factory('Todos', ['$http', function($http){ return $http.get('/todos'); }]) //--------------- // Controllers //--------------- .controller('TodoController', ['$scope', 'Todos', function ($scope, Todos) { Todos.success(function(data){ $scope.todos = data; }).error(function(data, status){ console.log(data, status); $scope.todos = []; }); }]) .controller('TodoDetailCtrl', ['$scope', '$routeParams', 'Todos', function ($scope, $routeParams, Todos) { $scope.todo = Todos[$routeParams.id]; }]) //--------------- // Routes //--------------- .config(['$routeProvider', function ($routeProvider) { $routeProvider .when('/', { templateUrl: '/todos.html', controller: 'TodoController' }) .when('/:id', { templateUrl: '/todoDetails.html', controller: 'TodoDetailCtrl' }); }]); </script> </body> </html>
$http returns a promise which has a success and error function.
We used a .factory and had a fixed array. But now, we need to change it to communicate with the REST API ($http.get('/todos')) that we just build.
Since we still have data in MongoDB, we can see them listed in the root page:
Calling a remote server is an asynchronous operation with no guarantee of when a response will be returned. How do we sequence code when we don't know precisely when things are going to happen? It would be nice if we could make our call and move on with our life until the call returned, and even better if the call would let us know when it was back.
Promises are just the tool for the job!
Imagine we're in a restaurant where we walk up to the host and make a request for a table for us and all our friends. The host takes our name and gives us a buzzer to hold. When a table is ready, the buzzer will go off and we'll know that we're ready to be seated. We're not sitting near the door wringing our hands wondering if we're going to get seated, because we have the buzzer and it's definitely going to go off sometime in the future with the promise of a table.
And here we are! A promise is much like that buzzer at the restaurant. When we make a remote server call, a promise object is returned and patiently sits there until it's resolved and then performs whatever logic we desire.
It's an object with a promise that it will be dealt with sometime in the future.
-from AngularJS in Action
Angular's $resource is a factory service that lets us interact with RESTful backends easily. It is built on the top of the $http service.
To use $resource inside our controller/service, we need to declare a dependency on $resource:
// add ngResource dependency angular.module('app', ['ngRoute', 'ngResource']) .factory('Todos', ['$resource', function($resource){ return $resource('/todos/:id', null, { 'update': { method:'PUT' } }); }]) .controller('TodoController', ['$scope', 'Todos', function ($scope, Todos) { $scope.todos = Todos.query(); }])
Note that $resource does not return a promise like $http but an empty reference instead. Angular will render an empty $scope.todos. However, when Todos.query() gets the data from the server it will re-render the UI automatically. So, we still can see the data.
Also, since $resource service doesn't come bundled with the main Angular script, we need to include it in our HTML page:
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular-resource.min.js"></script>
The rest works are straight forward, and mostly related to REST API related:
- A new text box, a button to send a POST request to server.
- Update (in-line editing)
- Delete
Here is the final version of views/index.ejs file:
<!DOCTYPE html> <html ng-app="app"> <head> <title><%= title %></title> <link rel='stylesheet' href='/stylesheets/style.css' /> </head> <body> <h1><%= title %></h1> <ng-view></ng-view> <!-- Libraries --> <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script> <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular-route.min.js"></script> <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular-resource.min.js"></script> <!-- Template --> <script type="text/ng-template" id="/todos.html"> Search: <input type="text" ng-model="search.name"> <ul> <li ng-repeat="todo in todos | filter: search"> <input type="checkbox" ng-model="todo.completed" ng-change="update($index)"> <a ng-show="!editing[$index]" href="#/{{todo._id}}">{{todo.name}}</a> <button ng-show="!editing[$index]" ng-click="edit($index)">edit</button> <button ng-show="!editing[$index]" ng-click="remove($index)">remove</button> <input ng-show="editing[$index]" type="text" ng-model="todo.name"> <button ng-show="editing[$index]" ng-click="update($index)">update</button> <button ng-show="editing[$index]" ng-click="cancel($index)">cancel</button> </li> </ul> New task <input type="text" ng-model="newTodo"><button ng-click="save()">Create</button> </script> <script type="text/ng-template" id="/todoDetails.html"> <h1>{{ todo.name }}</h1> completed: <input type="checkbox" ng-model="todo.completed"><br> note: <textarea ng-model="todo.note"></textarea><br><br> <button ng-click="update()">update</button> <button ng-click="remove()">remove</button> <a href="/">Cancel</a> </script> <script> angular.module('app', ['ngRoute', 'ngResource']) //--------------- // Services //--------------- .factory('Todos', ['$resource', function($resource){ return $resource('/todos/:id', null, { 'update': { method:'PUT' } }); }]) //--------------- // Controllers //--------------- .controller('TodoController', ['$scope', 'Todos', function ($scope, Todos) { $scope.editing = []; $scope.todos = Todos.query(); $scope.save = function(){ if(!$scope.newTodo || $scope.newTodo.length < 1) return; var todo = new Todos({ name: $scope.newTodo, completed: false }); todo.$save(function(){ $scope.todos.push(todo); $scope.newTodo = ''; // clear textbox }); } $scope.update = function(index){ var todo = $scope.todos[index]; Todos.update({id: todo._id}, todo); $scope.editing[index] = false; } $scope.edit = function(index){ $scope.editing[index] = angular.copy($scope.todos[index]); } $scope.cancel = function(index){ $scope.todos[index] = angular.copy($scope.editing[index]); $scope.editing[index] = false; } $scope.remove = function(index){ var todo = $scope.todos[index]; Todos.remove({id: todo._id}, function(){ $scope.todos.splice(index, 1); }); } }]) .controller('TodoDetailCtrl', ['$scope', '$routeParams', 'Todos', '$location', function ($scope, $routeParams, Todos, $location) { $scope.todo = Todos.get({id: $routeParams.id }); $scope.update = function(){ Todos.update({id: $scope.todo._id}, $scope.todo, function(){ $location.url('/'); }); } $scope.remove = function(){ Todos.remove({id: $scope.todo._id}, function(){ $location.url('/'); }); } $scope.remove = function(){ Todos.remove({id: $scope.todo._id}, function(){ $location.url('/'); }); } }]) //--------------- // Routes //--------------- .config(['$routeProvider', function ($routeProvider) { $routeProvider .when('/', { templateUrl: '/todos.html', controller: 'TodoController' }) .when('/:id', { templateUrl: '/todoDetails.html', controller: 'TodoDetailCtrl' }); }]); </script> </body> </html>
If we click "edit" button, we get other buttons - "update" and "cancel":
When we click the link of an item, we get another page:
Source is available from mongodb-expressjs-angularjs-nodejs-ToDo.
Node.JS
Ph.D. / Golden Gate Ave, San Francisco / Seoul National Univ / Carnegie Mellon / UC Berkeley / DevOps / Deep Learning / Visualization