Handling data binding in AngularJS Services

2019-02-01 22:02发布

问题:

I'm trying to figure out how you handle binding properly when my data is stored in a service.

I can get things working if it put the service into the $scope and then get the templates to bind directly into it but that seems like a really bad idea.

I'd basically like to have it so that my views / controllers are able to easily change the state down in a service and have that reflected everywhere.

It feels like I should be able to do something like the following, but it doesn't work (http://jsfiddle.net/aidankane/AtRVD/1/).

HTML

<div ng-controller="MyCtl">
    <select ng-model="drawing" ng-options="d.file for d in drawings"></select>
</div>
<div ng-controller="MyOtherCtl">
    {{ drawing }}
</div>

JS

var myApp = angular.module('myApp', []);

myApp.factory('myService', function(){
    var me = {
        drawings: [{'file':'a'}, {'file':'b'}]
    };
    // selected drawing
    me.drawing = me.drawings[0];
    return me;
});

function MyCtl($scope, myService){
    // can do:
    // $scope.mys = myService;
    // and then in html ng-model="mys.drawing"
    // but that seems wrong

    $scope.drawings = myService.drawings;
    $scope.drawing = myService.drawing;

    // can I not do this? it doesn't seem to work anyway...
    $scope.$watch('drawing', function(drawing){
        myService.drawing = drawing;
    });
}

function MyOtherCtl($scope, myService){
    $scope.drawing = myService.drawing;
}

MyCtl.$inject = ['$scope', 'myService'];
MyOtherCtl.$inject = ['$scope', 'myService'];

回答1:

You can bind to services using $watch and passing a function:

$scope.$watch( function () { return myService.drawing; }, function ( drawing ) {
  // handle it here. e.g.:
  $scope.drawing = drawing;
});

And then use $scope.drawing in your templates and they will automatically update:

<div ng-controller="MyOtherCtl">
  {{ drawing }}
</div>


回答2:

I think, even more elegant is to work with promises (see $q.deferred()) and to resolve them asynchronously. In the promise function you can then assign the data to $scope's members.



回答3:

There are two ways to do bind the data from a service: 1) By value (will require a watcher as done above to check for variable changes to the service primitive value) 2) By reference (values are directly linked) which is my preferred method of data binding.

I will only explain the second possibility since the accepted answer already shows how a watcher can be implemented. This blog describes what I am going to explain very well.

I created this plunk to illustrate data binding by reference.

Here is the code from the plunk:

HTML

<!DOCTYPE html>
<html ng-app="myApp">

  <head>
    <script data-require="angularjs@1.5.0" data-semver="1.5.0" src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.0/angular.js"></script>
    <link rel="stylesheet" href="style.css" />
    <script src="script.js"></script>
  </head>

  <body ng-controller="myAppCntrl">
    <h1>Hello Plunker!</h1>
    <h3>By value</h3>
    <p>{{byValue}}</p>
    <p>{{objByValue}}</p>
    <h3>By object in service reference</h3>
    <p>{{byRefence.stringPrimitive}}</p>
    <h3>By reference to service singleton</h3>
    <p>{{myservice.stringPrimitive}}</p>
    <p style="color: green">of course, you can reference an object through the service as well</p>
    <p>{{myservice.objectWithPrimitive.stringPrimitive}}</p>

    <button ng-click=update()>Update strings on service</button>
    <br />
    <button ng-click=setDefaults()>Restore Defaults</button>
  </body>

</html>

JAVASCRIPT

var myApp = angular.module("myApp", []);

myApp.controller('myAppCntrl', function($scope, myAppService){
  $scope.myservice = myAppService;
  $scope.byValue = myAppService.stringPrimitive;
  $scope.objByValue = myAppService.objectWithPrimitive.stringPrimitive;
  $scope.byRefence = myAppService.objectWithPrimitive;

  $scope.update = function () {
    myAppService.stringPrimitive = "updated string";
    myAppService.objectWithPrimitive.stringPrimitive = "updated string here too";
  };
  $scope.setDefaults = function () {
    myAppService.stringPrimitive = 'string primitive';
    myAppService.objectWithPrimitive.stringPrimitive = 'string primitive';
  };
});

myApp.service('myAppService', function(){
  this.stringPrimitive = 'string primitive';
  this.objectWithPrimitive = {
    stringPrimitive: 'string primitive'
  };
});

So how does this work?

It is important to understand that this has little to do with how angular works, and a lot to do with how Javascript works. When a variable is set equal to a primitive value (or passed to a function) in javascript (integer, string, etc.) the var is set by value. This means that the the new variable is a copy of the variable you are setting it equal to with a new location in memory. When a variable is set equal to an object (or passed to a function) in javascript the var is set by reference.

What does this mean?

When a $scope var is set by value, and the service variable changes, since the $scope variable is only a copy of the service variable, the service variable no longer has anything to do with the service variable and will not change when the service var does.

When the $scope var is set equal to and object, it is assigned by Reference. This means that when the service object (note that the service is an object since it is instantiated with the new keyword and you can refer to that object with "this" inside the service) or any objects on the service that are referenced are changed, any $scope variable that reference those objects will update as well.