6 min read

(For more resources related to this topic, see here.)

We are going to try mastering modules which will help us break up our application into sensible components.

Our configuration file for modules is given as follows:

var guidebookConfig = function($routeProvider) { $routeProvider .when('/', { controller: 'ChaptersController', templateUrl: 'view/chapters.html' }) .when('/chapter/:chapterId', { controller: 'ChaptersController', templateUrl: 'view/chapters.html' }) .when('/addNote/:chapterId', { controller: 'AddNoteController', templateUrl: 'view/addNote.html' }) .when('/deleteNote/:chapterId/:noteId', { controller: 'DeleteNoteController', templateUrl: 'view/addNote.html' }) ; }; var Guidebook = angular.module('Guidebook', []). config(guidebookConfig);

The last line is where we define our Guidebook module. This creates a namespace for our application.

Look at how we defined the add and delete note controllers:

Guidebook.controller('AddNoteController', function ($scope, $location, $routeParams, NoteModel) { var chapterId = $routeParams.chapterId; $scope.cancel = function() { $location.path('/chapter/' + chapterId); } $scope.createNote = function() { NoteModel.addNote(chapterId, $scope.note.content); $location.path('/chapter/' + chapterId); } } ); Guidebook.controller('DeleteNoteController', function ($scope, $location, $routeParams, NoteModel) { var chapterId = $routeParams.chapterId; NoteModel.deleteNote(chapterId, $routeParams.noteId); $location.path('/chapter/' + chapterId); } );

Since Guidebook is a module, we’re really making a call to module.controller() to define our controller. We also called Guidebook.service(), which is really just module.service(), to define our models.

One advantage of defining controllers, models, and other components as part of a module is that it groups them together under a common name. This way, if we have multiple applications on the same page, or content from another framework, it’s easy to remember which components belong to which application.

The second advantage of defining components as part of a module is that AngularJS will do some heavy lifting for us.

Take our NoteModel, for instance. We defined it as a service by calling module.service(). AngularJS provides some extra functionality to services within a module. One example of that extra functionality is dependency injection. This is why in our DeleteNoteController, we can include our note model simply by asking for it in the controller’s function:

Guidebook.controller('DeleteNoteController', function ($scope, $location, $routeParams, NoteModel) { var chapterId = $routeParams.chapterId; NoteModel.deleteNote(chapterId, $routeParams.noteId); $location.path('/chapter/' + chapterId); } );

This only works because we registered both DeleteNoteController and NoteModel as parts of the same module.

Let’s talk a little more about NoteModel. When we define controllers on our module, we call module.controller(). For our models, we called module.service(). Why isn’t there a module.model()?

Well, it turns out models are a good bit more complicated than controllers. In our Guidebook application, our models were relatively simple, but what if we were mapping our models to some external API, or a full- fledged relational database?

Because there are so many different types of models with vastly different levels of complexity, AngularJS provides three ways to define a model: as a service, as a factory, and as a provider.

We’ll now see how we define a service in our NoteModel:

Guidebook.service('NoteModel', function() { this.getNotesForChapter = function(chapterId) { ... }; this.addNote = function(chapterId, noteContent) { ... }; this.deleteNote = function(chapterId, noteId) { ... }; });

It turns out this is the simplest type of model. We’re simply defining an object with a number of functions that our controllers can call. This is all we needed in our case, but what if we were doing something a little more complicated?

Let’s pretend that instead of using HTML5 local storage to store and retrieve our note data, we’re getting it from a web server somewhere. We’ll need to add some logic to set up and manage the connection with that server.

The service definition can help us a little here with the setup logic; we’re returning a function, so we could easily add some initialization logic.

When we get into managing the connection, though, things get a little messy. We’ll probably want a way to refresh the connection, in case it drops. Where would that go? With our service definition, our only option is to add it as a function like we have for getNotesForChapter(). This is really hacky; we’re now exposing how the model is implemented to the controller, and worse, we would be allowing the controller to refresh the connection.

In this case, we would be better off using a factory. Here’s what our NoteModel would look like if we had defined it as a factory:

Guidebook.factory('NoteModel', function() { return { getNotesForChapter: function(chapterId) { ... }, addNote: function(chapterId, noteContent) { ... }, deleteNote: function(chapterId, noteId) { ... } }; });

Now we can add more complex initialization logic to our model. We could define a few functions for managing the connection privately in our factory initialization, and give our data methods references to them. The following is what that might look like:

Guidebook.factory('NoteModel', function() { var refreshConnection = function() { ... }; return { getNotesForChapter: function(chapterId) { ... refreshConnection(); ... }, addNote: function(chapterId, noteContent) { ... refreshConnection(); ... }, deleteNote: function(chapterId, noteId) { ... refreshConnection(); ... } }; });

Isn’t that so much cleaner? We’ve once again kept our model’s internal workings completely separate from our controller.

Let’s get even crazier. What if we backed our models with a complex database server with multiple endpoints? How could we configure which endpoint to use?

With a service or a factory, we could initialize this in the model. But what if we have a whole bunch of models? Do we really want to hardcode that logic into each one individually?

At this point, the model shouldn’t be making this decision. We want to choose our endpoints in a configuration file somewhere.

Reading from a configuration file in either a service or a factory will be awkward. Models should focus solely on storing and retrieving data, not messing around with configuration updates.

Provider to the rescue! If we define our NoteModel as a provider, we can do all kinds of configuration from some neatly abstracted configuration file.

Here’s how our NoteModel looks if we convert it into a provider:

Guidebook.provider('NoteModel', function() { this.endpoint = 'defaultEndpoint'; this.setEndpoint = function(newEndpoint) { this.endpoint = newEndpoint; }; this.$get = function() { var endpoint = this.endpoint; var refreshConnection = function() { // reconnect to endpoint }; return { getNotesForChapter: function(chapterId) { ... refreshConnection(); ... }, addNote: function(chapterId, noteContent) { ... refreshConnection(); ... }, deleteNote: function(chapterId, noteId) { ... refreshConnection(); ... } }; }; });

Now if we add a configuration file to our application, we can configure the endpoint from outside the model:

Guidebook.config(function(NoteModelProvider) { NoteModelProvider.setEndpoint('anEndpoint'); });

Providers give us a very powerful architecture. If we have several models, and we have several endpoints, we can configure all of them in one single configuration file. This is ideal, as our models remain single-purpose and our controllers still have no knowledge of the internals of the models they depend on.

Summary

Thus we saw in this article modules provide an easy, flexible way to create components in AngularJS. We’ve seen three different ways to define a model, and we’ve discussed the benefits of using a module to create an application namespace.

Resources for Article :


Further resources on this subject:


LEAVE A REPLY

Please enter your comment!
Please enter your name here