I am using the TodoMVC app to get better with the AngularJS framework. In the index.html on lines 14-16 you see this:
<form id="todo-form" ng-submit="addTodo()">
<input id="new-todo" placeholder="What needs to be done?" ng-model="newTodo" autofocus>
</form>
Notice how the ng-submit directive calls the addTodo() function without the newTodo model being passed as an argument.
A short time later I came across the following code in the very same file on line 19:
<input id="toggle-all" type="checkbox" ng-model="allChecked" ng-click="markAll(allChecked)">
You can see the author decided to pass the allChecked model to the markAll() function this time. If I understand correctly, they could have referenced $scope.allChecked inside the controller instead of passing it in.
Why use two different approaches in the same file? Is one approach better in some circumstances? Is this a case of inconsistency or is there a deeper logic being used?
I would prefer to always pass in arguments to the function:
It's clearer what parameters the function expects.
It's easier to unit-test because all parameters are injected into the function.(good for unit- testing)
Consider the following situation:
$scope.addToDo = function(){
//This declaration is not clear what parameters the function expects.
if ($scope.parameter1){
//do something with parameter2
}
}
And even worse:
$scope.addToDo = function(){
//This declaration is not clear what parameters the function expects.
if ($scope.someobject.parameter1){ //worse
}
}
Because of scope inheritance parameter2 may come from parent scope, accessing parameter2 inside the function creates a tight coupling, also causes troubles when you try to unit-test that function.
If I define the function like this:
//It's clearer that the function expects parameter1, parameter2
$scope.addToDo = function(parameter1, parameter2){
if (parameter1){
//do something with parameter2
}
}
In case your parameter2 is inherited from parent scope, you could still pass it in from the view. When you do unit-testing, it's easy to pass all parameters.
If you have ever worked with ASP.NET MVC, you would notice something similar: the framework tries to inject parameters into action function instead of accessing it directly from Request or HttpContext object
It's also good in case others have mentioned like working with ng-repeat
In my opinion, Controller and Model in angular are not quite clearly separated. The $scope object looks like our Model with properties and methods (Model contains also logic). People from OOP background would think that: we only pass in parameters that don't belong to object. Like a class Person already has hands, we don't need to pass in hands for every object method. An example code like this:
//assume that parameter1 belongs to $scope, parameter2 is inherited from parent scope.
$scope.addToDo = function(parameter2){
if ($scope.parameter1){ //parameter1 could be accessed directly as it belongs to object, parameter2 should be passed in as parameter.
//do something with parameter2
}
}
There are two parts to this answer, the first part is answering which one is the better option, the other part is the fact that neither of them is a good option!
Which one is correct?
This one is:
$scope.addToDo = function(params1, ...) {
alert(params1);
}
Why? Because A - it is testable. This is important even if you are not writing tests, because code that is testable is pretty much always more readable and maintainable in the long run.
It is also better because of B - it is agnostic when it comes to the caller. This function can be reused by any number of different controllers/services/etc because it does not depend on the existence of a scope or on the structure of that scope.
When you instead do this:
$scope.addToDo = function() {
alert($scope.params1);
}
Both A and B fail. It is not easily testable on its own, and it cannot easily be reused because the scope you use it in might be formatted differently.
Edit: If you are doing something very closely tied to your specific scope and running the function from the template, then you might run in to situations where trying to make it reusable just doesn't make sense. The function simply isn't generic. In that case, don't bother with that, some functions cannot be reused. View what I wrote about as your default mode but remember that in some cases it won't fit.
Why are both wrong?
Because as a general rule you should not be doing logic in your controllers, that is the job of a service. The controller can use a service and call the function or expose it in a model, but it should not define it.
Why is this important? Because again it makes it easy to reuse the function. A function that is defined in a controller cannot be reused in another controller without putting limits on how the controllers are invoked in the HTML. A function that is defined in a service can be injected and reused wherever you feel like it.
But I don't need to reuse the function! - Yes you do! Maybe not right now and maybe never for this specific function, but sooner or later you will end up wanting to reuse a function that you where convinced that you would never need to reuse. And then you will have to rework code that you have already half forgotten, which always take extra time.
It's better to just do it properly from the start and move all logic that you can into services. That way, if you ever need them somewhere else (even in another project) you can just grab it and use it without having to rewrite it to fit your current scope structure.
Of course, services don't know about your scope so you are forced to use the first version. Bonus! And don't fall for the temptation of passing the entire scope to a service, that will never end well :-)
So this is IMO the best option:
app.service('ToDoService', [function(){
this.addToDo = function(params1, ...){
alert(params1);
}
}]);
And inside the controller:
$scope.addToDo = ToDoService.addToDo;
Note that I wrote "general rule". In some cases it is reasonable to define the function in the controller itself as opposed to a service. One example would be when the function only relates to scope specific things, like toggling a state in the controller somehow. There is no real way to do that in a service without things becoming strange.
But it sounds like this is not the case here.
The Zen of Angular suggests:
Treat scope as read only in templates
Treat scope as write only in controllers
Following this principle, you should always call functions explicity with parameters from the template.
However, in any style you follow, you do have to be careful about priorities and order of execution of directives. In your example, using ng-model and ng-click leaves the order of execution of the two directives ambiguous. The solution is using ng-change, where the order of execution is clear: it will be executed only after the value changes.
The custom behavior methods, such as ng-click, ng-submit etc. allow us to pass parameters to methods being called. This is important since we may want to pass something that may not be available freely across till the handler in the controller. For eg,
angular.module('TestApp')
.controller('TestAppController', ['$scope', function($scope) {
$scope.handler = function(idx) {
alert('clicked ' + idx.toString());
};
}]);
<ul>
<li ng-repeat="item in items">
<button ng-click="handler($index)">{ item }</button>
<!-- $index is an iterator automatically available with ngRepeat -->
</li>
</ul>
In case of your second example, since allChecked is within the scope of the same controller which defines markAll(), you are absolutely correct, there is no need to pass anything. We are only creating another copy.
The method would have to simply be refactored to use whats available in the scope.
$scope.markAll = function () {
todos.forEach(function (todo) {
todo.completed = $scope.allChecked;
});
};
Hence, even though we have the option to use parameters in these methods, they are only required some of the time.
I think that this is simply a case of inconsistency in the code. I've pondered this question before and came to the following conclusion...
Rule: Don't pass $scope variables into $scope functions.
Reading the controller code should be enough code to demonstrate what the function of the component will be. The view should not contain any business logic, just bindings (ng-model, ng-click etc). if something in the view can be made clearer by being moved to the controller, so be it.
Exception: Allow conditional ng-class statements (e.g. ng-class='{active:$index==item.idx') - putting class conditionals in the controller can be very verbose, and muddies the logic of the controller with ideas from the view. If it's a visual property, keep it in the view.
Exception: You are working with an item in an ng-repeat. For example:
<ul ng-repeat="item in items">
<li><a ng-click="action(item)"><h1>{{item.heading}}</h1></a></li>
</ul>
I follow these rules when writing controllers & views, and they seem to work. Hope this helps.
Perhaps to illustrate that you can? There is no functional difference between the two assuming that they are the same controller. Note that there are situations where child scopes are generated in which case you won't have the same scope as the controller any longer.
Related
I'm trying to get a better understanding of a new codebase, and haven't found a ton of info (or maybe I'm not fully understanding what I have read) about the performance implications of some choices that were made. The previous maintainer isn't available for insight.
Each controller is set up to make its data request before being rendered initially using this pattern:
$routeProvider.when('/path', {
resolve: { someMethod: function($q) {
var deferred = $q.defer();
.... do some stuff ...
return deferred.promise;
}
});
This is then injected into the controller - that part makes sense and from my understanding is more performant because you're saving digests by waiting for the data to come through first. A loading state is shown by default and removed when we digest.
The value of the injected param - in this case someMethod - is then assigned to this.someModel.
Throughout the controller, remaining methods and properties are set with this.doStuff = function() { ... }, this.property = 'some string', etc.
In the end, a watch is setup and properties made available to the $scope by setting a $watchCollection like this:
$scope.$watchCollection(angular.bind(this.someModel, function() {
return this;
}), function(newTitle, oldTitle) {
if (newTitle.title !== oldTitle.title)
{
self.updateThings(newTitle);
}
});
I understand that it's binding the context of the watch to the current context of this, then watching to changes on its properties using $watchCollection which will perform shallow reference checks of its top level properties.
Per this article I get that simply using properties in my templates with {{ property }} will cause them to be watched, but in this case they aren't watched until they're bound to the $watchCollection.
Of course this is less expensive than using $watch and passing it true for the optional object equality param, but is it better or worse from a performance perspective than putting things directly onto $scope? Why, specifically?
I've never seen it done this way before, so any readings, insights, or things that could give me a better understanding will be greatly appreciated.
The only real thing I can think of is that the author wanted to keep everything off the scope directly, only populating properties of the controller instance, which will be available on the scope via the property specified in the controllerAs property on the route definition object.
However, when doing it that way, if you want to set up $watches on the scope to be notified of changes to something, you can't, because you don't actually know what property of the scope it will be published on.
You could settle on a convention, such as vm, ctrl or whatever, for the controllerAs value, but that is not ideal and is fragile and not really a concern of the controller code itself. But if you made that concession, you could use $scope.$watchCollection('vm.someModel', ... instead.
One final possible reason is that $scope.$watch() and $scope.$watchCollection() always ultimately end up dealing with a function as the thing they use to get the value of the thing being watched during the digest. If you pass a string, it uses the $parse service under the covers to turn it into a function. If you pass a function directly, no conversion is necessary, so this can be perceived as a minor performance improvement.
The real reason(s) may be one, all or none of these, but they are valid reasons for adopting this approach. I actually admire it for its purity and strict adherence to "controller as", which is the in-vogue strategy encouraged by the Angular team and community at the moment.
I'm afraid this question is pretty stupid....
Using the {{current.name}} syntax I can show the name of $scope.current in the view. I set current when I switch from the list view (/mythings) to editing an item (/mythings?id=someId).
Actually this is redundant as I have the information both in the $location and in $scope.current. This redundancy makes it more complicated to understand, so I'd like to get rid of it.
I replaced current by a current item returning function and hoped it would work (like it does in many other cases). But it doesn't, I need to write {{current().name}} everywhere, which I tend to forget.
Maybe I'm doing it all wrong? I'm a beginner here.
Is there a way to make it work? Somehow bless current so it always gets evaluated before use?
Both alternatives discussed have pros and cons:
Using a property (current) is easier (and more natural) to reference in the view, but it needs to be manually kept in sync with the location.
Using a function (current()) takes care of the keeping in sync issue, but is less intuitive.
All things considered, I would value the auto-syncing feature higher and go for the second alternative (current() function).
But, what if we could get a third option that combines the best of both worlds ?
And in fact we can :)
We can use the concept of Object Properties, introduced by ECMAScript 5, and define some "computed properties" for our $scope (or service?).
Without getting into much detail (see the docs for more details), we could augment a scope like this:
.cotroller('someCtrl', function ($location, $scope) {
Object.defineProperty($scope, 'current', {
get: function () {
return {
name: $location.path();
id: $location.search('id');
};
}
});
Now, we can access $scope.current.name and $scope.current.id as if current were a normal property of $scope (intuitiveness !) and current will be automatically computed based on the current location (auto-sync !).
Thank you ECMAScript 5 :)
This article provides a simple and clear introduction to the concept.
I imagine this comes up a lot. Let's say I have:
downloadData () {
var myData = //do something ;
displayData(myData);
}
displayData (myData) { //<--- is it bad practice to use same variable name myData?
//display data
}
Is it good or bad practice (or doesn't really matter) to use the same variable name in the second function displayData() or should I accept myData with some other name, like myDataToDisplay? Both names tell me what's in the var just as effectively.
As long as you declare the variables outside of the function properly (i.e. use "var") you shouldn't have any problems and it helps with making the code easier to read.
The variable myData is bounded to the scope of function displayData when displayData is invoked. So it's not like you're risking the possibility of any name collisions.
My general rule of thumb for parameter naming is clarity. So yes, I think it's perfectly fine to use the same name variable names in this case, insofar as they accurately describe the data that is being passed.
As a though experiment, lets suppose we had a hard rule that says "Thou shall not use the same variable names in multiple functions". What if you have multiple functions that call the same data and you're chaining the calls? That won't look pretty.
Its perfectly allright in this case because you are working with two different functions and scope of those variables are different. Though it depends on preference of coder but my suggestion is don't pollute your script by using same name again and again.
When I'm creating controllers, I always add functions to the $scope object, like so:
function DummyController($scope) {
$scope.importantFunction = function () { /*...*/ };
$scope.lessImportantFunction = function () { /*...*/ };
$scope.bussinessLogicFunction = function () { /*...*/ };
$scope.utilityFunction = function () { /*...*/ };
}
Of course I'm keeping my controllers encapsulated nicely, making sure that business logic lies in appropriate components (injected via DI) or services. So the controller is focused on orchestrating things between UI and Backend.
However, as you can see - there are still plenty of different kinds of functions. I like to keep more of them, as it improves readability IMHO.
The question
Is that a good practice to have lots of functions attached to $scope object? Does it have performance overhead? I know that $scope is a special kind of object, being constantly evaluated in Angular's digest cycle, so am I doing this right or will I ask for trouble by sticking to my approach?
(Please note: I'm not looking for alternative approaches. I'm looking for some well thought analysis of people who know Angular's internals.)
Thx!
UPDATE:
The answer from Anders is very good and shows you some paths to follow. Today I ran into this beauty, a chrome extension for Angular Js debugging and performance monitoring. It shows you all the scopes and assigned variables, along with some interesting performance graph. A must have for any Angular developer!
Update:
Should I add all of my functions and variables to the scope?
No, you should only add functions and variables to your scope if you need to access them in your template. A variable that is only accessed in the controller function shouldn't be on the scope, it should be local to the controller function.
Will if affect performance if I add a lot of functions to the scope?
Generally, no. Functions on your scope that are executed like ng-click="myFunction()" should not affect performance in a noticeable way. But if your function is executed like this: {{myFunction()}} it will get executed for every digest as Angular needs to know if the return value of it has changed so it can update the UI.
Will it affect performance if I add a lot of variables to the scope?
It can affect performance if you use them in places where Angular will dirty check them. Those cases are where you print them out like {{myVariable}}, if you use them in ng-model="myVariable", ng-show="myVariable", etc. Directives like ng-click does not perform dirty checks, so that doesn't slow things down. The guys behind Angular recommends you not to use more than 2000 expressions on one page that will require repaints/dirty checks, as your performance will start do degrade after that. The limit of 2000 is something they've found while researching performance in Angular.
Note that just because you add a property on the scope, it doesn't mean that Angular will perform dirty checks on it. They have to be used in your template for dirty checks to be performed (but not for ng-click).
If I want maximum performance in my Angular app, what should I be aware of?
Like I mentioned above, try to keep the number of bound template expressions below 2000. And if you implement watches on your scope, make sure that the expression for the watch executes really quickly. This is an example of how you shouldn't do it:
$scope.items = [];
for (var i = 0; i < 1000; i++) {
$scope.items.push({prop1: 'val1', prop2: 'val2', prop3: 'val3'});
}
$scope.$watch('items', function() {
}, true);
By adding the true as the third argument to $watch, you're telling Angular to loop through $scope.items for every digest cycle to check if any property of the thousand items have changed, which will be costly both in time and memory.
What you should do instead is:
$scope.items = [];
for (var i = 0; i < 1000; i++) {
$scope.items.push({prop1: 'val1', prop2: 'val2', prop3: 'val3'});
}
$scope.$watch('items.length', function() {
});
That is, only check when $scope.items.length have changed. That expression will execute very quickly.
Original post:
If your question is "is it better to expose functions to the template than objects" then yes, you should be using functions as much as you can. That way you encapsulate the logic inside the controller instead of letting it bleed into your template. Take this example:
<div ng-show="shouldShow">Toggled div</div>
<button ng-click="shouldShow = !shouldShow">Toggle<button>
Here the template has a little too much knowledge about what's happening. Instead, this should be solved like this:
// controller
var shouldShow = false;
scope.toggle = function() {
shouldShow = !shouldShow;
}
scope.shouldShow = function() {
return shouldShow;
}
<!-- template -->
<div ng-show="shouldShow()">Toggled div</div>
<button ng-click="toggle()">Toggle<button>
By doing it like this it's trivial to expand the logic in the controller without touching the template. While your requirements right now might be to just toggle the div when you press the button, tomorrows requirements might be to update some other part of the application when that happens. And if you use a function instead, it's easy to add that logic inside the function without changing the template.
Functions on your scope have a bit more overhead than using properties, but that overhead probably won't be what's slowing down your app when that day comes. So use functions when they make sense.
But you should still try to keep your controllers as small as possible. If they grow to contain tons of functions/functionality, you should probably split up your controller into reusable directives.
Is it possible to test myInnerFunction below?
var val = function() {
var myInnerfunction = function(input) {
return input + ' I ADDED THIS';
};
return myInnerfunction('test value');
}();
Because myInnerFunction is essentially a private member of the anonymously executed outer function, it doesn't seem like it is testable from the outside.
You could intentionally expose a testing hook to the outside world, like possibly this:
var val = function() {
var myInnerfunction = function(input) {
return input + ' I ADDED THIS';
};
/* START test hook */
arguments.callee.__test_inner = myInnerFunction;
/* END test hook */
return myInnerfunction('test value');
}();
now, once val has been run at least once, you can reference val.__test_inner and call it with testable inputs.
The benefits to this approach:
1. you choose what is exposed and not (also a negative 'cause you have to REMEMBER to do so)
2. all you get is a copy-reference to the private method, so you can't accidentally change it, only use it and see what it produces
The drawbacks:
1. if the private member changes (or relies on) state of its host/parent function, it's harder for you to unit test around that, since you have to recreate or artificially control the host/parent state at the same time
2. As mentioned, these hooks have to be manually added
If you got really clever, you could have your build process look for blocks of comments like the above and remove the test hooks when creating your production build.
afaik unit testing does not concern about internal workings of things you test. The point is that you test the functionality ie: it does what it's supposed to do, not how it does it.
So if it uses an internal private member, it should not be testable...
You can test the external behavior that is observable. In this simple case you returned just the value of the inner function, but in a real world example you might combine the result of that inner function with something else. That combination is what you would test, not the direct outputs of the private method.
Trying to test the private method will make your code difficult to change and refactor, even when the external behavior is preserved. That said, I like to view unit tests not as extensive tests of your code, but simply providing an example of the API and how it behaves to different conditions. ;)
I think my answer for this is (like so many things) that I'm doing it wrong. What I've defined as a 'private' function is really something that needs to be tested. It was only private because I didn't want to expose it within a utilities api or something like that. But it could still be exposed through my application namespace.
So within the anonymous function that is executed on-dom-ready, I just attach my pre-defined functions as event handlers to the proper DOM hooks. The functions themselves, while not stored with my more open utilities functions, are still stored publicly within a package in my namespace associated with the DOM structure they are dealing with. This way I can get at them and test them appropriately.