AngularJS - splitting an application into modules and loading components using RequireJS

Using AngularJS in tandem with RequireJS is a fairly popular approach to developing web applications recently. And one of the main issues is the structure of the application. There is a fairly well-known seed for such an application, tnajdek / angular-requirejs-seed , but it does not suit me, because with an increase in the application's functionality, this structure will simply clog a bunch of files, there will be no logical separation of scripts and it will be difficult to manage them.

The goal was to create an application with a modular and flexible architecture (well, rather just breaking the application is not logical parts), with a simple and clear description of the dependencies between the parts of the application and reduce the dependence of the code on the structure of the application.

Module


In this case, this is a logically separate part of the application, which includes a set of components:
  • ngModule;
  • Controller
  • FIlter
  • Directive
  • Service
  • Template;
  • Configs - contain the config () and run () methods for the current ngModule.




Problem


When using RequrieJS, application files are most often connected somehow like this:

require('modules/foo/controller/foo-controller.js');
require('modules/foo/service/foo-service.js');
require('modules/foo/directive/foo-controller.js');
require('text!modules/foo/templates/foo.html');
require('modules/bar/directive/bar-controller.js');


There are obvious disadvantages:
  • The code is very dependent on the structure of the project;
  • The code is very dependent on the names of the modules;
  • Quite a lot you need to write with your hands.


Decision


RequireJS plugins for loading module components were written.

For example, there is such an application structure (by the way, very similar to the structure of bundles in Symfony2):
app
   | -modules
   | | -menu
   | | | -controller
   | | | | -menu-controller.js
   | | | -menu.js    
   | |    
   | | -user
   | | -controllers
   | | | -profile.js
   | | -resources
   | | | -configs
   | | | | -main.js
   | | |
   | | | -templates
   | | | | -user-profile.html
   | | | -directives
   | | | -user-menu
   | | | -user-menu.js
   | | | -user-menu.html
   | | -src
   | | | -providers
   | | | | -profile-information.js
   | | | -factory
   | | | -guest.js
   | | -user.js
   |
   | -application.js
   | -boot.js


In this case, we have 2 modules: user and menu . Files /app/modules/menu/menu.js and /app/modules/user/user.js are scripts with initialization of angularJS modules. Everything else - I think it’s clear.

Now you need to configure the connection of all components. This is done using requirejs.config :

requirejs.config({
  baseUrl: '/application',
  paths: {
    'text': '../bower_components/requirejs-text/text',
    // Structure plugins:   
    'base': '../bower_components/requirejs-angular-loader/src/base',
    'template': '../bower_components/requirejs-angular-loader/src/template',
    'controller': '../bower_components/requirejs-angular-loader/src/controller',
    'service': '../bower_components/requirejs-angular-loader/src/service',
    'module': '../bower_components/requirejs-angular-loader/src/module',
    'config': '../bower_components/requirejs-angular-loader/src/config',
    'directive': '../bower_components/requirejs-angular-loader/src/directive',
    'filter': '../bower_components/requirejs-angular-loader/src/filter'
  },
  structure: {
      prefix: 'modules/{module}',
      module: {
        path: '/{module}'
      },      
      template: {
        path: '/resources/views/{template}',
      },
      controller: {
        path: '/controllers/{controller}'
      },
      service: {
        path: '/src/{service}'
      },
      config: {
        path: '/resources/configs/{config}'
      },
      directive: {
        path: '/resources/directives/{directive}/{directive}'
      },
      filter: {
        path: '/resources/filters/{filter}'
      }
    }
});


All paths of each component are defined within the module. The structure.prefix field is the path to the module root, after baseUrl .

Now, if we want to include the file /app/modules/user/user.js из:
1. /app.js :
require('module!user')


2. /app/modules/user/controllers/profile.js :
require('module!@')

Within one module - the name of the module can be omitted, the '@' character is enough. Thus, if you have to rename the module - you will not need to change the code.

Now, if we want to include a file /app/modules/user/controllers/profile.jsfrom:
1. /app.js :
require('controller!user:profile')

Before the colon - the name of the module, after the colon - the name of the controller.

2. /app/modules/user/user.js :
require('controller!profile')

Within the framework of one module - the name of the module can be omitted, just specify the name of the controller. Also, if the controller is one level lower, it is possible to connect as follows:
require('controller!additional/path/to/profile')


Similarly, for all other components.

Result


It turned out to be a very flexible application structure with support for dividing code into modules and with minimal code dependence on the project structure, even if you have to transfer any component from one module to another, you will not have to change anything. And there is less code too.

Also, there are no problems when resetting a project. In the test application there is an example of the assembled project in the / build folder and the Gruntfile for assembly, but there is nothing unusual in it.

References:


We use this approach in a large corporate application, the support and development of this approach will be supported and developed.

Also popular now: