Aim : We are going to be making a sexy sortable Todo List with Angular-js.
AngularJS is a toolset for building the framework most suited to your application development.
Lets start.You can follow the steps from the github repo for this tutorial.
DEMO
DOWNLOAD
Step 1 : Start with a boilerplate.
I always start a project with a boilerplate to provide a significant jumpstart for faster development .This boilerplate contains bootstrap , jquery and angular libraries ,comments about licenses on the top and script for Google Analytics.
https://github.com/kanakiyajay/angular-js-template
Step 2 : Create a basic Model and View
Lets think about the model to be used for a todo-list.It it a List so an array will be the best option .Each item in the array will have an taskName and a boolean Value whether that particular task is remaining or completed.The boolean Value will bind to a native angular form checkbox .
Here is the todos model :
$scope.todos = [
{ taskName : "Write an Angular js Tutorial for Todo-List" , isDone : false },
{ taskName : "Update jquer.in" , isDone : false },
{ taskName : "Create a brand-new Resume" , isDone : false }
];
Its corresponding html
<ul>;
<li ng-repeat="todo in todos">;<input ng-model="todo.isDone" type="checkbox">; {{todo.taskName}}</li>;
</ul>;
You should always a debug script to understand how the underlying model is changing.
<div class="debug">;
<p class="text-info">;{{ todos | json}}</p>;
</div>;
Now check and uncheck the checkboxes and you will see how the underlying debug changes.
We will also need a function to add a new Todo to the List.Its extremely easy to do that in Angular-js.
html :
<input type="text" ng-model="newTodo">;
<button class="btn btn-default" ng-click="addTodo()">;Add</button>;
js :
$scope.addTodo = function () {
$scope.todos.push({taskName : $scope.newTodo , isDone : false });
$scope.newTodo = "";//Reset the text field.
};
You should now have following screenshot

Following the principles of progressive enhancement , we also add some Css styles for better looking.
.todoName{
font-size: 20px;
border-bottom: 1px lightgray solid;
padding-bottom:5px;
}
#todoAdd {
margin-left: 40px;
}
#todoField{
padding-left: 5px;
font-size: 20px;
margin: 20px
}
.todoTask{
margin-bottom: 20px;
}

