Building modular apps with Angular is not an easy task. Since I arrived into an Angular project a couple of months ago I’ve been struggling with our architecture, trying to make it modular in a way that makes sense, and it hasn’t been a walk in the park.

Lets try to build an example app to see what I’m talking about. For my example we are going to have a single page app with three screens:

  • Greetings screen – Contains links to mine and yours pages
  • Mine – Shows a list of my stuff and has a link to greetings page
  • Yours – Shows a list of your stuff and has a link to greetings page

These are the parts that will make our app:

  • App module
  • Greetings controller
  • Mine controller
  • Yours controller
  • Reusable list module

If we put everything together it would look something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<html ng-app="myApp">
<body>
    <div ng-view></div>
    <script src="bower_components/angular/angular.js"></script>
    <script src="bower_components/angular-route/angular-route.js"></script>
    <script src="js/list.js"></script>
    <script>
        angular.module('myApp', ['adrian.awesomeList', 'ngRoute'])
        .config(['$routeProvider', function($routeProvider) {
          $routeProvider.otherwise({
            templateUrl: 'templates/main.html',
            controller: 'MainController'
          });
        }])
        .controller('MainController', function() {});

        angular.module('myApp')
        .config(['$routeProvider', function($routeProvider) {
          $routeProvider.when('/mine', {
            templateUrl: 'templates/mine.html',
            controller: 'MineController'
          });
        }])
        .controller('MineController', function() {});

        angular.module('myApp')
        .config(['$routeProvider', function($routeProvider) {
          $routeProvider.when('/yours', {
            templateUrl: 'templates/yours.html',
            controller: 'YoursController'
          });
        }])
        .controller('YoursController', function() {});
    </script>
</body>
</html>

I use a somewhat verbose syntax because I’m going to divide this in multiple files. For now, you can see that we are loading js/lists.js which is a reusable directive that is used in yours.html and mine.html. If we divided this into multiple files, our index.html would end up looking something like this:

1
2
3
4
5
6
7
8
9
10
11
<html ng-app="myApp">
<body>
    <div ng-view></div>
    <script src="bower_components/angular/angular.js"></script>
    <script src="bower_components/angular-route/angular-route.js"></script>
    <script src="js/list.js"></script>
    <script src="js/main.js"></script>
    <script src="js/mine.js"></script>
    <script src="js/yours.js"></script>
</body>
</html>

As you should know if you have been using angular for a while, the order in which you include the scripts matters. Because mine.js and yours.js use myApp module, main.js has to be included before.

Our app looks a little better now, but having to include all the scripts in the index page and keep them in the right order is not the right way to build web apps. Now a days dependencies are better manager by something like Require.js. Lets look at how we can use it for this scenario. The process is not super hard, but there are some things we have to keep in mind. First of all, we only need to declare one script tag for our app:

1
2
3
4
5
6
7
8
<html>
<body>
    <div ng-view></div>
    <script src="bower_components/angular/angular.js"></script>
    <script src="bower_components/angular-route/angular-route.js"></script>
    <script src="bower_components/requirejs/require.js" data-main="js/main"></script>
</body>
</html>

Now it starts becoming a little tricky. Main.js will be our entry point because it declares the main route for our app. Previously we had mine.js and yours.js depend on the myApp module and declare controllers on it. Now, because they are going to be loaded before myApp module exists we will need to make them their own module:

1
2
3
4
5
6
7
8
9
10
define(['list'], function() {
  angular.module('myApp.yours', ['adrian.awesomeList', 'ngRoute'])
  .config(['$routeProvider', function($routeProvider) {
    $routeProvider.when('/yours', {
      templateUrl: 'templates/yours.html',
      controller: 'YoursController'
    });
  }])
  .controller('YoursController', function() {});
});

Instead of declaring a route and controller in myApp module, we create a new module. Main.js will then depend on our new modules:

1
2
3
4
5
6
7
8
9
10
11
12
13
define(['mine', 'yours'], function() {
  var app = angular.module('myApp', ['ngRoute', 'myApp.mine', 'myApp.yours']);
  app.config(['$routeProvider', function($routeProvider) {
    app.$routeProvider = $routeProvider;
    $routeProvider.otherwise({
      templateUrl: 'templates/main.html',
      controller: 'MainController'
    });
  }])
  .controller('MainController', function() {});

  angular.bootstrap(document.getElementsByTagName('html')[0], ['myApp']);
});

Things look a lot better now. The myApp module depends on myApp.mine and myApp.yours. Since we don’t use adrian.awesomeList in myApp, we have moved that dependency to myApp.mine and myApp.yours.

There is one other aspect of modular web apps that Angular makes particularly difficult and that is lazy loading. I will cover lazy loading in another post.

[ design_patterns  javascript  productivity  programming  ]
Neovim as ESP32 IDE with Clangd LSP
Using Arduino Serial Monitor From Linux
Getting Started With Neovim
Dependency injection (Inversion of Control) in Spring framework
Flyway - Version control for Databases in Java