Search code examples
angularjsprimitive-typesangular-servicesreference-type

AngualrJS: Why can only bind to reference values but primitive values of services?


I have created a plunker to test binding $scope on services. And I found that I can only bind to primitive values but reference values of a service.

<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.7/angular.min.js"></script>
<body ng-app="BindToService">

    <div ng-controller="BindToServiceCtrl as ctrl">
        // I wonder why I could only bind on reference but primitive values of a service.
        counterFactoryObj: {{counterFactoryObj}}<br/>
        counterFactoryVal: {{counterFactoryVal}}<br/>
        counterFactoryFun(): {{counterFactoryFun()}}<br/>
    </div>

    <script type="text/javascript">
        var app = angular.module("BindToService", []);

        app.controller("BindToServiceCtrl", function ($scope, CounterFactory) {
          $scope.counterFactoryObj = CounterFactory.obj;
          $scope.counterFactoryVal = CounterFactory.val;
          $scope.counterFactoryFun = CounterFactory.fun;
        });

        app.factory("CounterFactory", function ($interval) {
            var obj = [0];
            var val = 0;
            var fun = function () { return val; };

            var addCounter = function () {
                obj[0] += 1;
                val += 1;
            };

            $interval(addCounter, 1000);

            return {
                obj: obj,
                val: val,
                fun: fun
            };
        });
    </script>
</body>

In this demo, only counterFactoryObj and counterFactoryFun() change by time. I wonder how AngularJS binds values.


Solution

  • There are a few things here that I think are causing confusion.

    First, when you return an object with:

    return {
      val: someVal
    }
    

    the property val of the object is set at the time you return it to whatever the value was of someVal. So, when you return your CounterFactory service, CounterFactory.val === 0 - always - and it's not related to Angular's binding.

    Second, with CounterService service, your CounterService instance has this.val and in fact it is being updated with $interval (try it with console.log). The problem there is that you assign its value in a particular point in time (e.g. when controller function runs) to the $scope.counterServiceVal variable. Here, although CounterService.val changes, the following remains true: $scope.counterServiceVal === 0.

    Btw, both .service and .factory return a singleton service instance as far as Angular is concerned, except in one your return a new-able Function and in the other - the actual instance, but that is just related to what Angular does to obtain the actual service instance - after the service is injected it behaves the same way.

    And, finally, if you want to bind to a value - you can, but you need to actually change that value and bind to that changing variable. Here's how you'd do this with your CounterFactory, as an example:

    app.factory("CounterService", function ($interval) {
        var svc = { val: 0 };
    
        function addCounter() {
            svc.val += 1;
        };
    
        $interval(addCounter, 1000);
    
        return svc;
    });
    
    // in controller
    $scope.counterService = CounterService;
    
    <!-- in the view -->
    <span>{{counterService.val}}</span>