Angular The Hard Way #1 Organise and manage domain data

Here in RedMart we use AngularJS in production for quite sometime. We have stories to tell and are pleased to share it. This episode is called "Angular The Hard Way" simply because egghead.io didn't exist at the point we started. That was not detailed developer guidelines and what is more important big and healthy community which we have today.

Even though today we have all these pieces to build our frontend in more efficient way, there is still a number of complex problems which have to be solved. Organising and managing dataflow is one of them. Let's take a look at it and try to answer a question:

"How do I organize a domain data came from backend?"

Ok, let's imagine we have simple application displaying tasks. We load domain data in controller and will simple bind a view to it.

    angular.module('task').controller('TaskCtrl', function($scope, $http) {
        $scope.tasks = [];

        $scope.getTasks = function() {
            $http.get('/api/tasks').success(function(tasks) {
                $scope.tasks = tasks;
            });
        };
    });

That works well. However, imagine we've got new requirements (which never happens) to write admin module. It will be displaying list of tasks too.

Ok, we can do it:

    angular.module('admin').controller('AdminCtrl', function($scope, $http) {
        $scope.tasks = [];

        $scope.getTasks = function() {
            $http.get('/api/tasks').success(function(tasks) {
                $scope.tasks = tasks;
            });
        };
    });

Starting from this point we have synchronization problem. If user creates task in task module we need to give update tasks in admin too.

We have two options how to solve this:

Option 1: Query everytime we initialize controller

    angular.module('admin').controller('AdminCtrl', function($scope, $http) {
        $scope.tasks = [];

        $scope.getTasks = function() {
            $http.get('/api/tasks').success(function(tasks) {
                $scope.tasks = tasks;
            });
        };

        $scope.getTasks()
    });                                                

    angular.module('taks').controller('TaksCtrl', function($scope, $http) {
        $scope.tasks = [];

        $scope.getTasks = function() {
            $http.get('/api/tasks').success(function(tasks) {
                $scope.tasks = tasks;
            });
        };

        $scope.getTasks()
    });

The code is clean and workable but makes application slower as querying takes time. As application grows you might be in a situation when loading new screen requires 10-20 queries.

Option 2: Notify admin module every time we create a task from task module (and the other way around)

    angular.module('task').controller('TaskCtrl', function($scope, $rootScope, $http) {

  $scope.tasks = [];

  $scope.getTasks = function() {
      $http.get('/api/tasks').success(function(tasks) {
          $scope.tasks = tasks;
          $rootScope.$broadcast('tasks_updated_from_task', tasks);
      });
  };

  $scope.tasksUpdatedFromAdmin = function(tasks) {
      $scope.tasks = tasks;
  };

  $scope.on('tasks_updated_from_admin', tasksUpdatedFromAdmin);
  });

    angular.module('admin').controller('AdminCtrl', function($scope, $rootScope, $http) {

  $scope.tasks = [];

  $scope.getTasks = function() {
      $http.get('/api/tasks').success(function(tasks) {
          $scope.tasks = tasks;
          $rootScope.$broadcast('tasks_updated_from_admin', tasks);
      });
  };

  $scope.tasksUpdatedFromTask = function(tasks) {
      $scope.tasks = tasks;
  };

  $scope.on('tasks_updated_from_task', tasksUpdatedFromTask);
  });

Although we queried only once our code looks messy.

And we have new problem: admin module can be uninitialized at the moment we query tasks in task module. It's possible that noone is listening for 'tasks_updated_from_task' event. In such case we have to query for tasks in admin at the moment it was initialized or load all modules in one shot.

Both options leads to increasing system complexity in a long term. Luckily we have one more way of resolving this problem:

Option 3: Use angular services for accessing domain data

angular.module('app').service('taskService', function () {
  var self = this;
  self.model = {tasks: [1, 2, 3]};

  self.loadTasks = function () {
    self.model.tasks = [1, 2, 3, 4, 5, 6];
  };
});

angular.module('task', [])
  .controller('TaskCtrl', function ($scope, taskService) {
    $scope.model = taskService.model;

    $scope.loadTasks = function () {
      taskService.loadTasks();
    };
  });

angular.module('admin', [])
  .controller('AdminCtrl', function ($scope, taskService) {
    $scope.model = taskService.model;

    $scope.loadTasks = function () {
      taskService.loadTasks();
    };
  });

Let's create view to see that approach is workable:

    <div ng-controller="TaskCtrl">
        "Task module" {{model.tasks}}
        <button ng-click="loadTasks()">Load tasks from Task module</button>
    </div>

    <div ng-controller="AdminCtrl">
        "Admin module" {{model.tasks}}
        <button ng-click="loadTasks()">Load tasks from Admin module</button>
    </div>

With this approach:

  • We are sure that our data is stored in a stateful part which simplifies data synchronisation across the application.
  • We have UI and domain-related logic nicely separated.

Stay tuned, we have more tips to share :)