AngularJS is a powerful tool to make all kind of web application. Chrome Extension is not an exception too. Today, let me show you my experience when using Angular to build an Chrome Extension. Actually, I built another version of this extension using jQuery too. But the AngularJS version is easier to build and maintain.

About this extension, it’s just an very simple Todo Application. You can test our extension on Chrome Store at here: https://chrome.google.com/webstore/detail/ng-todo/aommafigioodamhobgffifgoonmpnmmc

AngularJS todo extension

The final source code is located at here: https://github.com/davidtran/ng-todo

Project structure

This is the structure of our Chrome Extension

AngularJS Chrome Extension project structure

Manifest.json is the JSON formatted file which contains configuration for our configuration. App is the folder contains AngularJS webapp.
We use 3 libraries: jquery, angular and bootstrap for easier styling. In order to help you debug our extension easier, I use the uncompressed AngularJS script.

Prepare manifest.json

When make a Chrome Extensions, manifest.json is the most important file. This JSON formatted file will provide information about our extension, include extension name, extension description, version, locations of UI script, permissions.

{
  "manifest_version": 2,
  "name"            : "ng-Todo",
  "description"     : "Minimal todo app for minimalist",
  "short_name"      : "ng-Todo",
  "version"         : "0.1",
  "browser_action": {
    "default_popup": "index.html",
    "default_icon" : "images/logo.png"
  },
  "permissions": [
    "debugger",
    "storage"
  ],
  "content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'"
}

 

In manifest.json, we set index.html is our popup webpage. So next, we examine index.html
**AngularJS Template **

Our template is created in the following code:

<!DOCTYPE html>
<html ng-app='app'>
<head>
    <title>Angular Todo Chrome Extension</title>
    <link rel="stylesheet" type="text/css" href="lib/bootstrap/css/bootstrap.min.css">
    <link rel="stylesheet" type="text/css" href="css/style.css">
</head>
<body ng-controller='todoCtrl'>
    <div class='container'>
        <div class='row'>
            <div class='col-md-12'>
                <h5>{{ (todoList | filter: {completed:false}).length}} pending task(s)</h5>
                <form id='todo-form' ng-submit='add()'>
                    <div class="form-group">
                        <input type='text' id='new-todo' ng-model='newContent'
                            class="form-control" placeholder="What you need to do ?"/>
                    </div>
                </form>
            </div>
        </div>
        <div class='row' id="todo-list">
            <div class='todo-item col-md-12' ng-repeat='todo in todoList track by Page on Page on todo.id| orderBy: createdAt'
                    ng-class='{completed: todo.completed}'>
                <input type='checkbox' ng-model='todo.completed' ng-click='toggleCompleted()'/>
                <span class='todo-content'>{{todo.content}}</span>
                <a class='btn-remove-todo pull-right' ng-click='remove(todo)'>
                    <i class='glyphicon glyphicon-remove'></i></a>
            </div>
        </div>
        <div class='row'>
            <div class='col-md-12'>
                <div id='toolbar'>
                    <a id="btn-remove-all" ng-click='removeAll()'
                        class='btn btn-small btn-default pull-right'
                        ng-show="todoList.length > 0">
                        <i class="glyphicon glyphicon-remove"></i> Clear all
                    </a>
                </div>
            </div>
        </div>
    </div>
    <script type="text/javascript" src="lib/jquery/jquery-2.1.3.min.js"></script>
    <script type="text/javascript" src="lib/angular/angular.js"></script>
    <script type="text/javascript" src="lib/bootstrap/js/bootstrap.min.js"></script>
    <script type="text/javascript" src="app/app.js"></script>
    <script type="text/javascript" src="app/todoCtrl.js"></script>
    <script type="text/javascript" src="app/todoStorage.js"></script>
</body>
</html>

 

Attribute ng-app=”app” is registered in our app at html tag. We also use attribute ng-controller=”todoCtrl” and register todoCtrl at body tag.

Our template is a little bit complex. Let’s break it down:
<h3>Todo Form</h3>

This form only has one input text to add new todo.

* Expression {{ (todoList | filter: {completed:false}).length}} filters all todo which completed attribute value is false in todoList and return total of remaining todos.
* ng-submit=’add()’ works when user submit form by press enter key, it call add() method in todoCtrl which add a new todo.
* ng-model=’newContent’ This directive bind value of input text to newContent variable. In todoCtrl we can access this variable via $scope.newContent

Todo list

This piece of HTML responsible for render our todo list.

