• Home
  • AngularJS
  • Angular Migration Step 0 – Creating Modular Angular 1 Application

Angular Migration Step 0 – Creating Modular Angular 1 Application


This is a first part of series of posts for the topic of how to migrate Angular 1 application to Angular 2 by taking small steps at a time.

Throughout the series, I am going to use this angular-migration-tutorial-app as a base application.
You can see the final output of this step by checkout of code at step-0 tag.

git clone https://github.com/shripalsoni04/angular-migration-tutorial-app.git
git checkout step-0

If you are creating new Angular 1 application, you can skip this step and create application using hierarchical component structure introduced in Angular 1.5. Blog post of creating component style angular application, which makes migration to angular 2 somewhat easy, is coming shortly.

If you already have a considerable amount of code written in Angular 1 then continue reading.

In this first post, I am going to show you how we should write our angular 1 application, so it makes the task of migrating it to angular 2 somewhat easy.

Application Folder Structure

You need to divide your code based on the features rather than based on the type of the file (e.g controller, service, directive etc.) as shown below:

angular1-modular-app-structure

As shown in above folder structure, each feature has its own folder. Each feature folder may contain various angular specific files like controller, routes, service, module and test files.
app.module.js is a root module, which depends on all other modules of the application.

Use Module Loaders Like RequireJS or SystemJS

As we keep on adding more features to our application, it is not a good way to manually add script tags in your index.html file and to maintain their dependency manually based on the order of script tags. To avoid this manual dependency management, we can use asynchronous module loaders like RequireJS or SystemJS. To cover complete migration process, in this step-0, we are going to use requirejs, as many of the existing large projects of angular 1 may have been using requirejs instead of systemjs. If you are already using requirejs you can skip this section and go to next section.

To use requirejs in your application, first you need to create a requirejs config file as shown below:
main.js

require.config({
    paths: {
        'angular': 'bower_components/angular/angular.min',
        'angular-ui-router': 'bower_components/angular-ui-router/release/angular-ui-router.min',
        'bootstrap': 'bower_components/bootstrap/dist/bootstrap.min',
        'jquery': 'bower_components/jquery/jquery.min',
        'es6-promise': 'bower_components/es6-promise/es6-promise.min',
        'lodash': 'bower_components/lodash/dist/lodash.min',
        'angular-bootstrap': 'bower_components/angular-bootstrap/ui-bootstrap-tpls.min'
    },
    shim: {
        'angular': { 'exports': 'angular' },
        'angular-ui-router': ['angular'],
        'angular-bootstrap': ['angular']
    }
});

In this file, we are doing two things.
1. Creating alias names for the third party libraries, so that we don’t need to refer the full path whenever we want to load/reference them.
2. Defining dependency of third party modules. e.g angular-ui-router is dependent on angular. As per this shim, requirejs will load and execute code of angular-ui-router library only after angular library is loaded and executed.

Now, in your index.html file, remove all script import statements and add below one script import line.

index.html

<script data-main="main.js" src="bower_components/requirejs/require.js"></script>

Above code will load requirejs file and uses main.js for its configuration.

Write Controller, Service, Directive, Module as AMD Modules

As in angular 2, all the basic building blocks like component, service, directive, pipe etc. are just ES6 modules, it will be good if we start writing
angular 1 code also in the same fashion.

To create them as simple and reusable modules, we are going to write just a function and return it from the module. This function will behave as controller/factory/service/directive etc. depending on how we register this function with angular. All these registration will be done in a separate functionality.module.js file.

To write all basic building block of angular 1 as modules, we are going to use requirejs. So let’s first take a quick look at a simple requirejs module.

Quick Overview of RequireJS AMD Module

Below is the sample code for requirejs amd module:

define([
    'dep1',
    'dep2'
], function (dep1, dep2) {
    'use strict';

    function moduleFn() {

    }

    return moduleFn;
});

In above code first argument of define function call defines the dependencies which should be loaded and executed before the execution of the function, which is there in second argument.

Dependencies can be relative/absolute path to other requirejs modules or an alias name whose mapping is configured in main.js file.

Function in the second argument is called with the value returned by that dependencies. Whenever any other AMD module require this module, the value returned by this function will be used.

With this much understanding let’s see the code for various angular 1 building blocks.

Factory

base-service.factory.js

define([
    './base-service',
], function (BaseService) {
    'use strict';

    baseServiceFactory.NAME = 'baseServiceFactory';
    baseServiceFactory.$inject = ['$http'];
    function baseServiceFactory($http) {
        BaseService.$http = $http;
        return BaseService;
    }

    return baseServiceFactory;
})

As you know, the returned value of a factory function is used whenever it is used as dependency in some other controller,service etc. So we are just creating a function which returns something (here BaseService).

In above code, we are creating two static properties $inject and NAME

  1. $inject: It is used for defining the angular dependencies required by this factory function.
  2. NAME: It is the name by which we are going to register this function as factory with angular.

