AngularJS - DRY two-way data binding using controllerAs syntax and service properties

I came across a problem that should be general and obvious, but I cannot circle it.

I am working on a small prototype application. My third-party developer provides me with profile data in a JSON object. Let's say it looks like this:

profile = {Name: 'John', Email: ' john@mail.com ', DOB: '1980-11-03'} 

I need these values ​​in several places, and I also don't want to put backend http calls into controllers, so I created a service to handle this:

 angular.module('app', []) .service('ProfileService', ['$http', function ($http) { var service = this; service.Name = null; service.Email = null; service.DOB = null; service.getProfile = function () { return $http.get('/profile').then(function (response) { service.Name = response.data.Name; service.Email = response.data.Email; service.DOB = response.data.DOB; return true; }); }; return service; }]) .controller('ProfileCtr', ['ProfileService', function (service) { var vm = this; service.getProfile().then(function () { vm.Name = service.Name; vm.Email = service.Email; vm.DOB = service.DOB; }); }]); 

There are a number of problems with this solution:

  • Since profile data consists of primitives, direct binding to service properties will not allow automatic data synchronization.
  • More importantly, it breaks down the DRY concept , as I wrote data declarations in at least 3 different places (database schema in getProfile () and in the controller).

One solution would be to add an indirect layer and create an object inside the service:

 angular.module('app', []) .service('ProfileService', ['$http', function ($http) { var service = this; service.profile = {}; service.getProfile = function () { return $http.get('/profile').then(function (response) { for (key in response.data) { service.profile[key] = response.data[key]; }; return true; }); }; return service; }]) .controller('ProfileCtr', ['ProfileService', function (service) { var vm = this; service.getProfile().then(function () { vm.profile = service.profile; }); }]); 

This works in general, but now I get the awkward controllerAs syntax:

 <div ng-controller="ProfileCtr as ctr"> <h1> {{ ctr.profile.Name }}</h1> <p> Email: {{ ctr.profile.Email }} <br /> DOB: {{ ctr.profile.DOB }}</p> </div> 

I am wondering if there is a way that gives me both: pure HTML syntax {{ ctr.Name }} and a stylish programming style.

Thanks for any tips!

+5
source share
1 answer

I have a feeling that you want more than that, but this is at least DRY for me:

 angular.module('app', []) .service('ProfileService', ['$http', function ($http) { var service = this; service.getProfile = function () { return $http.get('/profile').then(function (response) { return response.data; }); }; return service; }]) .controller('ProfileCtr', ['ProfileService', function (ProfileService) { var vm = this; ProfileService.getProfile().then(function (profile) { vm.profile= profile; }); }]); 

The service is receiving data. You can also add features for caching. The controller uses the service to receive data. Repeated code does not exist.

I like to use the $scope variable, which will remove one level of the indirectness problem. However, the As controller has its own advantages, especially if you use nested controllers and want to make it clear which controller you are using. And the identifier $scope will be removed in version 2.

Using the directive for this html section instead of the controller should make the code easier to read and reuse. It is also recommended that you prepare it for upgrade to version 2.

Then:

 app.directive('isolateScopeWithControllerAs', function () { var controller = ['ProfileService', function (ProfileService) { var vm = this; ProfileService.getProfile().then(function (profile) { vm.profile= profile; }); }]; return { restrict: 'EA', //Default for 1.3+ controller: controller, controllerAs: 'vm', bindToController: true, //required in 1.3+ with controllerAs templateUrl: // path to template }; }); 

Then your HTML still gives you:

  <h1> {{ vm.profile.Name }}</h1> <p> Email: {{ vm.profile.Email }} <br /> DOB: {{ vm.profile.DOB }}</p> 

ProfileCtr as vm comes in handy if you use the directive for multiple objects. For example, if you have a custom directive, you might have:

 controllerAs: 'user', 

with user.profile.name and ng-repeat='friend in user.friends' etc.

+4
source

All Articles