AngularJS $watches: more simple ones or less complex ones? - angularjs

Which is better for performance, readability and maintainability (Angular 1.5)?
Less, but more complex watches
// component template
<div ng-if="vm.obj.a.b + vm.stuff.c.d + vm.another.thing">
{{ vm.obj.a.b + vm.stuff.c.d + vm.another.thing }}
</div>
Or more, but simpler watches
// component template
<div ng-if="vm.sum">
{{ vm.sum }}
</div>
// component controller...
var vm = this;
$scope.$watch('vm.obj.a.b', fnSum);
$scope.$watch('vm.stuff.c.d', fnSum);
$scope.$watch('vm.another.thing', fnSum);
function fnSum() {
vm.sum = vm.obj.a.b + vm.stuff.c.d + vm.another.thing;
}
Or is there a better way that I'm missing?
This is just a simplified illustration of a situation I come across a LOT. Please focus on the principle, not this exact example.

For readability and maintainability invoke the function directly from the template:
<div ng-show="vm.sum = vm.fnSum()">
{{ vm.sum }}
</div>
JS
vm.fnSum = function fnSum() {
return vm.obj.a.b + vm.stuff.c.d + vm.another.thing;
}
Also using ng-show avoids creating a new scope.
Be aware that, under the hood, ng-if, ng-show, and {{ fn() }} all create scope watchers. Adding additional watchers for each term of an expression doesn't improve performance.

The latter is better for maintenance reasons:
It allows you to put a succinct end-result into the view, allowing anyone scrolling through to simply read the intended output, rather than parts of what will produce the final result. The view remains uncluttered, while the controller can have verbose comments and contain the entire process for getting the ngIf determination.
It abstracts a potentially volatile result. Say that you needed to change/update what actually triggered ngIf; the former would require you to change every instance in the view along with whatever controller changes you're making. The latter is simply updating how the final value came to be in the controller, and knowing that the view is already set.
You could probably make the latter a little better by refactoring it into one $watch:
var vm = this;
$scope.$watch(function(){
return vm.obj.a.b + vm.stuff.c.d + vm.another.thing;
}, fnSum);
function fnSum(newSum, oldSum) {
vm.sum = newSum;
}

Related

Reloading Controller in Angular

I am a newbie in angular, and I haven't been able to find any solid answers for this question... even though the answer might have been in front of me, I just couldn't see it.
I have this piece of html:
<ul class="wrapper" ng-controller="MyController as vks">
<li ng-repeat="myvar in vks.vk" class="padding-left" data-myvar-id="{{myvar.id}}">
<img src="images/someimage.svg" ng-show="{{myvar.id}} == {{myvar.chosen}}" class="some-class">
<a class="hvr-underline-reveal">{{myvar.name}}</a>
</li>
</ul>
And this is my controller:
app.controller('MyController', function() {
var i = 0;
var tmp = [];
for (var key in someObject) {
if (someObject.hasOwnProperty(key)) {
var obj = someObject[key];
if (obj.checked) {
tmp[i] = {name: obj.name, id: obj.id, chosen: someStaticVar};
i++;
}
}
}
this.vk = tmp;
});
I want to focus on this part which works the first time the page is loaded:
<img ng-show="{{myvar.id}} == {{myvar.chosen}}">
What happens when suddenly {{myvar.chosen}} (which is is set from a static global var) changes from some outside javascript? The HTML part is not refreshed, and that specific is still shown, but it should be another one shown.
How can I reload only this controller? (or do something else to make this work as intended?)
Hope this makes sense...
As #Jodevan hinted, Angular will not be aware of the change to the global variable. But even if it was made aware of that change, based on your example, your view still wouldn't update since the value of the bound variable myvar.chosen is not changing. In other words, your for-loop is not being reevaluated when that global variable changes.
To get your desired effect, you would have to (1) know when the global variable has changed and (2) update the value of the chosen property so that the model actually changes and triggers a digest.
Here's a fiddle showing how the code above could be made to work: naive solution. Hopefully, knowing the 2 requirements above, you can come up with a solution that works for you.
Whenever you change a variable not directly through AngularJS, you must let AngularJS knows that this variable was changed. The way to do that is using the scope.$apply() function.
I think this can help you to understand what is going on under the hood.

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.

