Angular + Require project template

I've been working on an Angular project for good number months. There's a lot that I've learned and I've realized that the code I wrote could have been structured in a better way. I wanted to write this post sometime back, however I thought it would be better to create an application structure first and then post my learnings. I have posted all the code on github here - https://github.com/sagar-ganatra/angular-require-template. The template is still a work in progress, however the basic structure of the template is in place.


Motivation:
I always visualized a web page broken down into multiple components that are completely decoupled and components that can be reused. For example, a component or a widget that displays a chart does not have to know about the navbar in the page. It would however provide an interface through which external components would interact or change the state of it.

Why RequireJS?
In my Angular application's index.html file, I had included various script blocks that looked like this:

<script src="../bower_components/jquery/jquery.js"></script>
<script src="../bower_components/angular/angular.js"></script>
<script src="../bower_components/angular-bootstrap/ui-bootstrap-tpls.js"></script>
<script src="../bower_components/angular-cookies/angular-cookies.js"></script>
<script src="../bower_components/angular-route/angular-route.js"></script>

Here the script tags are included in such a way that the dependent files come first and then the others are listed. Every time I added a new script file in the project, I had to update the index.html file and ensure that the dependencies are always included first. Instead, I could use a RequireJS shim to declare the dependencies:

shim: {
    angular: {
        deps: ['jquery'],
        exports: 'angular'
    },
        
    angularMocks: {
        deps: ['angular']  
    },
        
    angularCookies: {
        deps: ['angular']
    },
        
    uirouter: {
        deps: ['angular']
    }
}


This is more declarative and less prone to errors.

This is not the only reason I would use RequireJS in my project, one of the other factors and the most important one is to provide structure to the application. I want my components to be independent or individual widgets that can be reused. When creating components I would make use of the directive interface which can then be included anywhere in the project. For example, the interface for a navbar could look like:

<navbar title="{{title}}"></navbar>

Here the navbar element has the title attribute which would then become a scope variable for the navbar directive. Essentially everything on the page would be a collection of directives which would then be compiled and the resultant HTML markup would be injected into the DOM:


Here the directive serves as a gateway to the service that we are trying to add to the application. A directive is an individual widget which is not aware of other directives used in the page and it's service agnostic. Here in our example, the playersList directive would consume list of players and perform some action when a player is selected. The directive would provide an interface and the page that wants to use the directive can then provide the data:

<div players-list
     players="players"
     on-player-select="showPlayerStats(playerId)"
     selected-player="selectedPlayerId">
</div>

Here the values provided to various attributes are defined in the controller scope. In this way, the directive remains agnostic of the data or the service that it should talk to.

In my project template, I've create a module for each directive. In this example, we would have two modules - navbarModule, playersStatsModule. I would include playersList and playerDetails directive into one single module because they serve as one entity. However, they are completely decoupled and don't interact with each other directly. The idea is to create modules that represent one single entity, which in this case is playerStats. The module definition would look like this:

define([
    'angular',
    'components/playerStats/directives/playersListDirective',
    'components/playerStats/directives/playersDetailDirective',
    'domain/Players'
],  function(ng, playersListDirective, playersDetailDirective, Players) {
        'use strict';
        
        return ng.module('playerStatsModule', [])
                 .directive('playersList', playersListDirective)
                 .directive('playersDetail', playersDetailDirective) 
                 .service('Players', Players);
                 
    }
);

Notice that there's one file playerStatsModule.js which declares dependency on the directives and the service. Also, the name is to the directive here in the module definition. This approach is the flip side of the approach that is generally followed when defining directives. Generally one would create a module first and then use the module reference in their directive definition; but the approach that I mentioned above makes the code more cleaner. Here's how the definition of the playersList directive would look like:

define(function() {
    'use strict';
    
    return function () {
        return {
            restrict: 'AE',
            templateUrl: './components/playerStats/partials/playersList.html',
            scope: {
                onPlayerSelect: '&',
                players: '=',
                selectedPlayer: '='
            }
        };
    };
});

It returns a function and then in the module a name is assigned to the directive.

Application structure:

/app
  - app.js
  - main.js
  - bootstrap.js
  - index.html
  /components
      /auth
      /login
      /navbar
      /playerStats
           /directives
                - playersDetailsDirective.js
                - playersListDirective.js
           /partials
                - playersDetails.html
                - playersList.html
  /domain
       -  Players.js
       -  User.js
  /modules
        - authModule.js
        - loginModule.js
        - navbarModule.js
        - pagesModule.js
        - playerStatsModule.js
  /pages
      /home
          /controllers
               - playersListController.js
               - playersStatsController.js
          /partials
               - playersList.html
               - playersStats.html
      /login

Notice that the services responsible for sending requests to the server are listed under domain. The pages directory lists various pages in the application and the partials listed under each page contain references to these components. In this way, pages handle the application logic (events, querying the service, etc) and individual component directives handle rendering logic.

In app.js, a module for the application is created. This module will declare dependency on various other modules in the source code in addition to third party modules:

define([
    'angular',
    /*mock module*/
    'mockModule',
    'mockServices',
    /* user defined modules */
    'modules/authModule',
    'modules/loginModule',
    'modules/navbarModule',
    'modules/playerStatsModule',
    /* pages module */
    'modules/pagesModule',
    /* include vendor dependencies here */
    'angularCookies',
    'angularMessages',
    'uirouter',
    'lumx',
], function (ng) {
    'use strict';
    
    var app = ng.module('playersApp', [
        'ngCookies',
        'ui.router',
        'lumx',
        'mockModule',
        'authModule',
        'loginModule',
        'navbarModule',
        'playerStatsModule',
        'pagesModule'
    ]);
});

The main.js file declares the shim configuration (mentioned before) and bootstrap.js would bootstrap the application by calling angular.bootstrap on the document object.

Other Goodies:

I'm taking help from lumx to implement Material design, Angular UI router to define states/routes in the application, Angular Mocks to mock services and Gulp for build.

Pending:

Unit testing using Jasmine and Karma.
Structure styles (CSS/SCSS).
Refine Gulp build system.
CI/travis.yaml

You can clone the repository and see how the application is structured and how it is currently styled.


Comments

  1. This is a Great Article ever i see... thank's for sharing :D

    Cara Bermain Judi

    ReplyDelete

Post a Comment

Popular posts from this blog

Custom validation messages for HTML5 Input elements using the constraint validation API

JavaScript debugging with Chrome Developer Tools and some tips\tricks

File upload and Progress events with HTML5 XmlHttpRequest Level 2