In the "Create Components" section of AngularJS's homepage, there is this example:
controller: function($scope, $element) {
var panes = $scope.panes = [];
$scope.select = function(pane) {
angular.forEach(panes, function(pane) {
pane.selected = false;
});
pane.selected = true;
}
this.addPane = function(pane) {
if (panes.length == 0) $scope.select(pane);
panes.push(pane);
}
}
Notice how the select method is added to $scope, but the addPane method is added to this. If I change it to $scope.addPane, the code breaks.
The documentation says that there in fact is a difference, but it doesn't mention what the difference is:
Previous versions of Angular (pre 1.0 RC) allowed you to use this interchangeably with the $scope method, but this is no longer the case. Inside of methods defined on the scope this and $scope are interchangeable (angular sets this to $scope), but not otherwise inside your controller constructor.
How does this and $scope work in AngularJS controllers?
"How does this and $scope work in AngularJS controllers?"
Short answer:
this
When the controller constructor function is called, this is the controller.
When a function defined on a $scope object is called, this is the "scope in effect when the function was called". This may (or may not!) be the $scope that the function is defined on. So, inside the function, this and $scope may not be the same.
$scope
Every controller has an associated $scope object.
A controller (constructor) function is responsible for setting model properties and functions/behaviour on its associated $scope.
Only methods defined on this $scope object (and parent scope objects, if prototypical inheritance is in play) are accessible from the HTML/view. E.g., from ng-click, filters, etc.
Long answer:
A controller function is a JavaScript constructor function. When the constructor function executes (e.g., when a view loads), this (i.e., the "function context") is set to the controller object. So in the "tabs" controller constructor function, when the addPane function is created
this.addPane = function(pane) { ... }
it is created on the controller object, not on $scope. Views cannot see the addPane function -- they only have access to functions defined on $scope. In other words, in the HTML, this won't work:
<a ng-click="addPane(newPane)">won't work</a>
After the "tabs" controller constructor function executes, we have the following:
The dashed black line indicates prototypal inheritance -- an isolate scope prototypically inherits from Scope. (It does not prototypically inherit from the scope in effect where the directive was encountered in the HTML.)
Now, the pane directive's link function wants to communicate with the tabs directive (which really means it needs to affect the tabs isolate $scope in some way). Events could be used, but another mechanism is to have the pane directive require the tabs controller. (There appears to be no mechanism for the pane directive to require the tabs $scope.)
So, this begs the question: if we only have access to the tabs controller, how do we get access to the tabs isolate $scope (which is what we really want)?
Well, the red dotted line is the answer. The addPane() function's "scope" (I'm referring to JavaScript's function scope/closures here) gives the function access to the tabs isolate $scope. I.e., addPane() has access to the "tabs IsolateScope" in the diagram above because of a closure that was created when addPane() was defined. (If we instead defined addPane() on the tabs $scope object, the pane directive would not have access to this function, and hence it would have no way to communicate with the tabs $scope.)
To answer the other part of your question: how does $scope work in controllers?:
Within functions defined on $scope, this is set to "the $scope in effect where/when the function was called". Suppose we have the following HTML:
<div ng-controller="ParentCtrl">
<a ng-click="logThisAndScope()">log "this" and $scope</a> - parent scope
<div ng-controller="ChildCtrl">
<a ng-click="logThisAndScope()">log "this" and $scope</a> - child scope
</div>
</div>
And the ParentCtrl (Solely) has
$scope.logThisAndScope = function() {
console.log(this, $scope)
}
Clicking the first link will show that this and $scope are the same, since "the scope in effect when the function was called" is the scope associated with the ParentCtrl.
Clicking the second link will reveal this and $scope are not the same, since "the scope in effect when the function was called" is the scope associated with the ChildCtrl. So here, this is set to ChildCtrl's $scope. Inside the method, $scope is still the ParentCtrl's $scope.
Fiddle
I try to not use this inside of a function defined on $scope, as it becomes confusing which $scope is being affected, especially considering that ng-repeat, ng-include, ng-switch, and directives can all create their own child scopes.
The reason 'addPane' is assigned to this is because of the <pane> directive.
The pane directive does require: '^tabs', which puts the tabs controller object from a parent directive, into the link function.
addPane is assigned to this so that the pane link function can see it. Then in the pane link function, addPane is just a property of the tabs controller, and it's just tabsControllerObject.addPane. So the pane directive's linking function can access the tabs controller object and therefore access the addPane method.
I hope my explanation is clear enough.. it's kind of hard to explain.
I just read a pretty interesting explanation on the difference between the two, and a growing preference to attach models to the controller and alias the controller to bind models to the view. http://toddmotto.com/digging-into-angulars-controller-as-syntax/ is the article.
NOTE: The original link still exists, but changes in formatting have made it hard to read. It's easier to view in the original.
He doesn't mention it but when defining directives, if you need to share something between multiple directives and don't want a service (there are legitimate cases where services are a hassle) then attach the data to the parent directive's controller.
The $scope service provides plenty of useful things, $watch being the most obvious, but if all you need to bind data to the view, using the plain controller and 'controller as' in the template is fine and arguably preferable.
I recommend you to read the following post:
AngularJS: "Controller as" or "$scope"?
It describes very well the advantages of using "Controller as" to expose variables over "$scope".
I know you asked specifically about methods and not variables, but I think that it's better to stick to one technique and be consistent with it.
So for my opinion, because of the variables issue discussed in the post, it's better to just use the "Controller as" technique and also apply it to the methods.
In this course(https://www.codeschool.com/courses/shaping-up-with-angular-js) they explain how to use "this" and many other stuff.
If you add method to the controller through "this" method, you have to call it in the view with controller's name "dot" your property or method.
For example using your controller in the view you may have code like this:
<div data-ng-controller="YourController as aliasOfYourController">
Your first pane is {{aliasOfYourController.panes[0]}}
</div>
Previous versions of Angular (pre 1.0 RC) allowed you to use this
interchangeably with the $scope method, but this is no longer the
case. Inside of methods defined on the scope this and $scope are
interchangeable (angular sets this to $scope), but not otherwise
inside your controller constructor.
To bring back this behaviour (does anyone know why was it changed?) you can add:
return angular.extend($scope, this);
at the end of your controller function (provided that $scope was injected to this controller function).
This has a nice effect of having access to parent scope via controller object that you can get in child with require: '^myParentDirective'
$scope has a different 'this' then the controller 'this'.Thus if you put a console.log(this) inside controller it gives you a object(controller) and this.addPane() adds addPane Method to the controller Object. But the $scope has different scope and all method in its scope need to be accesed by $scope.methodName().
this.methodName() inside controller means to add methos inside controller object.$scope.functionName() is in HTML and inside
$scope.functionName(){
this.name="Name";
//or
$scope.myname="myname"//are same}
Paste this code in your editor and open console to see...
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>this $sope vs controller</title>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.7/angular.min.js"></script>
<script>
var app=angular.module("myApp",[]);
app.controller("ctrlExample",function($scope){
console.log("ctrl 'this'",this);
//this(object) of controller different then $scope
$scope.firstName="Andy";
$scope.lastName="Bot";
this.nickName="ABot";
this.controllerMethod=function(){
console.log("controllerMethod ",this);
}
$scope.show=function(){
console.log("$scope 'this",this);
//this of $scope
$scope.message="Welcome User";
}
});
</script>
</head>
<body ng-app="myApp" >
<div ng-controller="ctrlExample">
Comming From $SCOPE :{{firstName}}
<br><br>
Comming from $SCOPE:{{lastName}}
<br><br>
Should Come From Controller:{{nickName}}
<p>
Blank nickName is because nickName is attached to
'this' of controller.
</p>
<br><br>
<button ng-click="controllerMethod()">Controller Method</button>
<br><br>
<button ng-click="show()">Show</button>
<p>{{message}}</p>
</div>
</body>
</html>
I created a directive, I´m using in my template:
<data-input> </data-input>
In my directive (dataInput) I have the template (...data-input.html).
In that template the html-code says:
<input ng-change="dataChanged()" ... > ...
In the JavaScript it says:
scope.dataChanged= function(){ //change the data }
Works perfectly, but know I need to safe the changed data to a variable in my controller (that has a different scope of course).
The template, where I put the <data-input> </data-input> belongs to the final scope, I´m trying to reach.
I tried it via parameter, but it didnt work.
Thanks
Here are your options:
You can nest controllers if possible. This will create scope inheritance and you will be able to share variables of the parent.
You can use $rootscope from both the controllers to share data. Your dataChanged function can save anything to the $rootscope
You can use angular.element to get the scope of any element. angular.element('data-input').scope() returns it's cope.
This is not recommended. But there are circumstances in which people use global space to communicate between angular and non-angular code. But I don't think this is your case.
You can use Angular Watches to see changes is some variable, like this
eg:
$scope.$watch('age + name', function () {
//called when variables 'name' or 'age' changed
//Or you can use just 'age'
});
With standard controller syntax in AngularJS, you can watch a variable like:
$scope.$watch(somethingToWatch, function() { alert('It changed!'); });
Using the controllerAs syntax, I want to react to this change in an active controller. What's the easiest way to do this?
More detail, if it helps. I have one controller in a side pane that controls the context of the application (user selection, start time, end time, etc.). So, if the user changes to a different context, the main view should react and update. I'm storing the context values in a factory and each controller is injecting that factory.
You can always use a watcher evaluator function, especially helpful to watch something on the controller instance or any object. You can actually return any variable for that matter.
var vm = this;
//Where vm is the cached controller instance.
$scope.$watch(function(){
return vm.propToWatch;
}, function() {
//Do something
}, true);//<-- turn on this if needed for deep watch
And there are also ways to use bound function to bind the this context.
$scope.$watch(angular.bind(this, function(){
return this.propToWatch;
//For a variable just return the variable here
}), listenerFn);
or even ES5 function.bind:
$scope.$watch((function(){
return this.propToWatch;
}).bind(this), listenerFn);
If you are in typescript world it gets more shorter.
$scope.$watch(()=> this.propToWatch, listenerFn);
Eventhough you can watch on the controller alias inside the controller ($scope.watch('ctrlAs.someProp'), it opens up couple of problems:
It predicts (or in other words pre-determines) the alias used for the controller in the view/route/directive/modal or anywhere the controller is used. It destroys the purpose of using controllerAs:'anyVMAlias' which is an important factor in readability too. It is easy to make typo and mistakes and maintenance headache too since using the controller you would need to know what name is defined inside the implementation.
When you unit test the controller (just the controller), you need to again test with the exact same alias defined inside the controller (Which can probably arguably an extra step if you are writing TDD), ideally should not need to when you test a controller.
Using a watcher providing watcher function against string always reduced some steps the angular $parse (which watch uses to create expression) internally takes to convert the string expression to watch function. It can be seen in the switch-case of the $parse implementation
I've searched all over the internet and cannot find a solution please help!
directive('menu',function(){
return{
link : function(scope,element,attrs){
scope.foo = function(){
alert('test!');
}
},
controller : function($scope){
$scope.foo();
}
}
});
Delay the call to foo() using $evalAsync():
controller : function($scope){
$scope.$evalAsync(function() {
$scope.foo();
console.log($scope);
});
}
fiddle
You could also use $timeout() instead of $evalAsync(). Both allow the link function to execute first.
As Ye Liu said, your controller calls your directive's compile and then link functions.
From the angular directive doc (http://docs.angularjs.org/guide/directive):
The controller is instantiated before the pre-linking phase
The controller will be within the scope of your app, and once the post-link function finishes, your directive will be a child of this scope. Consider that the link function's purpose is to bind model data to your template and set watches for bound variables, not to create a discreet 'directive object'.
If you are trying to set the foo function inside of the link function in order to access directive scope variables, take a look at directive delegate functions and bound variables in the "scope:" directive attribute. The angular directive tutorial gives a somewhat obtuse version of this as its final example ("zippy"), and Angularjs Directive Delegate not firing through intermediary handler gives an example of a delegate function you can invoke from your template itself.
How to set ng-controller as an expression from the $scope?
According to the documentation:
ngController – {expression} – Name of a globally accessible
constructor function or an expression that on the current scope
evaluates to a constructor function.
But how to evaluate scope expression as a controller for controllers that have been registered with module .controller?
For example:
Layout:
<div ng-controller="myExpr"></div>
JavaScript (define controller):
app.controller('myCtrl', ['$scope', '$timeout', function () { ... }];
JavaScript (parent scope):
$scope.myExpr = ...;
What should be in myExpr to use myCtrl as a controller via expression?
I've tried $controller('myCtrl')... not working...
P.S. If controller has been defined via globally accessible function.. it's possible to provide it as myExpr. But what to do if it has been defined so?
The expressions that ng-controller accept are a bit wierd. So you can do this by writing your controller slightly differently (but read below as for why you probably don't want to).
function myCtrl($scope) {
$scope.value = 'Stuff';
}
This is a controller and will work like normal for this case. Like in this example: http://jsbin.com/ubevel/2/edit
So why not do it?
First of all this is not a good way to define things from a testing perspective. Secondly, this allows you to set the controller dynamically on load but it won't allow you to change things after that. If you change the value of myExpr after the page has loaded the controller will not change.
So what to do?
I would highly suggest looking at using a service instead. Swap out your actions by supplying your outer controller with a service that you then change in the same manner you are now trying to change the inner controller. So something like: http://jsbin.com/ubevel/5/edit
This service can be swapped out on the fly, changing it will change the actions that are available in the scope.
You could also use an include, but this would result in duplicate html. I personalty am fine with this since I am against reusing html for two different types objects (sooner or later you want to change one but not the other and it becomes a mess). But a lot of people would object to that.
An extra note: There are probably nicer ways to do it with controllers, I probably haven't looked at all angles, but I just don't think controllers are the right tool for this case.