CjS
CjS

Reputation: 2047

What's the best practice for binding service data to views in Angular (1.3)

I'm new to Angular, but have a fair amount of experience in coding and am having some trouble getting data binding in views "right".

My approach is broadly:

The confusion I'm having is that not having an explicit $scope object obfuscates the model in the MVC philosophy. There was a passing comment to this effect in the blog post, but I didn't see any real conclusion on the matter.

The Angular Docs state that the model is "the single point of truth" for data. Perhaps I read to much into this (is it just the "truth" of values between the V and C?) but this suggests that, if you're using ControllerAs syntax, that the controller properties are the model -- and the "single point of truth".

If that's a given, and you want to abstract all logic into services, then as I see it, you can't get away from one of two pretty ugly alternatives for keeping the controller properties in sync with the service values:

  1. Expose the service to the view. i.e. myController.data = service.data. This works, but is pretty gross, because all the service data is exposed to the view, and isn't the point of these thin controllers to act as a facade to the view? Or to look at it another way, the service has now become a de facto 'fat' controller, full of business logic.
  2. Expose only the elements from the service that you want the view to access, but put $watch expressions on each of them. This is not ideal because
    • You add a lot of boilerplate to your controllers
    • You add significantly to the number of watch expressions in your code (angular puts a watch on the template <-> controller link, and you add the controller <-> service link; for each variable in )

Are these really the only ways of doing this in Angular 1.3? Or is there something (more elegant) that I've missed?

Edit: Based on the answers and some additional investigations, I've collected my thoughts on the question in this plunkr. It covers 3 ways (IMHO) not to do service binding and two ways that are probably closer to the 'right' approach.

Upvotes: 2

Views: 683

Answers (1)

Michael Kang
Michael Kang

Reputation: 52867

Services return models. Controllers interact with services to populate the view model ($scope). The view interacts with the view model to render the view.

To keep data in-sync, you have a few options available.

Option 1: Introduce a factory or service whose job is to track instances - like a caching service. Any controller that needs an instance for binding to the view can ask for it from the service. This is clean - no need for explicit watch expressions, or non-specific model binding.

Here is an example. Suppose you want to bind to a User object across multiple controllers.

Create a User Service

app.factory('UserService', function() {
    return {
        getUserById: function(id) {
           ...
           return user;
        }
    }
});

Create a Cached User Service

app.factory('CachedUserService', function(UserService) {
     var cache = [];
     return {
         getUserById: function(id) {
             if (!cache[id])
                 cache[id] = UserService.getUserById(id);                    
             return cache[id];                 
         }
     }
});

Here, we are leveraging singletons (which all factories and services are), and relying on the Caching service to preserve the model reference. The latter point is essential for making sure that view changes are in sync with model changes.

Use the Cached User Service in any controller that requires it:

app.controller('ctrl', function($scope, CachedUserService) {
     $scope.user = CachedUserService.getUserById(...);
});

Option 2: Simply store the model that you would like to keep in sync, higher up the $scope chain (i.e. $rootScope)

app.controller('ctrl', function($scope, $rootScope, UserService) {
      $rootScope.user = UserService.getUserById(...);
} 

Any child scopes are able to bind to the model higher up the scope chain because of prototypical scope inheritance. Take care, however, when you assign or overwrite models on $scope - you may inadvertently break the model binding. To preserve the reference, use angular.copy instead.

Upvotes: 2

Related Questions