Service

project-service.js

define([
    '../shared/base-service.factory'
], function (baseServiceFactory) {
    'use strict';

    ProjectService.NAME = 'ProjectService';
    ProjectService.$inject = [baseServiceFactory.NAME];
    function ProjectService(BaseService) {
        BaseService.call(this, 'project');
    }

    return ProjectService;
});

Here in above code, only new thing is that, instead of hardcoded ‘baseserviceFactory’ in $inject, we are using the NAME property of the baseServiceFactory function.
By doing this, we have two benefits:
1. No hardcoding of service/factory names, so less maintenance while change in name of any service/factory.
2. We are actually trying to sync the angular service’s dependency with the requirejs dependency by putting '../shared/base-service.factory' as dependency for this requirejs module. This will be very useful when you want to migrate to angular 2 as in angular 2 whatever you use as dependency, you need to import them.

Controller

dashboard.controller.js

define([
   '../project/project.service' 
], function(ProjectService) {
    'use strict';

    DashboardCtrl.NAME = 'DashboardCtrl';
    DashboardCtrl.$inject = ['$state', ProjectService.NAME];  
    function DashboardCtrl($state, ProjectService) {
        var vm = this;
        vm.lstActiveProjects = [];  // list of active projects

        vm.loadActiveProjects = loadActiveProjects;

        /**
         * Loads active projects list.
         */
        function loadActiveProjects() {
            ...
        }

        function init() {
            ...
        }

        init();
    }

    return DashboardCtrl;
});

In case of controller, there isn’t anything new. We know all the stuff now, right?. If you have any doubt, kindly check the description for factory and service in above sections.

Route Configuration

dashboard.routes.js

define([
   './dashboard.controller' 
], function(DashboardCtrl) {
    'use strict';

    dashboardRouteConfig.$inject = ['$stateProvider'];
    function dashboardRouteConfig($stateProvider) {
        'use strict';

        $stateProvider
            .state('app.dashboard', {
                url: 'dashboard',
                templateUrl: 'modules/dashboard/dashboard.html',
                controller: DashboardCtrl.NAME,
                controllerAs: 'vm'
            });
    }

    return dashboardRouteConfig;
});

Instead of writing all the route configurations of the app in single file, it is preferable to create separate .routes.js file for the modules.
The above code is for routes of dashboard module. Here the code is using ui-router library. But you can use ng-route also.

Here, there are two things that we need to know:
1. Instead of hardcoding controller’s name, we are importing controller file and then using the NAME property exposed by the controller function and by doing this keeping track of actual file dependency of this route file.
2. It is preferable to use a fixed controllerAs name (like vm or $ctrl) for all the same level routes. This will help as while migrating our template files to angular 2. In angular 2, templates are executed in the context of the component class, so we don’t need to write alias name in template bindings. So if we have written same controllerAs name in template files, we just need to find and replace it by blank space while migration.

Module

dashboard.module.js

define([
    'angular',
    'angular-ui-router',
    '../project/project.module',
    './dashboard.routes',
    './dashboard.controller'
], function(angular, uiRouterModule, projectModule, dashboardRouteConfig, DashboardCtrl) {
    'use strict';

    return angular.module('app.dashboard', ['ui.router', projectModule.name])
        .config(dashboardRouteConfig)
        .controller(DashboardCtrl.NAME, DashboardCtrl);
});

This is a dashboard module file. The registration of all the building blocks of dashboard module is being done here instead of at their individual files. We should create angular modules for each of our functionality for ease of maintenance and testability.

By doing this, we are actually creating a hierarchical structure of the application, which will be very useful while migrating application to angular 2 as angular 2’s application is made up of hirarchy of components.

Registering All Modules To Root Module

Register all the top level modules to the root module from where our application bootstraps as shown below.

app.module.js

define([
    'angular',
    'angular-bootstrap',
    'angular-ui-router',
    './dashboard/dashboard.module',
    './project/project.module',
    ....
], function(angular, uiBootstrap, uiRouter, dashboardModule, projectModule, .....) {
    'use strict';

    return angular.module('app', [
        /* 3rd-party modules */
        'ui.router',
        'ui.bootstrap',

        /* Feature modules */
        dashboardModule.name,
        projectModule.name,
    ])
});

Manual Bootstrap Application With Root Module

Once you have create a root module, now its time to bootstrap this module as shown below.

index.js

define([
    'angular',
    'modules/app.module'
], function(angular, appModule) {
    'use strict';

    angular.bootstrap(document.body, [appModule.name]);
});

Execute Bootstrap File

The final step is to execute index.js file using requirejs to bootstrap our application.

index.html

....
<body>
    ....
    <script type="text/javascript">
            require(['./index'])
    </script>
</body>

This is how we can create a totally modular angular 1 application using requirejs. In next posts I will show how to move ahead from this step and create your application using components and new component router. To get updates of these posts, you can subscribe to my blog if you want.

Menu