Select Page

This is Part One of a series that is going to deconstruct the lessons I learned from building AngularJS Sticky Notes. This is going to be different from other posts that I have done in that we will be starting with a finished product and working backwards. I will do my best to approach it in a way that can be translated to how applications are really written. From nothing, through many, many iterations and edits.

This fiddle can be found here:
http://jsfiddle.net/simpulton/VJ94U/

The git repository is here:
https://github.com/simpulton/angular-sticky-notes

My goal is that the passerby will see something they like about AngularJS and try it. If you have already spent some time with AngularJS, I am proof that there is still so much to learn. Let’s get started!

Lesson 1: Make friends and listen

I owe a great deal of gratitude to the amazing AngularJS community, and it was because of them that a lot of the rough edges of the application were smoothed out. I thought I had built something really clever, and was humbled and amazed to find that there were dozens of things that I could have done better.

So we arrive at my first piece of advice for any application: be courageous and solicit feedback from other developers. Doing peer reviews has never been easier! You can get advice from people all around the world who are genuinely interested in helping, and will go the extra mile to give thoughtful and relevant feedback.

Here is the mailing list thread to see the conversation that formed the final version. It is full of great advice!
https://groups.google.com/forum/?fromgroups#!topic/angular/kNH-gB0vKqQ

Lesson 2: Don’t let controllers own the domain model

In the application I have a service called notesService that is responsible for maintaining the data for the application.

I originally had this functionality in the NotebookCtrl, but realized that this simply was not scalable.

It is considered bad form for two controllers to share information via $scope inheritance.

So how do you share data between controllers?
And how do you implement cross-controller communication?

To share data between controllers, the solution is [ironically] very simple. Extract the data models that need to be shared across the application into a service and then inject that service into the controllers that need it.

angular.module('myApp', [])
    .service('notesService', function () {
        return {
            notes:function () {
            },
            addNote:function (noteTitle) {
            },
            deleteNote:function (id) {
            }
        };
    })
    .controller('NotebookCtrl', ['$scope', 'notesService', function ($scope, notesService) {
        $scope.getNotes = function () {
            return notesService.notes();
        };

<pre><code>    $scope.addNote = function (noteTitle) {
        if (noteTitle != '') {
            notesService.addNote(noteTitle);
        }
    };

    $scope.deleteNote = function (id) {
        notesService.deleteNote(id);
    };

    $scope.resetForm = function () {
        $scope.noteTitle = '';
    };
    }]);
</code></pre>

If an event happens that needs to modify the data, the controller delegates that responsibility to the service.

There are a few benefits to this approach:

  1. It creates a single source of truth
  2. It keeps your controllers small and lightweight
  3. You can easily hook the service into a real backend

Let me press pause here for a second. Cross-controller communication is a fairly advanced topic and a person could write an entire blog post just on this subject. This is just one small facet of the problem.

Lesson 3: Define an API for your service

Even though JavaScript is not a classical language like Java, it is still important to strive for encapsulation in your design. The idea is to only expose the parts that you need to be available, and nothing more.

Encapsulation is achieved via the module pattern. We use the noteService constructor function to define a module that manages the data variable. This data var is accessed [via closure access] by the API that the constructor function returns.

Witness the following code:

.service('notesService', function () {
    var data = [
        // This is private
    ];

<pre><code>return {
    notes:function () {
        // This exposed private data
        return data;
    },
    addNote:function (noteTitle) {
        // This is a public function that modifies private data
    },
    deleteNote:function (id) {
        // This is a public function that modifies private data
    }
};
</code></pre>
})

There are three functions that are available in the service, and nothing more. The underlying data is even being protected and can only be accessed through a public function.

Lesson 4: DOM wrangling belongs in the link function of a directive

DOM manipulation and responding to DOM events are tricky and, left unchecked, will render your application a total mess in no time. One of my favorite things about AngularJS is that they have thoughtfully provided a place to put all of this particular code: directive(s).

Notice the myNote directive:

.directive('myNote', function () {
    return {
        link:function (scope, element, attrs) {

<pre><code>        var $el = $(element);

        $el.hide().fadeIn('slow');

        $('.thumbnails').sortable({
            placeholder:"ui-state-highlight", forcePlaceholderSize:true
        });
    }
};
</code></pre>
})

By using the directives to directly manipulate DOM elements and Controllers to define databinding models, the controllers should know nothing about the DOM/views. One of the great advantages of this approach is that it makes it much easier to test controllers. It is so much nicer to run tests on controllers without having to construct or prepare the HTML DOM (visual elements that comprise the ‘view’).

Lesson 5: Embrace the functional nature of JavaScript

JavaScript is a dynamic, functional language and, in my opinion, is where all the magic is.

Examine the code below:

angular.module('myApp', [])
    .service('notesService', function () {
        // Service functionality goes here
    })
    .directive('myNotebook', function ($log) {
        // Directive functionality goes here
    })
    .directive('myNote', function () {
        // Directive functionality goes here
    })
    .controller('NotebookCtrl', ['$scope', 'notesService', function ($scope, notesService) {
        // Controller functionality goes here
    }]);

In this snippet, I have defined my module, a service, two directives, and a controller in a way that builds on top of the previous definition. By daisy chaining my declarations it is easy to maintain what actually is attached to the underlying module. If you really wanted to get wild, you could even add new functionality to the module at runtime, and because JavaScript is dynamic, it would totally work.

Special note: because you have the power to add functionality at runtime in such a powerful way, it is important to be extra disciplined about writing tests.

Conclusion

I have just scratched the surface of the AngularJS Sticky Notes application. I wanted to cover the broad strokes first since I believe they apply to any AngularJS application. Stay tuned for the next parts of this series as I start to dig deeper into each of the features one by one.

I owe a special thanks to Thomas Burleson, Igor Minar, Misko Hevery and everyone on the mailing list for all of their awesome input. The community is my favorite feature of the framework!

AngularJS Mailing List
https://groups.google.com/forum/?fromgroups#!forum/angular