AngularJS InfDig error (infinite loop) with ng-repeat function that returns array of objects - angularjs

Here's my code:
<h1 ng-repeat="item in func()">something</h1>
$scope.func = function(){
return [{"property" : "value1"},{"property": "value2"}];
}
In Angular.js v. 1.1.1 there's no mistake. In Angular.JS v 1.2.1 I get an infDig mistake.
Fiddle of v.1.1.1
Fiddle of v.1.2.1
Could you explain this situation? Thanks a lot.

As of AngularJS 1.2: The "track by" expression was added to ng-repeat and more appropriately addresses this issue as demonstrated
in the following code.
<h1 ng-repeat="item in func() track by $index">something</h1>
$scope.func = function(){
return [{"property" : "value1"},{"property": "value2"}];
}
The following article helps understand the expression in more detail and why it is so useful, particularly when dealing with $$haskey Using Track-By With ngRepeat In AngularJS 1.2 by Ben Nadal.
The problem is that you're creating a new array each time, so it's something new that angular needs to track. As far as I can tell, ng-repeat runs, then immediately checks its collection again to see if anything changed in that cycle. Because the function returns a new array, that is perceived as a change.
Check this out: http://jsfiddle.net/kL5YZ/.
If you look in the console.log and click the button, you will see that the $$hashKey property of the objects is being changed each time ng-repeat runs.
The change occurs starting at version 1.1.4, but the changelog doesn't give any clues as to why the behavior is different. The new behavior does make more sense to me.
Here's a great post I found explaining the current behavior in depth: How to Loop through items returned by a function with ng-repeat?
If you make sure to return the same object/array each time, you won't have the error. You could have the function cache anything it creates based on the arguments and always return the same array/object when those arguments are passed in. So, myFunc('foo') will always return the same array, not a new one that looks the same. See the notes in my code below. Live demo (click).
<div ng-repeat="foo in foos">
<div ng-repeat="bar in barFunc(foo)">{{bar.text}}</div>
<div ng-repeat="bar in barFunc('test')">{{bar.text}}</div>
</div>
JavaScript:
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope, myService) {
$scope.foos = [
'a','b','c'
];
//I put this into a service to avoid cluttering the controller
$scope.barFunc = myService.getObj;
});
app.factory('myService', function() {
/*
* anything created will be stored in this cache object,
* based on the arguments passed to `getObj`.
* If you need multiple arguments, you could form them into a string,
* and use that as the cache key
* since there's only one argument here, I'll just use that
*/
var cache = {};
var myService = {
getObj : function(val) {
//if we haven't created an array with this argument before
if (!cache[val]) {
//create one and store it in the cache with that argument as the key
cache[val] = [
{text:val}
];
}
//return the cached array
return cache[val];
}
};
return myService;
});

Related

angular result and digest in code

I have a simple controller, i want to get a random value on loading, then use it to paint an arc somewhere, but it gives many values and it paints several arcs. I think it's cause of angular refreshing, but can I just take final value, not all?
angular.module("app", [])
.controller("Controller", ['$scope', function($scope)
{
$scope.sample = {};
$scope.sample.first = Math.floor(4+Math.random()*3);
$scope.sample.arc = function(unitstart, number)
{ paint arc using first
};
$scope.samle.arc(arguments);
}]);
If not talking about arcs, just place some element, like shown below, it won't get final value of random expression evaluation, but some value of between of evaluation of it.
<span ng-controller = "Controller">{{sample.first}}</span>
Try this instead:
$scope.sample[0].first = Math.floor(4+Math.random()*3);
And in the markup <span ng-controller = "Controller">{{sample[0].first}}</span>
And see if you'll get any value
I think you need to initialize the array as well in order to be able to access the properties.

Using ng-class with a function call - called multiple times

