Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AngularJS: When to pass $scope variable to function

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?

like image 951
Adam Avatar asked Sep 23 '13 19:09

Adam


People also ask

What is the use of $scope variable in AngularJS?

The $scope in an AngularJS is a built-in object, which contains application data and methods. You can create properties to a $scope object inside a controller function and assign a value or function to it. The $scope is glue between a controller and view (HTML).

What is the difference between $scope and scope in AngularJS?

The $ in "$scope" indicates that the scope value is being injected into the current context. $scope is a service provided by $scopeProvider . You can inject it into controllers, directives or other services using Angular's built-in dependency injector: module.

What is difference between $scope and $rootScope object?

The main difference is the availability of the property assigned with the object. A property assigned with $scope cannot be used outside the controller in which it is defined whereas a property assigned with $rootScope can be used anywhere.

Is $scope still supported in angular2?

In Angular 2.0, there will be no $scope .


3 Answers

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
        }   
    }
like image 99
Khanh TO Avatar answered Oct 23 '22 09:10

Khanh TO


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.

like image 9
Erik Honn Avatar answered Oct 23 '22 08:10

Erik Honn


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.

like image 9
musically_ut Avatar answered Oct 23 '22 10:10

musically_ut