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:
- It creates a single source of truth
- It keeps your controllers small and lightweight
- 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
Nice post! I look forward to the rest of the series.
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.
Thank you
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/
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.
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?
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
“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.
@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.
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”.
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.
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.
Thanks and that is a good idea 😀 Care to make a pull request?
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?
Thanks Samuel 😀
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!
Duh! I pulled the git code and then it became obvious once I saw what was in the partial.
×
{{note.title}}
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 — 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.
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!
You rock my world.
Haha! Totally made my day. Thanks!
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, & = @.
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 “.
Hi Neil — I actually have an entire section written on that for the book. Let me see if I can release some of it.
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?
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.
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?
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”
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!
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.
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!
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.
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?
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.
Hi Arturo – do you have an example we could walk through?
“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.
What I find interesting about this as I read it carefully some time later. It that the signature for your .service matches the .factory not the .service.
I believe it works for you because of a quirk in the way that “new” works in javascript but your code should be using .factory not .service. IMHO
Specifically the signature for module.service is to return a constructor function that gets called with new.
whereas the signature for module.factory is to return a function that gets called normally and returns an object.
In your example, at least as I read it, you are returning a function that returns an object = factory function.
Not a constructor function which would use the word “this” to assign properties.
return {
notes:function () {
return data;
},
addNote:function (noteTitle) {
},
deleteNote:function (id) {
}
};
})
For a constructor function I would have expected to see “this” used to assign properties to the new constructed object.
It just so happens that in current javascript calling new on a function that does not use “this” simply returns the result of the function.
Example: from javascript console in chrome
Constructory function
> y = function(){ this.a = 5}
function (){ this.a = 5}
> z = new y()
y {a: 5}
> z.a
5
Factory function
> w = function(){ return {“a”: 5}}
function (){ return {“a”: 5}}
> s = new w()
Object {a: 5}
> s.a
5
> t = w()
Object {a: 5}
> t.a
5
many thanks!!
Thanks for the article, it got me thinking about the two controllers and one shared “angular.service” paradigm, that your championing.
But it left me wondering how to propagate change events to all controllers when a change is made to the underlying data model?
Thanks Andrew! The easiest way is to use $rootScope as the event bus… in the service use $rootScope.$broadcast and in the controllers listen via $scope.$on.
Hi SImpulton,
in this function:
———–
deleteNote:function (id) {
var oldNotes = data;
data = [];
angular.forEach(oldNotes, function (note) {
if (note.id !== id) data.push(note);
});
}
———–
Is it faster than : data.splice(data.map(function(i){return i.id}).indexOf(id),1); ??
or maybe does your version reads easier…
very interesting article!!
Fred
It is probably NOT faster but I made some concessions for readability. Great tip though!
can you elaborate more on why it’s bad form to share state with nested controllers via $scope inheritance – assuming that the state belongs in a controller in the first place? (fields bound to view)
It seems like that may be exactly what is desired in some cases. Suppose you start with a single controller that gets too big, so you want to break it up into smaller pieces. The smaller pieces still function together as a composite entity. Why is it bad for them to access some common state from scope inheritance? How is passing the common state in through injection any different or better?
Hi Bill —
Good question… so there are cases where inheritance happens implicitly i.e. when you use ng-repeat and a child scope is created for each repeated item and also cases where it is not worth the effort to optimize at that point in time i.e. prototypes. With THAT said… inheritance forces coupling between the child controller and the parent controller. You will never be able to pick the child controller up and move it somewhere else AS IS. Implicitly relying on data to exist somewhere up the stream also creates brittle code (it is really easy to introduce unintended side effects), it impairs readability (you have to know more than just what that specific controller is doing to make any sense of it) and it severely impairs testability. Tight cohesion means that you have to spin up not only the child controller but N controllers up the stream to effectively test it. I ran into this problem many years ago with a Flex framework and it was a world of pain. We could not test ANYTHING without spinning up the entire framework because everything was dependent on something up the stream.
So I am of the opinion that it is best practice to be explicit about where your data is coming from, favor composition (dependency injection) over inheritance and strive for low cohesion and portability. Clean Code by Robert C. Martin is masterclass on this topic.
Your post explained me a LOT! Thanks. Now I have to refactor my app in proper way.
Wrapping Angular element into $() is actually an Anti-Pattern:
https://github.com/angular/angular.js/wiki/Anti-Patterns
One possibly important piece of information that should be added – services are singletons. Meaning, given a service ‘Foo’, angular will inject the same instance of Foo when requested.
This limits the applicability of this pattern to cases where data is intended to be shared across the application. This pattern is explicitly not suitable to cases where you need to have multiple instances of the same controllers on the same page.
Re: frowning on scope inheritance.
I agree with you, in principle. But there are scenarios where this pattern is the exact best thing. Take for example NG-Grid (http://angular-ui.github.io/ng-grid/). It is structured as a set of cooperating directives: ng-grid, ng-row, ng-cell etc. These directives are only meaningfully used when nested in the correct hierarchy (i.e. [grid [row [cell]]]). This nesting also directly leads to a defined scope hierarchy, in which it is perfectly legitimate for an ng-cell controller to access its parent controller and expect it to be an ng-row.
More generically – when the controller hierarchy is constrained by design, as in the case of cooperative directives, there’s no expectation of controllers reuse. In these circumstances there’s no loss of generality if controllers communicate via the scope hierarchy.
In fact, Angular provides great facilities for these use cases – see the section ‘Creating Directives that Communicate’ in https://docs.angularjs.org/guide/directive.
Very well thought out comment indeed! Well done! #highFive
Thank you for this information! I am a newbie to angularjs. Please forgive my newbie-ness.
I am having issues wrapping my head around using your architecture utilizing a factory that uses $resource to get/post data to an external api.
Would I substitute your Service for my Factory? Or would I need to use both?
Hi Joanna — good question! I would check out my series on building a RESTful web application to see a full working example but I will elaborate here. Let us say your external api returns items… I would create an ItemsModel using factory or service (the difference comes to to preference imo) and inject $resource or $http (again preference) into ItemsModel and handle all your asynchronous logic there. The point would be to create an AngularJS service that wraps your service calls and then makes those results available to the rest of your application. Does this make sense?
Thanks for wonderful blog. Cleared many points for me!
Very good, clear explanation!
Why you use service in Lesson 3 which return object? I thought that in this cause need use factory.
Errors of my youth 😀
Thank u
It helps me a lot , thanks !!!!!!
Thanx alot, this one really helped getting a deeper understanding of angular!
Thank you very much, this post has been really helpful and illustrative.