AngularJS Sticky Notes Pt 1 – Architecture

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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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();
        };
       
        $scope.addNote = function (noteTitle) {
            if (noteTitle != '') {
                notesService.addNote(noteTitle);
            }
        };
       
        $scope.deleteNote = function (id) {
            notesService.deleteNote(id);
        };
       
        $scope.resetForm = function () {
            $scope.noteTitle = '';
        };
        }]);

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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
.service('notesService', function () {
    var data = [
        // This is private
    ];

    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
        }
    };
})

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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
.directive('myNote', function () {
    return {
        link:function (scope, element, attrs) {

            var $el = $(element);

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

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

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:

1
2
3
4
5
6
7
8
9
10
11
12
13
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

AngularJS Sticky Notes Pt 1 – Architecture

36 Responses

  1. Nice post! I look forward to the rest of the series.

    Dinesh June 27, 2012 at 5:47 am #
  2. Hi,
    this is a nice post!

    One thing to note is that there is no need to $(wrap) elements in directives, because AngularJS provides them wrapped already:

    var $el = $(element); //this wraps already wrapped element

    Angular never gives you raw DOM elements, because they vary between browsers.

    Witold Szczerba June 27, 2012 at 9:08 am #
  3. Thank you

    BSR June 28, 2012 at 4:38 pm #
  4. Hi – First this is an excellent resource so thank you for providing it. I noticed getNotes is called 6 times on page load. Is this expected behavior?

    Thanks.

    jsfiddle with 6 alerts for example: http://jsfiddle.net/VJ94U/183/

    Jeff July 20, 2012 at 6:00 am #
  5. You may want to checkout this post I’ve been updating too for more helpful notes: http://deansofer.com/posts/view/14/AngularJs-Tips-and-Tricks-UPDATED

    Another tip (for your next post) I might recommend is decoupling cause and effect. I’ve been trying to get my team to focus on events changing variables and watches responding to changes, rather having your functions firing all of your logic.

    If you find yourself calling the same function in more than 1 location of the controller, you can probably switch to an approach with $watch.

    ProLoser July 20, 2012 at 6:32 am #
  6. Sometimes one uses factory and somtimes the service method to define services?

    Is there a difference between the two or are they doing the same thing?

    joel July 31, 2012 at 5:37 pm #
  7. The difference is in the type of function you send in.

    You can read more about it here.

    http://docs.angularjs.org/api/AUTO.$provide

    simpulton July 31, 2012 at 5:53 pm #
  8. “Don’t let controllers own the domain model”. Thank you very much for highlighting this (the lights have now come on for me). Many AngularJS examples show data associated with controller scope, so in my initial app, that’s how I started… I have multiple controllers that need to feed data/updates to a single view. I associated this view with what I designated as my “master controller” and used $scope.$emit (in the other controllers) to pass info to this master controller. I now see how a service, which owns the shared data, is a much better architectural approach, allowing each controller to interact directly with the service. For some reason I was mentally locked into: this a view, this is the controller for that view, and the controller has all the data for that view. I suppose I’ve been reading too many simple AngularJS examples.

    Mark Rajcok August 18, 2012 at 6:12 pm #
  9. @simpulton, in your service definition, you use
    return {
    notes:function () { …
    Is there are reason for using/preferring this syntax vs. the (somewhat shorter) object constructor function syntax:
    this.notes = function() { …

    @Witold, thanks for the tip.

    Mark Rajcok August 24, 2012 at 8:21 pm #
  10. Wow, I finally understood the concept of “services”, thanks for the write up @SIMPULTON. I have been looking everywhere for examples like this and there aren’t many out there. Perhaps the guys from docs.angularjs.org would be interested in this tut and could add it to a new section on their site titled “Case Studies”.

    JorgeC September 7, 2012 at 1:37 am #
  11. Sorry for the broken record, but you may want to checkout AngularUI as it has an animate directive (for animating the injection of new DOM elements) and a sortable directive that can both be attached to the same element.

    Dean | ProLoser September 20, 2012 at 2:09 am #
  12. Wonderful example. I learned a lot.

    BTW: If you delete a note and subsequently add a note then the two last notes end up having the same id. I don’t think that you should use data.length to assign an id’s to notes.

    Niels L Ellegaard October 12, 2012 at 6:49 pm #
  13. Thanks and that is a good idea :D Care to make a pull request?

    simpulton October 12, 2012 at 6:51 pm #
  14. This is a great example. I have been bashing my head trying to grok the architecture of angular.js and you lay it out so much better. Its like the examples and tutorials on the angular web site lead you down the wrong path. The examples should start with a module and a service and build from that like this one does.

    I am trying to find the partial “partials/notebook-directive.html”, on the jsfiddle but I can’t. I am not the great yet with jsfiddle. So it may be in an obvious place. But I don’t see it and its not in the resources. What am I missing?

    Samuel Smith October 16, 2012 at 4:02 pm #
  15. Thanks Samuel :D
    So there is a little trick we use in jFiddle to do partials which you can see in the script block on the second line of the HTML pane.

    Also if you REALLY want to play around with it please grab the repo from github and give it a spin! Let me know if you have any problems as sometimes the API updates etc and things break.

    Cheers!

    simpulton October 16, 2012 at 4:07 pm #
  16. Duh! I pulled the git code and then it became obvious once I saw what was in the partial.

    ×

    {{note.title}}

    Samuel Smith October 16, 2012 at 4:20 pm #
  17. Hey Lucas

    Just revisited your example after using jQ UI sortable in a project and recalling you used in Sticky Notes.

    My concern was calling/updating sortable each time the directive is applied – especially if there are many notes.

    My solution is to trigger an event in the directive and listen for the event in a sortable initialize() function. In the init code I use a timer to throttle/debounce multiple events so the init code only runs once.

    Do you think this is a valid concern and appropriate solution ?

    Thanks

    Johan October 22, 2012 at 11:05 pm #
  18. Johan — I think that is a valid approach

    Interestingly enough, I am using the angular-ui sortable directive on a large list with nested lists inside of those and it raises some interesting challenges. Especially! When you have to tie into a server and keep everything synced up.

    So I am actually working on that same problem. I will keep you posted on how it shakes out.

    simpulton October 24, 2012 at 4:48 pm #
  19. Thanks Lucas – nice to bounce the approach off you and validate.

    As it turns out the directive is super simple – restrict: ‘C’ that ties to the existing styles used by the UI sortable. Directive just emits event, it does trigger multiple times (due to repeater and $digest) but the throttle function cleans that up easily.

    I think I’ll leave those nested sortables to you!

    Johan October 24, 2012 at 7:10 pm #
  20. You rock my world.

    Robert November 15, 2012 at 3:02 am #
  21. Haha! Totally made my day. Thanks!

    simpulton November 15, 2012 at 5:22 am #
  22. Really helpful post, any chance you could do a post on the various as scopes in directives eg. Isolate, transclusion and examples of the various bindings, & = @.

    Neil January 13, 2013 at 2:41 pm #
  23. Hi, thank you for this nice and understandable post. One thing I would like to ask about is what do you meant by the third point of Lesson 2: Don’t let controllers own the domain model ” hook the service into a real back end “.

    Adelin January 23, 2013 at 7:17 am #
  24. Hi Neil — I actually have an entire section written on that for the book. Let me see if I can release some of it.

    simpulton January 23, 2013 at 7:39 am #
  25. Controllers are well suited for handling state for very specific portions of the application interface. They should not hold state for the entire application as controllers should not talk to other controllers. So! You isolated your domain model ie really important data to services so that you can inject them where need. From there it is easy to hook it into a back end if you need persistence.

    Make sense?

    simpulton January 23, 2013 at 7:43 am #
  26. I didn’t asked the question, but it makes a lot of sense :)
    great tut,
    i was searching all over the web to find how to connect a directive to a controller.
    the tutorials in the angularJs.org are really confusing, and now things are getting clearer.

    Edo January 26, 2013 at 1:18 am #
  27. Hi,

    Great article! Very clear and well written. This is a somewhat unrelated question, but in the fiddle you’ve posted, I can’t for the life of me figure out how the partial “partials/notebook-directive.html” gets loaded! I can see no network request being issued for it in the browser, nor do I see it being manually inserted into the templateCache, yet somehow, the partial is being correctly loaded. What sorcery is this?

    En February 20, 2013 at 5:58 am #
  28. Hi En –
    This is a jsfiddle workaround in the HTML. You declare your partial in the script tag and give it an id of the html file it would use. ie script type=”text/ng-template” id=”partials/notebook-directive.html”

    simpulton February 20, 2013 at 4:05 pm #
  29. Hi Simpulton,

    Thanks. I expected it to be something like that. Unfortunately, I still can’t locate where the id specified in the script tag is mapped to a resource defined in jsFiddle. Nor can I see a browser request being made for said resource. I looked in the jsFiddle docs and it appears that github files can be directly linked in, but where the github repo etc. is specified within the fiddle, is not apparent.

    But thanks anyway!

    En February 21, 2013 at 1:03 am #
  30. Notice the type and id of this script tag:
    type=”text/ng-template” id=”partials/notebook-directive.html”

    And then in the myNotebook directive:
    templateUrl:”partials/notebook-directive.html”

    I wouldn’t get too hung up on how we are doing it in jsFiddle as it is totally hacky and you are not going to see a browser request.

    simpulton February 21, 2013 at 1:39 am #
  31. Hi Simpulton,

    I feel very shamefaced since I just realized that the template content was actually embedded within the script tags. Sorry, temporary insanity!

    Cheers!

    En February 21, 2013 at 2:57 am #
  32. Great article! I have been trying to make my architecture more modular but I am still running into a problem. The service code above exposes a ‘addNote’ and ‘deleteNote’. So if I have a 100 rest services I’d have to write a 100 controllers with ‘add___’ and ‘delete___’, so far I have about 15 and I am copying and pasting more that i’d like.

    The way I would like to write my code is that the info about specific models is moved to the HTML view as much as possible, because it certainly has know the model attributes in order to display the model data. And the controllers/services are partially model agnostic. Here is what I mean when I say partially. We could have a List controller that knows that the model is an array. Or a paginated List controller that knows it is an array, and may know to use a pagination factory service. Say our particular use case calls for a list with a nested paginated list. An example would be if we are modeling countries by continent, and we don’t need to paginate continents since they will be just 5 for the lifetime of the app :) . But there is enough country info that we want to paginate them. Potentially I could create a mini controller that just binds the RESTful api through a $resource object to the controller hierarchy. And then create my angular decorated HTML with references to the generic controllers for the case as well as my specific data binding controller. And I am done!!!

    Part of the idea can be that later I may want to take my same countries page and make the countries editable. For sure I am going to have to create more HTML for the form itself. So I do that, and the I just plug a controller div to make the members editable. And I am done again!!!! Notice that because I already have the rest binding in place I wouldn’t even have to write the binding controller.

    Where I am running into a problem is that in order for this to work I don’t have a good way for each controller to bind to the right model. At first I thought to use the same model name $scope.model in all controllers. But that creates conflicts with nested controllers that are meant to look at a different model.

    Arturo Hernandez May 22, 2013 at 3:46 pm #
  33. Hi Arturo –
    A few things… I recommend creating a base service that handles all of the mundane code in one place. The idea is to identify the commonalities and then code around the differences. REST is nice because it shares the same set of verbs and so it makes it fairly easy.

    Secondly, I would abstract your domain model [your countries] into a service and then inject it into your controllers as necessary. From that service, you can expose just the information that that specific controller is interested in. I also would not recommend binding a controller to a REST api as the controller is only concerned about the state of the view and should be very small and specific. I find that having to worry about that AND marshall data from the server makes for a fatter controller than I would like.

    To summarize, use a base service that all your other services extend off of and extract your domain model into a service and only expose what each controller needs. Does this make sense?

    simpulton May 22, 2013 at 4:43 pm #
  34. It makes sense, but how do I generalize further? I already have many services that hold the actual persistent data via the $resource object. So my controllers do communicate via services. If I just move the code left in my controllers to a service, I am going to be copying and pasting services instead of controllers. I want to stop doing that but I’m not all there yet.

    Say language is an attribute of country, I can move the country object around without needing to know there is a language attribute. The only thing that matters is that there is a continents array and that each continent has a country array. Now if I can have a controller or a service know the name of the array, I can have full generic code. Or at least I could in theory. The only places that need to know about the “language” attribute are the HTML view and the RESTful server implementation.

    I am using controller inheritance for this. I have implemented delete/undelete functions for lists. Briefly, delete goes back all the way to the RESTful api, and an undelete button shows up. I can add this function as an extra controller and it works with any list. My problem is when I have a nested controller that I do not want to inherit. In this example I can add delete/undelete to countries by adding a generic control. But countries don’t inherit from continents.

    Arturo Hernandez May 22, 2013 at 6:16 pm #
  35. Hi Arturo – do you have an example we could walk through?

    simpulton May 28, 2013 at 8:26 pm #
  36. “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.”

    Could you expand on this? I don’t see a functional difference between services and global variables. They have all the exact same hallmarks and weaknesses. They simplify code, they can be accessed and modified by anything, they pile on to the name space, when things get complicated you can guarantee a programmer will think “maybe I should just use that?”

    frankly. They’re javascript global variables that you can mock for tests, and that have namespaces. An improvement, but it’s still architectural suicide. There’s over 40 years of material documenting exactly why using global variables for state causes problems in large scale projects. How do services avoid those faults? They seem analogous to me.

    Ghoti June 3, 2013 at 5:15 pm #

Leave a Reply