I'm using Ionic and want to dynamically change the background colour of each item in an <ion-list> based on the data. I thought I'd do this by way of a function call to return the correct class
<ion-list>
<ion-item ng-repeat="singleCase in allCases" ng-class="getBackgroundColour(singleCase)" class="item-avatar">
<h2>{{singleCase.date}}</h2>
<p>{{singleCase.caseType}}</p>
</ion-item>
</ion-list>
This is the controller at present
.controller('AllCasesCtrl', ['$scope', '$log', 'dummyData', function($scope, $log, dummyData) {
$scope.allCases = dummyData.cases;
$scope.getBackgroundColour = function(singleCase){
$log.log("getBackgroundColour called...singleCase type: " + singleCase.speciality);
var colourMap = {
speciality1: "speciality1Class",
speciality2: "speciality2Class",
speciality3: "speciality3Class"
};
return colourMap[singleCase.speciality];
};
}])
On checking the console, the getBackgroundColour() function is being called 7 times for each <ion-item> in the list. Why is this, and should I avoid using a function call in ng-class?
AngularJS works with dirty checking: it needs to call the function each cycle to be sure that it doesn't return a new value and that the DOM doesn't need to be updated.
It's part of the typical process of the framework, and calling a function as simple as yours shouldn't have any negative performance impact. Readability and testability of your code is far more important here, so keep the logic in your controller.
One simple things to do, however, is simply to move the declaration of colourMap, which is a constant, outside of your function:
var colourMap = {
speciality1: "speciality1Class",
speciality2: "speciality2Class",
speciality3: "speciality3Class",
};
$scope.getBackgroundColour = function(singleCase) {
return colourMap[singleCase.speciality];
};
Your way is fine as long as your list is not some huge size. That being said if you are using angular 1.3 and you want to lower the number of calls you can change your ng-class to ng-class="::getBackgroundColour(singleCase)". This applies one time binding so once the value is stable it will not check again. This would most likely mean two calls per item.

Binding variables from Service/Factory to Controllers

I have a variable that will be used by one or more Controllers, changed by Services.
In that case, I've built a service that keeps this variable in memory, and share between the controllers.
The problem is: Every time that the variable changes, the variables in the controllers aren't updated in real time.
I create this Fiddle to help. http://jsfiddle.net/ncyVK/
--- Note that the {{countService}} or {{countFactory}} is never updated when I increment the value of count.
How can I bind the Service/Factory variable to $scope.variable in the Controller? What I'm doing wrong?
You can't bind variables. But you can bind variable accessors or objects which contain this variable. Here is fixed jsfiddle.
Basically you have to pass to the scope something, which can return/or holds current value. E.g.
Factory:
app.factory('testFactory', function(){
var countF = 1;
return {
getCount : function () {
return countF; //we need some way to access actual variable value
},
incrementCount:function(){
countF++;
return countF;
}
}
});
Controller:
function FactoryCtrl($scope, testService, testFactory)
{
$scope.countFactory = testFactory.getCount; //passing getter to the view
$scope.clickF = function () {
$scope.countF = testFactory.incrementCount();
};
}
View:
<div ng-controller="FactoryCtrl">
<!-- this is now updated, note how count factory is called -->
<p> This is my countFactory variable : {{countFactory()}}</p>
<p> This is my updated after click variable : {{countF}}</p>
<button ng-click="clickF()" >Factory ++ </button>
</div>
It's not good idea to bind any data from service,but if you need it anymore,I suggest you those following 2 ways.
1) Get that data not inside your service.Get Data inside you controller and you will not have any problem to bind it.
2) You can use AngularJs Events feature.You can even send data to through that event.
If you need more with examples here is the article which maybe can help you.
http://www.w3docs.com/snippets/angularjs/bind-value-between-service-and-controller-directive.html

AngularJS : The correct way of binding to a service properties