<div class='row' id="todo-list">
		<div class='todo-item col-md-12' ng-repeat='todo in todoList | orderBy: createdAt' ng-class='{completed: todo.completed}'>
				<input type='checkbox' ng-model='todo.completed' ng-click='toggleCompleted()'/>
				<span class='todo-content'>{{todo.content}}</span>
				<a class='btn-remove-todo pull-right' ng-click='remove(todo)'><i class='glyphicon glyphicon-remove'></i> </a>
		</div>
</div>

 

The attribute ng-repeat=’todo in todoList | orderBy: createdAt’ will create a div.todo-item for every todo. We have attribute ng-class='{completed:todo.completed}’, this expression toggle completed class for #todo-item based on the completed attribute of todo item.

By binding the value of todo.completed to the checkbox, the checkbox value will be changed based on the value of todo.completed

Expression {{todo.content}} will be replace by actual value of todo.content
Because we bind remove(todo) to btn-remove-todo link, when we click that link, it will call method remove() method of todoCtrl.

We create our app module in the following codes:

angular.module("app", []);

The first parameter of module() function is the module name, we have already used this module name in attribute ng-app in html tag of index.html

The second parameter is the list of dependencies, but in our todo, we don’t need any dependency so it’s just an empty array.

todoStorage service

Our todoStorage service has responsibility of store todo data, and it has some methods to retrieve, remove and filter our todo list. In this service, we use Chrome Storage API to store our todo data.

angular.module('app').service('todoStorage', function ($q) {
    var _this = this;
    this.data = [];

    this.findAll = function(callback) {
        chrome.storage.sync.get('todo', function(keys) {
            if (keys.todo != null) {
                _this.data = keys.todo;
                for (var i=0; i<_this.data.length; i++) {
                    _this.data[i]['id'] = i + 1;
                }
                console.log(_this.data);
                callback(_this.data);
            }
        });
    }

    this.sync = function() {
        chrome.storage.sync.set({todo: this.data}, function() {
            console.log('Data is stored in Chrome storage');
        });
    }

    this.add = function (newContent) {
        var id = this.data.length + 1;
        var todo = {
            id: id,
            content: newContent,
            completed: false,
            createdAt: new Date()
        };
        this.data.push(todo);
        this.sync();
    }

    this.remove = function(todo) {
        this.data.splice(this.data.indexOf(todo), 1);
        this.sync();
    }

    this.removeAll = function() {
        this.data = [];
        this.sync();
    }

});

chrome.storage.sync.get will return data in a callback that’s why we need a callback to return data.

Notice that we call sync() method every time we modify our data. It synchronizes our data to Chrome storage.
todoStorage service will be injected to our TodoCtrl, by that way we keep our controller separated from too much logic.

TodoCtrl Controller

In index.html, we already declare our todoCtrl at the body tag. Now we implement it.

 

angular.module('app').controller('todoCtrl', function ($scope, todoStorage) {

    $scope.todoStorage = todoStorage;

    $scope.$watch('todoStorage.data', function() {
        $scope.todoList = $scope.todoStorage.data;
    });

    $scope.todoStorage.findAll(function(data){
        $scope.todoList = data;
        $scope.$apply();
    });

    $scope.add = function() {
        todoStorage.add($scope.newContent);
        $scope.newContent = '';
    }

    $scope.remove = function(todo) {
        todoStorage.remove(todo);
    }

    $scope.removeAll = function() {
        todoStorage.removeAll();
    }

    $scope.toggleCompleted = function() {
        todoStorage.sync();
    }

});

 

When todoCtrl is created, we call $scope.todoStorage.findAll to populate data in $scope.todoList. Other methods, like add(), remove(), removeAll(), toggleCompleted() are assigned to $scope service. Hence we can use them in the html template.

Run our extension

Google Chrome makes it very easy to develop and test extensions. From Google Chrome browser, go to address: chrome://extensions. On this page, click “Developer mode” to reveal “Load unpacked extension…” button. Click on that button and locate the location of our extension. Click “Select” to load our extension, after that you can see our Todo extension button on the right side of toolbar.
Anything if you want to debug popup script, right click “Inspect popup”. It will show our powerful inspector to debug our extension.
If you make changes to our extension, you can click on the “Reload” button of our extension in Extension page.

Conclusion

In this tutorial, you learn about developing a simple Todo chrome extension with AngularJS. By using Angular controller, service and template, developing an AngularJS app is easy and fun.
We learn how to create manifest.json to declare our extension and use Chrome Storage API to store todo data. In the end, you learn to test and debug our extension by tools provided by Google Chrome.