Wednesday 18 December 2013

Using RequireJS with Angular - Inline Block's Blog


Since attending Fluent Conf 2013 and watching the many AngularJS talks and seeing the power of its constructs, I wanted to get some experience with it.
Most of the patterns for structuring the code for single page webapps, use some sort dependency management for all the JavaScript instead of using global controllers or other similar bad things. Many of the AngularJS examples seem to follow these bad-ish patterns. Using angular.module('name' , []), helps this problem (why don't they show more angular.module() usage in their tutorials?), but you can still end up with a bunch of dependency loading issues (at least without hardcoding your load order in your header). I even spent time talking to a few engineers with plenty experience with Angular and they all seemed to be okay with just using something like Ruby's asset pipeline to include your files (into a global scope) and to make sure everything ends up in one file in the end via their build process. I don't really like that, but if you are fine with that, I'd suggest you do what you are most comfortable with.

Why RequireJS?

I love using RequireJS. You can async load your dependencies and basically remove all globals from your app. You can use r.js to compile all your JavaScript into a single file and minify that easily, so that your app loads quickly.
So how does this work with Angular? You'd think it would be easy when making single page web apps. You need your 'module' aka your app. You add the routing to your app but to have your routing, you need the controllers and to have your controllers you need the module they belong to. If you do not structure your code and what you load in with Require.js in the right order, you end up with circular dependencies.

Example

So below for my directory structure. My module/app is called "mainApp".
My base public directory:
directory listing
123456789101112131415
index.html- javascripts    - controllers/    - directives/    - factories/    - modules/    - routes/    - templates/    - vendors/      require.js      jquery.js    main.js    require.js- stylesheets/  ...
Here is my boot file, aka my main.js.
javascripts/main.js
12345678910111213141516171819
require.config({  baseUrl: '/javascripts',  paths: {    'jQuery': '//ajax.googleapis.com/ajax/libs/jquery/1.10.1/jquery.min',    'angular': '//ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular',    'angular-resource': '//ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular-resource'  },  shim: {    'angular' : {'exports' : 'angular'},    'angular-resource': { deps:['angular']},    'jQuery': {'exports' : 'jQuery'}  }});require(['jQuery', 'angular', 'routes/mainRoutes'] , function ($, angular, mainRoutes) {  $(function () { // using jQuery because it will run this even if DOM load already happened    angular.bootstrap(document , ['mainApp']);  });});
You'll notice how I am not loading my mainApp in. Basically we are bringing in the last thing that needs to configured for your app to load, to prevent circular dependencies. Since the Routes need the mainApp controllers and the controllers need the mainApp module, we just have them directly include the mainApp.js.
Also we are configuring require.js to bring in angular and angular-resource (angular-resource so we can do model factories).
Here is my super simple mainApp.js
javascripts/modules/mainApp.js
123
define(['angular' , 'angular-resource'] , function (angular) {  return angular.module('mainApp' , ['ngResource']);});
And here is my mainRoutes file:
javascripts/routes/mainRoutes.js
123456
define(['modules/mainApp' , 'controllers/listCtrl'] , function (mainApp) {  return mainApp.config(['$routeProvider' , function ($routeProvider) {    $routeProvider.when('/' , {controller: 'listCtrl' , templateUrl: '/templates/List.html'});  }]);});
You will notice I require the listCtrl, but actually use its reference. Including it adds it to my mainApp module so it can be used.
Here is my super simple controller:
javascripts/controllers/listCtrl.js
12345
define(['modules/mainApp' , 'factories/Item'] , function (mainApp) {  mainApp.controller('listCtrl' , ['$scope' , 'Item' , function ($scope , Item) {    $scope.items = Item.query();  });});
So you'll notice, I have to include that mainApp again, so I can add the controller to it. I also have a dependency on Item, which in this case is a factory.The reason I include that, is so that it gets added to the app, so the dependency injection works. Again, I don't actually reference it, I just let dependency injection do its thing.
Lets take a look at this factory really quick.
javascripts/factories/Item.js
12345
define(['modules/mainApp'] , function (mainApp) {  mainApp.factory('Item' , ['$resource' , function ($resource) {    return $resource('/item/:id' , {id: '@id'});  }]);});
Pretty simple, but again, we have to pull in that mainApp module to add the factory to it.
So finally lets look at our index.html, most if it is simple stuff, but the key part is the ng-view portion, which tells angular where to place the view. Even if you don't use document in your bootstrap and opt to use a specific element, you still need this ng-view.
index.html
123456789101112
  <!DOCTYPE html><html><head>  <title>Angular and Require</title>  <script src="/javascripts/require.js" data-main="javascripts/main"></script></head><body>  <div class="page-content">    <ng:view></ng:view>  </div></body></html>
Posted by Inline Block Jun 6th, 2013 amd, angular, angularjs, coding, javascript, requirejs

Like
Share
3 people like this. Be the first of your friends.

Sent from Evernote

No comments:

Post a Comment