Is there way to avoid/workaround Angularjs computed property running twice everytime?

http://jsfiddle.net/V9sYB/50/
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.6/angular.min.js"></script>
<div ng-app>
<div ng-controller="SomeCtrl">
<input ng-model="textProperty"/>
<span>{{computedProperty()}}</span>
</div>
</div>
script
function SomeCtrl($scope) {
$scope.textProperty = "initial";
var times = 0;
$scope.computedProperty = function(){
console.log(++times);
return $scope.textProperty+ " asdf";
};
}
Is there way to avoid/workaround Angularjs computed property running twice everytime?
or Do I have to write directive for the one that I will never reuse on another controller?
When you use interpolation with {{, you are effectively creating a watch on the expression inside it. Since angularjs works on the dirty check principal, where it revaluates the expression multiple time during a digest cycle you cannot control how many times any expression defined inside a interpolation runs.
Therefore such computed property\method should have no side affects as it gets called multiple times which you as a developer do not have control.
If view expression is computationally expensive, it's advised to use some sort of function caching mechanism, to reduce the cost.
Your function, for example, can be easily cached using a hefty memoization method from the underscore utility belt:
FIDDLE
function SomeCtrl($scope) {
$scope.textProperty = "initial";
var memoizedTimes = 0;
$scope.computedMemoizedProperty = _.memoize(function(){
console.log(++memoizedTimes);
return $scope.textProperty + " asdf";
});
}
This ensures that function logic will always be execute at most once for every input.

New to Angular - Computed Variables

I am moving to Angular from Knockout, and I have a few issues. I'm assuming that I must be doing something a non-angular type of way.
http://jsfiddle.net/LostInDaJungle/BxELP/
I linked to jsfiddle so I didn't have to include my code here
Stack Overflow will not let me post my question without a code block.
Here is a very basic fiddle that outlines two of my major problems...
Problem 1: val1 and val2 are initialized as 3 and 4, and add up to 7 properly. However, if you change either of the values in the text boxes, the new value is treated as a string and I get concatenation instead of addition. Change val1 to 4 and you get 44 when it should be 8. What is the best way around this behaviour?
Problem 2: Calculated fields. I can get a calculated field by using the curly brackets like {{val1 + val2}} and have the calculated fields auto update when the underlying model changes, but this is totally unacceptable. In my full fledged app, we generate a "cost" that is used several times throughout and having to put in the cost calculation each and every time is a pain. Not to mention that when this calculation changes, I now have the unenviable task of finding 15 different places that use the cost calculation and updating them all.
In addition, if I try to put an ng-model="cost" on the input with the curly brackets, then the curly brackets don't work. So nothing jumps out at me as a way to bind cost.
http://jsfiddle.net/LostInDaJungle/QNVwe/
This example is more like the structure I desire. However, unlike a ko.observable, the calculated fields do not update when the values that generate them change. The boilerplate solution everyone has foisted on me is to write a bunch of ng-change handlers... But that is awful. If width changes change the cost and change the payback calculations, etc... It quickly becomes a tangled mess.
Both of these methods fail as far as separating logic from presentation. Method one has my business logic embedded in my HTML. Method two puts a whole bunch of ng-change handlers in my code which isn't that much different from having to write a whole mess of onChange handlers in plain ol' HTML. If I HAVE to do a bunch of ng-change handlers, I would just as soon do an onChange handler in Javascript because I can at least declare them outside of my presentation layer.
Here's a knockout version of the same:
http://jsfiddle.net/LostInDaJungle/eka4S/2/
This is more like what I would expect... Nothing but data-binds on my inputs, all program logic nicely contained within the view model. Also, since my computable is a Javascript function, I don't have to scratch my head about how to ensure my two values are numeric.
So....
Computed variables: Is there a way to watch the underlying variables and update the computed amount automatically? Without having to bury my program logic in my HTML?
Is there a good way to keep my numbers from turning into strings?
Thank you for your help.
FYI, also posted to Google Groups: https://groups.google.com/forum/#!topic/angular/0dfnDTaj8tw
For a calculated field, add a method to your controller . . .
$scope.cost = function() { return $scope.val1 + $scope.val2 };
and then bind to it directly. It will know when it needs to recalculate as its constituent values change.
<div>{{cost()}}</div>
Ok,
A few hours later and I think I have my answer.
Using $scope.$watch.
$scope.$watch('(height * width) * 40', function(v) {$scope.cost = v;});
or
$scope.$watch('height + width', function() {$scope.cost = (Number(height) * Number(width)) * 40;});
This auto-updates any computables for watched variables. And it gives me a way to work with these without having to live inside curly brackets.
Also, the computed values can be reused and tracked for cascading updates:
$scope.$watch('height * width', function(v) {$scope.dim = v;});
$scope.$watch('dim * 40', function(v) {$scope.cost = v;});
So if height and/or width change, dim is updated, and since dim has changed, cost is updated.
I changed your third input to:
<input type="text" value="{{val1 * 1 + val2}}" />
which causes Angular.js to treat the values as numbers, not strings.
Here is the fiddle. I gleaned the answer from here.
About problem 1:
You should use input type="number" if possible. That would take care of parsing numbers properly. Even if you have an older browser angular would take care of formatting them as numbers.
About problem 2:
Your answer is good Jason if you just need to show plain text on the screen. However if you would like to bind an input with a model to an arbitrary expression, you need something else.
I wrote a directive you can use to bind an ng-model to any expression you want. Whenever the expression changes the model is set to the new value.
module.directive('boundModel', function() {
return {
require: 'ngModel',
link: function(scope, elem, attrs, ngModel) {
scope.$watch(attrs.boundModel, function(newValue, oldValue) {
if(newValue != oldValue) {
ngModel.$setViewValue(newValue);
ngModel.$render();
}
});
}
};
})
You can use it in your templates like this:
<input type="text" ng-model="total" bound-model="value1 + value2">
Or like this:
<input type="text" ng-model="total" bound-model="cost()">
Where cost() is a simple function of the scope like this:
$scope.cost = function() { return $scope.val1 + $scope.val2 };
The good thing is that you keep using a model for your input and you don't have to dinamically update your value attribute, which doesn't work well in angular.
I'm new to AngularJS but I think that $parse could be used:
http://docs.angularjs.org/api/ng/service/$parse
This is interesting if you have the expression as a string. You can use a path of properties and that string can be generated dynamically. This works if you don't know the expression at compile time, a lot like eval() but probably a lot faster and maybe more secure(?).
Here's an example:
function Ctrl($scope,$parse) {
var expression = 'model.val1 + model.val2';//could be dynamically created
$scope.model = {
val1: 0,
val2: 0,
total: function() {
return ($parse(expression))($scope);
}
};
}
u can bind to a function
function CTRL ($scope) {
$scope.val1 = 3;
$scope.val2 = 4;
$scope.sum = function(){
return ($scope.val1 *1 + $scope.val2 *1);
};
}
it will work the same
the binding expression will work but in much more complex cases we need functions
The $watch function that is made available through the $scope variable is best for this job in my opinion.
$scope.$watch(function(scope) { return scope.data.myVar },
function(newValue, oldValue) {
document.getElementById("myElement").innerHTML =
"" + newValue + "";
}
);
The $watch function takes in a:
value function
& a listener function
The above example is taken from this awesome article: http://tutorials.jenkov.com/angularjs/watch-digest-apply.html
After reading through it, I learnt a lot and was able to implement the solution I was looking for.

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...

Resources