Browse the code on github
Step 3 : Add ui-sortable for sortable Lists
We will be using the awesome Sortable component from the Angular UI project.It is a directive that allows you to sort an array using drag and drop.
[Update: We have now included jQuery UI Touch Punch in order to support drag and drop on touch devices]
Note : We will be using the angular-js 1.2 branch
https://github.com/angular-ui/ui-sortable/tree/angular1.2
Add the necessary jquery ui and sortable js files , add ui.sortable as a dependency to the angular js app.
You can pass ui-sortable as a directive and give the options defined in app.js to variable name todoSortable.
<ul ui-sortable="todoSortable" ng-model="todos">;
$scope.todoSortable = {
containment : "parent",//Dont let the user drag outside the parent
cursor : "move",//Change the cursor icon on drag
tolerance : "pointer"//Read http://api.jqueryui.com/sortable/#option-tolerance
};
Browser the step 3 code on github :-
https://github.com/kanakiyajay/Angular-js-todolist/tree/9bafde5b12d474ab35679163a469f5a0c1c0c95e
Step 4 : Add LocalStorage Support.
You should be able to store all your todos even when you close your browser.
Fortunately html5 has got LocalStorage to your help and Angular js has got a module for that https://github.com/grevory/angular-local-storage which gives you access to browser’s local storage.
Localstorage has got a simple key and value storage system.So we will have to also serialize the array when storage it and parse it when it is obtained again.
Note that we will be using the Angular js method toJson instead of the native to get rid of the unwanted $$haskey added by angular for ng-repeat.
The app should first check whether there already a todoList previously saved and if not give the initial list.Also this should happen on initialization.
Automatic saving the list is achieved by using $scope.$watch on the todos object.
We are storing the new value key “todoList” everytime there is a change in todoList.
html :
<body ng-controller="TodoCtrl" ng-init="init()">;
js :
$scope.init = function () {
if (localStorageService.get("todoList")===null) {
$scope.todos = [
{ taskName : "Create an Angular-js TodoList" , isDone : false }
];
}else{
$scope.todos = localStorageService.get("todoList");
}
};
$scope.$watch("todos",function (newVal,oldVal) {
if (newVal !== null && angular.isDefined(newVal) && newVal!==oldVal) {
localStorageService.add("todoList",angular.toJson(newVal));
}
},true);
I have added some additional checks for null ,notDefined.
You can refresh the page and find that all your Todos are saved in the same state.
Step 5: Editable todos and Delete
We should also be able to edit the todos present and then also be able to delete the old todos.We are going to be and edited version of this jsfiddle for the directive for editinplace editing of the task names.
I have updated the directive to use jQuery and enter editing phase when double-clicked.
Here’s the code :
App.directive( 'editInPlace', function() {
return {
restrict: 'E',
scope: { value: '=' },
template: '<input class="todoField" type="text" />;',
link: function ( $scope, element, attrs ) {
// Let's get a reference to the input element, as we'll want to reference it.
var inputElement = angular.element( element.children()[1] );
// This directive should have a set class so we can style it.
element.addClass( 'edit-in-place' );
// Initially, we're not editing.
$scope.editing = false;
// ng-dblclick handler to activate edit-in-place
$scope.edit = function () {
$scope.editing = true;
// We control display through a class on the directive itself. See the CSS.
element.addClass( 'active' );
// And we must focus the element.
// `angular.element()` provides a chainable array, like jQuery so to access a native DOM function,
// we have to reference the first element in the array.
inputElement.focus();
};
// When we leave the input, we're done editing.
inputElement.on("blur",function () {
$scope.editing = false;
element.removeClass( 'active' );
});
}
};
});
The delete a todo part is easy because you just need to delete that todo from the model and it will get removed from the view.
$scope.deleteTodo = function (index) {
$scope.todos.splice(index, 1);
};
Update: It should be deleted by item and not by index.:
$scope.deleteTodo = function (item) {
var index = $scope.model[$scope.currentShow].list.indexOf(item);
$scope.model[$scope.currentShow].list.splice(index, 1);
};
<button type="button" aria-hidden="true" ng-click="deleteTodo($index)">;&times;</button>;
Github link for Step 5
Step 6 : Adding Filters for Search and showing Incomplete Tasks
If we have a lot of todos , it becomes difficult to search through them and to find a particular todo.It also becomes easier to see all the completed and incompleted tasks at once.Fortunately angular makes this task easier by the use of filters.Filters will only display a subset of todos.Note that we will also need ng-class directive to give class active according to current show.
Following is the code for the nav-pills :-
<ul class="nav nav-pills todo-filter">;
<li ng-class="{'active' : show == 'All' }" ng-click="show='All'">;<a href="#">;All</a>;</li>;
<li ng-class="{'active' : show == 'Incomplete' }" ng-click="show='Incomplete'">;<a href="#">;Incomplete</a>;</li>;
<li ng-class="{'active' : show == 'Complete' }" ng-click="show='Complete'">;<a href="#">;Complete</a>;</li>;
</ul>;
Here is the filter function
$scope.showFn = function (todo) {
if ($scope.show === "All") {
return true;
}else if(todo.isDone && $scope.show === "Complete"){
return true;
}else if(!todo.isDone && $scope.show === "Incomplete"){
return true;
}else{
return false;
}
};
You can then append this showFn filter function to ng-repeat using |.
<li ng-repeat="todo in todos | filter:showFn ">;
Searching
Searching is much more simpler using the native filter of angular-js.
It implements it automatically.Here’s the code
html :
<div class="input-group">;
<span class="input-group-btn">;
<button class="btn btn-default" type="button">;<span class="glyphicon glyphicon-search">;</span>;</button>;
</span>;
<input type="search" class="form-control" placeholder="Search" ng-model="todoSearch">;
</div>;
You can now append it to the ng-repeat using filter
<li class="todoTask" ng-repeat="todo in todos | filter:showFn | filter :todoSearch ">;
Step 7 : ngEnter
Our next step will be a adding a todo Field when enter is pressed.
As we are doing things the angular way we should have directive in order to capture the enter event on the #newTodoField.
This is the code for the directive for ngEnter
App.directive("ngEnter",function () {
return function (scope,elem) {
$(elem).keyup(function (e) {
//Enter Keycode is 13
if (e.keyCode === 13) {
/*Also update the Angular Cycle*/
scope.$apply(function () {
scope.addTodo();//Call addTodo defined inside controller
});
}
});
};
});
Step 8 : Adding Multiple Todos
The next step will be the ability to add multiple todos.Fox example we might have a list that is used only for grocery lists , the other might be a list of planned blog posts or a list of the things to do today.Thats why its important to have ability to sort the todos in different lists.
For this to work to work seamlessly we will have delete the localStorage cache as we will be changing the model too.
In Chrome Devtools , Go to the Resources Tab , then to LocalStorage , then to url ->;.On the right side you will have all the Keys and values.
Click on the ls.todoList
and press delete.
The model will be a list i.e. an array . Each element of the array will be a object having a key for name and list.The list value is an array of objects containing our todos similar to our previous todos.
Here is the js code
$scope.model = [
{
name : "Primary" , list : [
{ taskName : "Create an Angular-js TodoList" , isDone : false },
{ taskName : "Understanding Angular-js Directives" , isDone : true }
]
},
{
name : "Secondary" , list : [
{ taskName : "Build an open-source website builder" , isDone : false },
{ taskName : "BUild an Email Builder" , isDone : false }
]
}
];
currentShow is the variable which will store the current todos being shown.
On clicking a particular list we will change the currentShow to $index of the todos-name.
The todos-list should also update according to currentShow .Doing it the angular-way we will show all the todos using ng-repeat and then hide those that we don’t wanna display using ng-show.
According to the model , the view will change :
We are using the list-group-item class of bootstrap.
on clicking we want the current
html for the todos-names:
<div class="list-group">;
<a href="#" ng-repeat="todos in model" class="list-group-item" ng-class="{'active' : currentShow === $index}" ng-click="changeTodo($index)" >;
<span class="badge">;{{todos.list.length}}</span>;
{{todos.name}}
</a>;
</div>;
html for the todos lists :
<ul class="list-unstyled" ng-repeat="todos in model track by $index" ui-sortable="todoSortable" ng-model="todos.list" ng-show="$index === currentShow">
<li class="todoTask" ng-repeat="todo in todos.list | filter:showFn | filter :todoSearch ">;
</ul>
Update 20th Oct 2014:
ng-model should be not be todos because it should refer to the new array, the new ng-model should be todos.list
Thanks Alex !
Update 21st June 2015:
– Included jQuery UI Touch Punch to support touch devices
– deleteTodo() should be by item and not by index
App.js Code :-
(I have not used inline ng-click code because we will be requiring the func changeTodo later on.)
//addTodo()
$scope.model[$scope.currentShow].list.splice(0,0,{taskName : $scope.newTodo , isDone : false });
//deleteTodo()
$scope.model[$scope.currentShow].list.splice(index, 1);
//changeTodo(i)
$scope.currentShow = i;
github commit