11

I am writing an AngularJS application in which one array (a list of active work orders) is used in many different controllers. This array is refreshed periodically using an $http call to the server. In order to share this information I've placed the array in a service.

The array in the service starts out empty (or, for debugging purposes, with dummy values ["A", "B", "C"]) and then queries the server to initialize the list. Unfortunately, all my controllers seem to be bound to the pre-queried version of the array -- in my application all I see are the dummy values I initialized the array with.

My goal is to bind the array into my controllers in such a way that Angular will realize that the array has been updated and cause my view to re-render when the array changes without my having to introduce a $watch or force my $http call to wait for results.

Question: How do I bind the wo_list array from my service to my controller so that Angular treats it like a regular observable part of the model?

I have:

  1. A service that contains the array and a refresh function used to initialize and periodically update the array from the server (I know this is working by the console.log() message). For debugging purposes I'm initializing the array with the placeholders "A", "B", and "C" -- the real work orders are five digit strings.

    angular.module('activeplant', []).
       service('workOrderService', function($http) {
    
       wo_list = ["A", "B", "C"]; //Dummy data, but this is what's bound in the controllers.
    
       refreshList = function() {
             $http.get('work_orders').success(function(data) {
                 wo_list = data;
                 console.log(wo_list) // shows wo_list correctly populated.
             })
       }
    
       refreshList();
    
       return {
    
           wonums: wo_list,  // I want to return an observable array here.
    
           refresh:  function() {
             refreshList();
           }
    
       }
     })
    
  2. A controller that should bind to the array in workOrderService so that I can show a list of work orders. I'm binding both the service and the array returned by the service in two different attempts to get this to work.

    function PlantCtrl($scope, $http, workOrderService) {
      $scope.depts      = []
      $scope.lastUpdate = null
      $scope.workorders = workOrderService
      $scope.wonums     = workOrderService.wonums  // I want this to be observable
    
      $scope.refresh = function() {
          $scope.workorders.refresh()
          $http.get('plant_status').success(function(data) {
              $scope.depts = data;
              $scope.lastUpdate = new Date()
          }); 
      }
    
      $scope.refresh()
    
    }
    
  3. A view template that iterates over the bound array in the plant controller to print a list of work orders. I'm making two attempts to get this to work, the final version will only have the ul element once.

    <div ng-controller="PlantCtrl">
      <div style='float:left;background-color:red' width="20%">
          <h2>Work Orders</h2>
          <ul>
             <li ng-repeat="wonum in workorders.wonums"><a href=""> {{wonum}} </a></li>
          </ul>
          <ul>
              <li ng-repeat="wonum in wonums"><a href=""> {{wonum}} </a></li>
          </ul>
      </div>
    </div>
    
  4. Several other view / controller pairs, not shown here, that also bind and iterate over the array of work orders.

Larry Lustig
  • 49,320
  • 14
  • 110
  • 160

2 Answers2

16

See if this solves your problem:

Instead of wo_list = data, populate the same array. When you assign a new data array to wo_list, you lose the bindings to the old array -- i.e., your controllers are probably still bound to the previous data array.

wo_list.length = 0
for(var i = 0; i < data.length; i++){
    wo_list.push(data[i]);
}

See https://stackoverflow.com/a/12287364/215945 for more info.

Update: angular.copy() can/should be used instead:

angular.copy(data, wo_list);

When a destination is supplied to the copy() method, it will first delete destination's elements, and then copy in the new ones from source.

Community
  • 1
  • 1
Mark Rajcok
  • 362,217
  • 114
  • 495
  • 492
  • Thank you, this solution worked for preserving the observability of the array. However, I noticed that you also commented on yoxigen's answer. The nested controller solution strikes me as more elegant (and it eliminates the need for the service) -- do you have an opinion on which of the two solutions is "better"? – Larry Lustig Jan 15 '13 at 17:03
  • @Larry, a service is better. To share data you shouldn't have to structure your HTML/controllers a certain way. "It is considered bad form for two controllers to share information via $scope inheritance." -- [Lukas blog](http://onehungrymind.com/angularjs-sticky-notes-pt-1-architecture/) The only time I might rely on/use a scope or controller hierarchy is for something like dependent directives -- e.g., a "pane" directive would require a "tabs" directive to be "above" it in the HTML. – Mark Rajcok Jan 15 '13 at 17:10
2

You can put the array which all the controllers use in a parent controller. Since scopes inherit from higher scopes, this'll mean that all the controllers have the same copy of the array. Since the array is a model of the higher scope, changing it will update the view, whenever it's used.

Example:

<div ng-controller="MainControllerWithYourArray">
    <div ng-controller="PlantCtrl">
         Wonums length: {{wonums.length}}
    </div>
    <div ng-controller="SecondCtrl">
         Wonums length in other controller: {{wonums.length}}
    </div>
</div>

You only need to define the wonums array in the MainController:

function MainControllerWithYourArray($scope, workOrderService){
    $scope.wonums = workOrderService.wonums;
}

Hope this works for you!

yoxigen
  • 123
  • 1
  • 6
  • I'm not familiar with the concept of a "parent controller" and "higher scopes" in Angular (obviously, I'm just starting out). Is it the nesting of `div`s in the HTML that creates the parent/child and higher/lower relationship? – Larry Lustig Jan 15 '13 at 16:47
  • @Larry, it is the nesting of ng-controllers that causes the parent-child scopes. For more on this topic, see http://stackoverflow.com/questions/14049480/what-are-the-nuances-of-scope-prototypal-prototypical-inheritance-in-angularjs – Mark Rajcok Jan 15 '13 at 16:54
  • @MarkRajcok, a comment for your comment below - scope inheritance exists exactly for that reason, to make models hierarchical. Services are business logic units, not data units, so I think it's better to put your data in scopes, who also manage models state. – yoxigen Jan 15 '13 at 17:32
  • @yoxigen, this would make for a good SO question: "what should Angular services do (and not do)?" Back to Larry's case: the child scopes all "want access to the same" data. I think Angular's typical answer for "access to the same" is dependency injection, not so much scope inheritance. But scope inheritance is certainly an option, and sometimes it is the best (or only) option for certain cases. Larry, another option: you could also store the data on a $rootScope property. Then a parent controller and extra HTML would not be needed. – Mark Rajcok Jan 15 '13 at 19:01
  • I'm still thinking this out. In this case, the parent scope seems to me to be the most "natural", but that may simply be my object-oriented background shading my opinion. I guess the main problem with nesting the scopes is that if anything inside the child controller refers to the parent scope, that will break when the child is used in a different context (without the parent)? The service (which is now working) simply seems like overkill for my requirements. – Larry Lustig Jan 15 '13 at 20:19
  • @Larry, yes, it will break if the parent is not somewhere "above" the child. RootScope is always above, but it is akin to global data. – Mark Rajcok Jan 16 '13 at 04:50