4

Can anyone give me some advice on the pros / cons of using events heavily to keep different parts of an Angular app in sync with each other?

I am considering using a series of events and event listeners to wire together an SPA with multiple concurrent and nested views, provided by Angular's ui-router. The layout of the SPA could be analogous to gmail, where there are logically separate regions of the screen (left area with list of mail folders, top navbar, main details view, etc).

I have been looking at ways to keep all the controllers in sync with regards to models they all need to access at the same time. When a model is updated by the top view, if some of that model is displayed in the main view, I want the main view to notice the change, and update its view accordingly.

I use services to share models across controllers, as is considered the best practice, but there are issues with that approach as discussed in depth in this question.

A possible solution to this is to use Angular's $broadcast and .$on('event, fn()) to have controllers notice when a service they care about has updated data.

Problems:

  1. Using events to pass data instead of using services (tight coupling, ignoring any authoritative data source). I know you can pass object with events, but you don't have to.

  2. Performance (events bubble throughout $scope hierarchy, etc).

    $broadcast/$emit have performance and tight-coupling concerns that are covered a-plenty here and elsewhere, and I am considering using solution from an SO answer here to address performance / memory leaks from not cleaning up listeners when controllers are destroyed.

  3. Maintenance (spaghetti code / ignoring OO and just passing your entire app's model around in events). This is the main question I have for the community: based on the (ugly) diagram below, I imagine each angular .service firing an event when it gets updated model data, and any controllers who care about that data just listen for the event to fire: Event Wiring Diagram

It's actually simple to code (works now), but I worry about the 5-years-from-now headache of trying to keep track of all the event relationships.

Does anyone have any experience with a design like this, who can give me some advice on a smarter / more concise design?

Thanks in advance, and sorry for the crude diagram... hopefully its gets across the point.

Community
  • 1
  • 1
tengen
  • 2,125
  • 3
  • 31
  • 57

3 Answers3

2

Pros?

  • It makes angular more like Knockout/Durandal?

Cons?

  • It makes angular more like Knockout/Durandal?

I've answered one of the questions you linked: here So that might help you.

Which might also answer the question in your comment.

Also my advice is to not abandon OO. But embrace it, angular will then reward you by working all of the time.

Had you in the previosly linked question Had a User object with a password field you would have never had the issue Because the password would always have been reference-able through the user. And all of the bindings to User.password would have worked.

So only ever have one source of data. Keep that reference. And your two way binding will modify that data. You shouldn't have to worry about propagating it throughout your app in events.

Community
  • 1
  • 1
calebboyd
  • 5,744
  • 2
  • 22
  • 32
  • Once again you are right... thanks for the help. As I mention in my comment to @stofl, often times I will need to validate data entered by a user before I allow the model held by the service to be updated. So do I operate on a copy of the model (like `angular.copy()`) and then update the model held by the service when I'm satisfied with the changes? – tengen Jan 29 '14 at 17:06
  • Yeah, That's great, Like my comment on your other question, That is generally referred to as a 'Work in Progress' or a 'WIP' – calebboyd Jan 29 '14 at 17:10
2

Because this is too long, to put it into a comment reply, I post it as an answer:

If you want to validate user input and hold the state (or the shared parts of it or the domain model) in services, I would put the viewmodel of the form only in the controller and call a service method to update the model. This method would return an array of validation errors:

$scope.errors = myModel.update($scope.formFields);

Or in the case, the user would have to click a button to submit the form:

$scope.$watch('formFields', function (values) {
    $scope.errors = myModel.validate(values);
});
$scope.submit = function () {
    $scope.errors = myModel.update($scope.formFields);
    if ($scope.errors.length < 0) {
        $scope.formFields = {};
        $scope.showForm = false; 
    }
};

To avoid a direct access to the domain model, you could hold this data in the service and only provide methods to access the data.

The service:

var items = {};
return {
    'getItems': function () {
        return items;
    }
};

In the controller:

$scope.myList = myListModelService;

In the views:

<li ng-repeat="item in myList.getItems()">

Of course, this allows the developer, to modify the returned objects/arrays directly ($scope.myList.getItems().push(newItem);). Here you would have to decide, to either declare a convention: "Never update model objects directly; ever use the APIs of the model services" or to blow up the users memory and let the model services provide (deep) clones of the data objects. In the latter case, you would also have to make a convention: "Model service getters must ever provide clones." But maybe the model is developed by other developers, then the controllers or views and the this could be easier covered by unit tests - if there is a convention to test, if model service getters provide clones :).

Advantage of this approach is, that you can use the getter functions in Angular expressions and in $watch statements and you don't have to deal with events.

stofl
  • 2,950
  • 6
  • 35
  • 48
  • Thanks for the great explanation. I had not considered using the service as an API for validating the domain model as well... outdated mentality I guess. I really appreciate you taking the time to expound for me - this was very helpful! – tengen Jan 29 '14 at 18:02
0

From Mastering web application with angularjs

In the entire AngularJS framework, there are only three events being emitted ($includeContentRequested, $includeContentLoaded, $viewContentLoaded), and seven events being broadcasted ($locationChangeStart, $locationChangeSuccess, $routeUpdate, $routeChangeStart, $routeChangeSuccess, $routeChangeError, $destroy). As you can see, scope events are used very sparingly and we should evaluate other options (mostly the two-way data binding) before sending custom events.

Whisher
  • 31,320
  • 32
  • 120
  • 201
  • This validates my concern about custom events. It is the other options I'm interested in getting some perspective on. Is there a way to get 2-way data binding (n-way data binding?) working between multiple controllers and a service? – tengen Jan 28 '14 at 21:21
  • 1
    Yes, you can pull the service object into your controllers scope, as described here: http://joelhooks.com/blog/2013/04/24/modeling-data-and-state-in-your-angularjs-application/ – stofl Jan 29 '14 at 12:39
  • 1
    This is also described here: https://egghead.io/lessons/angularjs-sharing-data-between-controllers – stofl Jan 29 '14 at 13:04
  • @stofl The thing I don't like about the direct binding is that often times I will need to validate data entered by a user before I allow the model held by the service to be updated. So do I operate on a copy of the model (like `angular.copy()`) and then update the model held by the service when I'm satisfied with the changes? – tengen Jan 29 '14 at 17:05
  • I've tried to give an answer in a separate "Answer", because the length of comments is restricted. – stofl Jan 29 '14 at 17:57