I’m looking for the best practice of how to bind to a service property in AngularJS.
I have worked through multiple examples to understand how to bind to properties in a service that is created using AngularJS.
Below I have two examples of how to bind to properties in a service; they both work. The first example uses basic bindings and the second example used $scope.$watch to bind to the service properties
Are either of these example preferred when binding to properties in a service or is there another option that I’m not aware of that would be recommended?
The premise of these examples is that the service should updated its properties “lastUpdated” and “calls” every 5 seconds. Once the service properties are updated the view should reflect these changes. Both these example work successfully; I wonder if there is a better way of doing it.
Basic Binding
The following code can be view and ran here: http://plnkr.co/edit/d3c16z
<html>
<body ng-app="ServiceNotification" >
<div ng-controller="TimerCtrl1" style="border-style:dotted">
TimerCtrl1 <br/>
Last Updated: {{timerData.lastUpdated}}<br/>
Last Updated: {{timerData.calls}}<br/>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.5/angular.js"></script>
<script type="text/javascript">
var app = angular.module("ServiceNotification", []);
function TimerCtrl1($scope, Timer) {
$scope.timerData = Timer.data;
};
app.factory("Timer", function ($timeout) {
var data = { lastUpdated: new Date(), calls: 0 };
var updateTimer = function () {
data.lastUpdated = new Date();
data.calls += 1;
console.log("updateTimer: " + data.lastUpdated);
$timeout(updateTimer, 5000);
};
updateTimer();
return {
data: data
};
});
</script>
</body>
</html>
The other way I solved binding to service properties is to use $scope.$watch in the controller.
$scope.$watch
The following code can be view and ran here: http://plnkr.co/edit/dSBlC9
<html>
<body ng-app="ServiceNotification">
<div style="border-style:dotted" ng-controller="TimerCtrl1">
TimerCtrl1<br/>
Last Updated: {{lastUpdated}}<br/>
Last Updated: {{calls}}<br/>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.5/angular.js"></script>
<script type="text/javascript">
var app = angular.module("ServiceNotification", []);
function TimerCtrl1($scope, Timer) {
$scope.$watch(function () { return Timer.data.lastUpdated; },
function (value) {
console.log("In $watch - lastUpdated:" + value);
$scope.lastUpdated = value;
}
);
$scope.$watch(function () { return Timer.data.calls; },
function (value) {
console.log("In $watch - calls:" + value);
$scope.calls = value;
}
);
};
app.factory("Timer", function ($timeout) {
var data = { lastUpdated: new Date(), calls: 0 };
var updateTimer = function () {
data.lastUpdated = new Date();
data.calls += 1;
console.log("updateTimer: " + data.lastUpdated);
$timeout(updateTimer, 5000);
};
updateTimer();
return {
data: data
};
});
</script>
</body>
</html>
I’m aware that I can use $rootscope.$broadcast in the service and $root.$on in the controller, but in other examples that I’ve created that use $broadcast/$on the first broadcast is not captured by the controller, but additional calls that are broadcasted are triggered in the controller. If you are aware of a way to solve $rootscope.$broadcast problem, please provide an answer.
But to restate what I mentioned earlier, I would like to know the best practice of how to bind to a service properties.
Update
This question was originally asked and answered in April 2013. In May 2014, Gil Birman provided a new answer, which I changed as the correct answer. Since Gil Birman answer has very few up-votes, my concern is that people reading this question will disregard his answer in favor of other answers with many more votes. Before you make a decision on what's the best answer, I highly recommend Gil Birman's answer.
Consider some pros and cons of the second approach:
0 {{lastUpdated}} instead of {{timerData.lastUpdated}}, which could just as easily be {{timer.lastUpdated}}, which I might argue is more readable (but let's not argue... I'm giving this point a neutral rating so you decide for yourself)
+1 It may be convenient that the controller acts as a sort of API for the markup such that if somehow the structure of the data model changes you can (in theory) update the controller's API mappings without touching the html partial.
-1 However, theory isn't always practice and I usually find myself having to modify markup and controller logic when changes are called for, anyway. So the extra effort of writing the API negates it's advantage.
-1 Furthermore, this approach isn't very DRY.
-1 If you want to bind the data to ng-model your code become even less DRY as you have to re-package the $scope.scalar_values in the controller to make a new REST call.
-0.1 There's a tiny performance hit creating extra watcher(s). Also, if data properties are attached to the model that don't need to be watched in a particular controller they will create additional overhead for the deep watchers.
-1 What if multiple controllers need the same data models? That means that you have multiple API's to update with every model change.
$scope.timerData = Timer.data; is starting to sound mighty tempting right about now... Let's dive a little deeper into that last point... What kind of model changes were we talking about? A model on the back-end (server)? Or a model which is created and lives only in the front-end? In either case, what is essentially the data mapping API belongs in the front-end service layer, (an angular factory or service). (Note that your first example--my preference-- doesn't have such an API in the service layer, which is fine because it's simple enough it doesn't need it.)
In conclusion, everything does not have to be decoupled. And as far as decoupling the markup entirely from the data model, the drawbacks outweigh the advantages.
Controllers, in general shouldn't be littered with $scope = injectable.data.scalar's. Rather, they should be sprinkled with $scope = injectable.data's, promise.then(..)'s, and $scope.complexClickAction = function() {..}'s
As an alternative approach to achieve data-decoupling and thus view-encapsulation, the only place that it really makes sense to decouple the view from the model is with a directive. But even there, don't $watch scalar values in the controller or link functions. That won't save time or make the code any more maintainable nor readable. It won't even make testing easier since robust tests in angular usually test the resulting DOM anyway. Rather, in a directive demand your data API in object form, and favor using just the $watchers created by ng-bind.
Example
http://plnkr.co/edit/MVeU1GKRTN4bqA3h9Yio
<body ng-app="ServiceNotification">
<div style="border-style:dotted" ng-controller="TimerCtrl1">
TimerCtrl1<br/>
Bad:<br/>
Last Updated: {{lastUpdated}}<br/>
Last Updated: {{calls}}<br/>
Good:<br/>
Last Updated: {{data.lastUpdated}}<br/>
Last Updated: {{data.calls}}<br/>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.5/angular.js"></script>
<script type="text/javascript">
var app = angular.module("ServiceNotification", []);
function TimerCtrl1($scope, Timer) {
$scope.data = Timer.data;
$scope.lastUpdated = Timer.data.lastUpdated;
$scope.calls = Timer.data.calls;
};
app.factory("Timer", function ($timeout) {
var data = { lastUpdated: new Date(), calls: 0 };
var updateTimer = function () {
data.lastUpdated = new Date();
data.calls += 1;
console.log("updateTimer: " + data.lastUpdated);
$timeout(updateTimer, 500);
};
updateTimer();
return {
data: data
};
});
</script>
</body>
UPDATE: I've finally come back to this question to add that I don't think that either approach is "wrong". Originally I had written that Josh David Miller's answer was incorrect, but in retrospect his points are completely valid, especially his point about separation of concerns.
Separation of concerns aside (but tangentially related), there's another reason for defensive copying that I failed to consider. This question mostly deals with reading data directly from a service. But what if a developer on your team decides that the controller needs to transform the data in some trivial way before the view displays it? (Whether controllers should transform data at all is another discussion.) If she doesn't make a copy of the object first she might unwittingly cause regressions in another view component which consumes the same data.
What this question really highlights are architectural shortcomings of the typical angular application (and really any JavaScript application): tight coupling of concerns, and object mutability. I have recently become enamored with architecting application with React and immutable data structures. Doing so solves the following two problems wonderfully:
Separation of concerns: A component consumes all of it's data via props and has little-to-no reliance on global singletons (such as Angular services), and knows nothing about what happened above it in the view hierarchy.
Mutability: All props are immutable which eliminates the risk of unwitting data mutation.
Angular 2.0 is now on track to borrow heavily from React to achieve the two points above.
From my perspective, $watch would be the best practice way.
You can actually simplify your example a bit:
function TimerCtrl1($scope, Timer) {
$scope.$watch( function () { return Timer.data; }, function (data) {
$scope.lastUpdated = data.lastUpdated;
$scope.calls = data.calls;
}, true);
}
That's all you need.
Since the properties are updated simultaneously, you only need one watch. Also, since they come from a single, rather small object, I changed it to just watch the Timer.data property. The last parameter passed to $watch tells it to check for deep equality rather than just ensuring that the reference is the same.
To provide a little context, the reason I would prefer this method to placing the service value directly on the scope is to ensure proper separation of concerns. Your view shouldn't need to know anything about your services in order to operate. The job of the controller is to glue everything together; its job is to get the data from your services and process them in whatever way necessary and then to provide your view with whatever specifics it needs. But I don't think its job is to just pass the service right along to the view. Otherwise, what's the controller even doing there? The AngularJS developers followed the same reasoning when they chose not to include any "logic" in the templates (e.g. if statements).
To be fair, there are probably multiple perspectives here and I look forward to other answers.
Late to the party, but for future Googlers - don't use the provided answer.
JavaScript has a mechanism of passing objects by reference, while it only passes a shallow copy for values "numbers, strings etc".
In above example, instead of binding attributes of a service, why don't we expose the service to the scope?
$scope.hello = HelloService;
This simple approach will make angular able to do two-way binding and all the magical things you need. Don't hack your controller with watchers or unneeded markup.
And if you are worried about your view accidentally overwriting your service attributes, use defineProperty to make it readable, enumerable, configurable, or define getters and setters. You can gain lots of control by making your service more solid.
Final tip: if you spend your time working on your controller more than your services then you are doing it wrong :(.
In that particular demo code you supplied I would recommend you do:
function TimerCtrl1($scope, Timer) {
$scope.timer = Timer;
}
///Inside view
{{ timer.time_updated }}
{{ timer.other_property }}
etc...
Edit:
As I mentioned above, you can control the behaviour of your service attributes using defineProperty
Example:
// Lets expose a property named "propertyWithSetter" on our service
// and hook a setter function that automatically saves new value to db !
Object.defineProperty(self, 'propertyWithSetter', {
get: function() { return self.data.variable; },
set: function(newValue) {
self.data.variable = newValue;
// let's update the database too to reflect changes in data-model !
self.updateDatabaseWithNewData(data);
},
enumerable: true,
configurable: true
});
Now in our controller if we do
$scope.hello = HelloService;
$scope.hello.propertyWithSetter = 'NEW VALUE';
our service will change the value of propertyWithSetter and also post the new value to database somehow!
Or we can take any approach we want.
Refer to the MDN documentation for defineProperty.
I think this question has a contextual component.
If you're simply pulling data from a service & radiating that information to it's view, I think binding directly to the service property is just fine. I don't want to write a lot of boilerplate code to simply map service properties to model properties to consume in my view.
Further, performance in angular is based on two things. The first is how many bindings are on a page. The second is how expensive getter functions are. Misko talks about this here
If you need to perform instance specific logic on the service data (as opposed to data massaging applied within the service itself), and the outcome of this impacts the data model exposed to the view, then I would say a $watcher is appropriate, as long as the function isn't terribly expensive. In the case of an expensive function, I would suggest caching the results in a local (to controller) variable, performing your complex operations outside of the $watcher function, and then binding your scope to the result of that.
As a caveat, you shouldn't be hanging any properties directly off your $scope. The $scope variable is NOT your model. It has references to your model.
In my mind, "best practice" for simply radiating information from service down to view:
function TimerCtrl1($scope, Timer) {
$scope.model = {timerData: Timer.data};
};
And then your view would contain {{model.timerData.lastupdated}}.
Building on the examples above I thought I'd throw in a way of transparently binding a controller variable to a service variable.
In the example below changes to the Controller $scope.count variable will automatically be reflected in the Service count variable.
In production we're actually using the this binding to update an id on a service which then asynchronously fetches data and updates its service vars. Further binding that means that controllers automagically get updated when the service updates itself.
The code below can be seen working at http://jsfiddle.net/xuUHS/163/
View:
<div ng-controller="ServiceCtrl">
<p> This is my countService variable : {{count}}</p>
<input type="number" ng-model="count">
<p> This is my updated after click variable : {{countS}}</p>
<button ng-click="clickC()" >Controller ++ </button>
<button ng-click="chkC()" >Check Controller Count</button>
</br>
<button ng-click="clickS()" >Service ++ </button>
<button ng-click="chkS()" >Check Service Count</button>
</div>
Service/Controller:
var app = angular.module('myApp', []);
app.service('testService', function(){
var count = 10;
function incrementCount() {
count++;
return count;
};
function getCount() { return count; }
return {
get count() { return count },
set count(val) {
count = val;
},
getCount: getCount,
incrementCount: incrementCount
}
});
function ServiceCtrl($scope, testService)
{
Object.defineProperty($scope, 'count', {
get: function() { return testService.count; },
set: function(val) { testService.count = val; },
});
$scope.clickC = function () {
$scope.count++;
};
$scope.chkC = function () {
alert($scope.count);
};
$scope.clickS = function () {
++testService.count;
};
$scope.chkS = function () {
alert(testService.count);
};
}
I think it's a better way to bind on the service itself instead of the attributes on it.
Here's why:
<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">
ArrService.arrOne: <span ng-repeat="v in ArrService.arrOne">{{v}}</span>
<br />
ArrService.arrTwo: <span ng-repeat="v in ArrService.arrTwo">{{v}}</span>
<br />
<br />
<!-- This is empty since $scope.arrOne never changes -->
arrOne: <span ng-repeat="v in arrOne">{{v}}</span>
<br />
<!-- This is not empty since $scope.arrTwo === ArrService.arrTwo -->
<!-- Both of them point the memory space modified by the `push` function below -->
arrTwo: <span ng-repeat="v in arrTwo">{{v}}</span>
</div>
<script type="text/javascript">
var app = angular.module("BindToService", []);
app.controller("BindToServiceCtrl", function ($scope, ArrService) {
$scope.ArrService = ArrService;
$scope.arrOne = ArrService.arrOne;
$scope.arrTwo = ArrService.arrTwo;
});
app.service("ArrService", function ($interval) {
var that = this,
i = 0;
this.arrOne = [];
that.arrTwo = [];
$interval(function () {
// This will change arrOne (the pointer).
// However, $scope.arrOne is still same as the original arrOne.
that.arrOne = that.arrOne.concat([i]);
// This line changes the memory block pointed by arrTwo.
// And arrTwo (the pointer) itself never changes.
that.arrTwo.push(i);
i += 1;
}, 1000);
});
</script>
</body>
You can play it on this plunker.
I would rather keep my watchers a less as possible. My reason is based on my experiences and one might argue it theoretically.
The issue with using watchers is that you can use any property on scope to call any of the methods in any component or service you like.
In a real world project, pretty soon you'll end up with a non-tracable (better said hard to trace) chain of methods being called and values being changed which specially makes the on-boarding process tragic.
To bind any data,which sends service is not a good idea (architecture),but if you need it anymore I suggest you 2 ways to do that
1) you can get the data not inside you service.You can get data inside your controller/directive and you will not have a problem to bind it anywhere
2) you can use angularjs events.Whenever you want,you can send a signal(from $rootScope) and catch it wherever you want.You can even send a data on that eventName.
Maybe this can help you.
If you need more with examples,here is the link
http://www.w3docs.com/snippets/angularjs/bind-value-between-service-and-controller-directive.html
What about
scope = _.extend(scope, ParentScope);
Where ParentScope is an injected service?
The Most Elegant Solutions...
app.service('svc', function(){ this.attr = []; return this; });
app.controller('ctrl', function($scope, svc){
$scope.attr = svc.attr || [];
$scope.$watch('attr', function(neo, old){ /* if necessary */ });
});
app.run(function($rootScope, svc){
$rootScope.svc = svc;
$rootScope.$watch('svc', function(neo, old){ /* change the world */ });
});
Also, I write EDAs (Event-Driven Architectures) so I tend to do something like the following [oversimplified version]:
var Service = function Service($rootScope) {
var $scope = $rootScope.$new(this);
$scope.that = [];
$scope.$watch('that', thatObserver, true);
function thatObserver(what) {
$scope.$broadcast('that:changed', what);
}
};
Then, I put a listener in my controller on the desired channel and just keep my local scope up to date this way.
In conclusion, there's not much of a "Best Practice" -- rather, its mostly preference -- as long as you're keeping things SOLID and employing weak coupling. The reason I would advocate the latter code is because EDAs have the lowest coupling feasible by nature. And if you aren't too concerned about this fact, let us avoid working on the same project together.
Hope this helps...

Sharing observable array reference between controllers in AngularJS

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:
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();
}
}
})
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()
}
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"> {{wonum}} </li>
</ul>
<ul>
<li ng-repeat="wonum in wonums"> {{wonum}} </li>
</ul>
</div>
</div>
Several other view / controller pairs, not shown here, that also bind and iterate over the array of work orders.
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.
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!

Resources