"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.
From a recent discussion with the Angular guys on this very topic: For future-proofing reasons, you should not use $$phase
When pressed for the "right" way to do it, the answer is currently
$timeout(function() {
// anything you want can go here and will safely be run on the next digest.
})
I recently ran into this when writing angular services to wrap the facebook, google, and twitter APIs which, to varying degrees, have callbacks handed in.
Here's an example from within a service. (For the sake of brevity, the rest of the service -- that set up variables, injected $timeout etc. -- has been left off.)
window.gapi.client.load('oauth2', 'v2', function() {
var request = window.gapi.client.oauth2.userinfo.get();
request.execute(function(response) {
// This happens outside of angular land, so wrap it in a timeout
// with an implied apply and blammo, we're in action.
$timeout(function() {
if(typeof(response['error']) !== 'undefined'){
// If the google api sent us an error, reject the promise.
deferred.reject(response);
}else{
// Resolve the promise with the whole response if ok.
deferred.resolve(response);
}
});
});
});
Note that the delay argument for $timeout is optional and will default to 0 if left unset ($timeout calls $browser.defer which defaults to 0 if delay isn't set)
A little non-intuitive, but that's the answer from the guys writing Angular, so it's good enough for me!
Best Answer
There is no issue with the code you posted except
&iframeCallBack
should be&iframeCallback
. Otherwise consider changingdata-iframe-callback="testLoad()"
todata-iframe-call-back="testLoad()"
.The possible reason of the issue is that you have multiple directives that you create by copy-pasting an existing code of the directive and forgot to change the name of the directive to new one. Just search in your code for
iframeOnLoad
, I'm pretty sure you will find two directives with such name.It is happening because AngularJS allows to register multiple directives with the same name. This pattern may be used to extend existing functionality of directive. However, you can create only one isolated scope for one DOM element, therefore two directives with the same name that require isolated scope are contradicting.
Working example see here.
The same issue as yours reproduced here.