I'm trying to write a script to access the rootscope and change a variables value from the dev tools. I can get the scope and the rootscope, but my changes don't show up in my bindings. However if I use a bound input everything is fine.
Example:
<input ng-model="mode"></input>
<span ng-bind-html="mode"></span>
update each other fine but
angular.element("body").scope().$root.mode = 'test'
Updates the controller but has no effect on the bindings. I have compared my original controller and my console controller using === and they are equal.
So from what I read I need to broadcast or emit that I've changed the scope, however I can't seem to figure out what to broadcast , or if I am broadcasting correctly.
Broadcast code attempts:
scope.$apply(function() {root.$emit('change', {mode: 1})})
scope.$apply(function() {root.$emit('stateChangeSuccess', {mode: 1})})
root.$apply(function() {root.$emit('change', {mode: 1})})
root.$apply(function() {root.$emit('stateChangeSuccess', {mode: 1})})
Additionally it should be noted that changing the input will change the span but not my copy of the root scope.
root.mode = 'console'
type 'input text' in the input
root.mode returns 'console'
I don't think this is possible with events (perhaps I'm wrong). There is another way to do make changes from the console though.
The reason angular.element("body").scope().$root.mode = 'test' doesn't update your bindings is that Angular doesn't "know" that a change to the scope has been made. If you are making updates to your data model from "outside" the Angular world, you will need to wrap you changes in an $apply() function:
var $rootScope = angular.element("body").scope().$root;
$rootScope.$apply(function() {
$rootScope.mode = 'hi';
});
FYI, when sending an event from the $rooteScope, you'll want to use $broadcast instead of $emit. $broadcast broadcasts an event "downward" to all descendant scopes, while $emit sends the event "upwards" to ancestor scopes. If you $emit from the $rootScope, there are no ancestor scopes to pick up the event (though perhaps the $rootScope will?).
If I understand you correctly, scope() === $root, otherwise you're making some false assumptions here.
WHat I believe is happening is that you are changing value of root scope but your bindings are actually using another scope due to prototype inheritance(eg the string property is actually created in scope() when you bind against it).
Try either of these two tricks:
1) Either try scope.$apply(function(){scope.mode ='test'});
2) $root.mode={name:'test'}; and bind against mode.name in html
Notice that you should be able to fire a $digest cycle using $apply on root scope and it should propagate down to child scopes too. $root.$apply();
Related
Tried to find some basic information for AngularJS $rootScope.$broadcast, But the AngularJS documentation doesn't help much. In easy words why do we use this?
Also, inside John Papa's Hot Towel template there is a custom function in the common module named $broadcast:
function $broadcast() {
return $rootScope.$broadcast.apply($rootScope, arguments);
}
I did not understand what this is doing. So here are couple of basic questions:
1) What does $rootScope.$broadcast do?
2) What is the difference between $rootScope.$broadcast and $rootScope.$broadcast.apply?
$rootScope basically functions as an event listener and dispatcher.
To answer the question of how it is used, it used in conjunction with rootScope.$on;
$rootScope.$broadcast("hi");
$rootScope.$on("hi", function(){
//do something
});
However, it is a bad practice to use $rootScope as your own app's general event service, since you will quickly end up in a situation where every app depends on $rootScope, and you do not know what components are listening to what events.
The best practice is to create a service for each custom event you want to listen to or broadcast.
.service("hiEventService",function($rootScope) {
this.broadcast = function() {$rootScope.$broadcast("hi")}
this.listen = function(callback) {$rootScope.$on("hi",callback)}
})
What does $rootScope.$broadcast do?
$rootScope.$broadcast is sending an event through the application scope.
Any children scope of that app can catch it using a simple: $scope.$on().
It is especially useful to send events when you want to reach a scope that is not a direct parent (A branch of a parent for example)
!!! One thing to not do however is to use $rootScope.$on from a controller. $rootScope is the application, when your controller is destroyed that event listener will still exist, and when your controller will be created again, it will just pile up more event listeners. (So one broadcast will be caught multiple times). Use $scope.$on() instead, and the listeners will also get destroyed.
What is the difference between $rootScope.$broadcast & $rootScope.$broadcast.apply?
Sometimes you have to use apply(), especially when working with directives and other JS libraries. However since I don't know that code base, I wouldn't be able to tell if that's the case here.
$rootScope.$broadcast is a convenient way to raise a "global" event which all child scopes can listen for. You only need to use $rootScope to broadcast the message, since all the descendant scopes can listen for it.
The root scope broadcasts the event:
$rootScope.$broadcast("myEvent");
Any child Scope can listen for the event:
$scope.$on("myEvent",function () {console.log('my event occurred');} );
Why we use $rootScope.$broadcast? You can use $watch to listen for variable changes and execute functions when the variable state changes. However, in some cases, you simply want to raise an event that other parts of the application can listen for, regardless of any change in scope variable state. This is when $broadcast is helpful.
Passing data !!!
I wonder why no one mention that $broadcast accept a parameter where you can pass an Object
Example:
// the object to transfert
var obj = {
status : 10
}
$rootScope.$broadcast('status_updated', obj);
$scope.$on('status_updated', function(event, obj){
console.log(obj.status); // 10
})
What does $rootScope.$broadcast do?
It broadcasts the message to respective listeners all over the angular app, a very powerful means to transfer messages to scopes at different hierarchical level(be it parent , child or siblings)
Similarly, we have $rootScope.$emit, the only difference is the former is also caught by $scope.$on while the latter is caught by only $rootScope.$on .
refer for examples :- http://toddmotto.com/all-about-angulars-emit-broadcast-on-publish-subscribing/
I have a service in Angular that has one method in there - say GetName() that returns someones name
I want in my html to be able to do something like {{ $scope.GetName() }} and have this bound to my method result. My main issue is if the name in my service changes (through another controller) then I would like my {{ ..... }} change.
Hopefully this makes sense.
Regards,
Yannis
You can't bind dynamic service calls to a DOM element.
Problably the best way would be to $watch for the name changes within the service, and use a callback function on change. You'd also need a way to notify the controller that the name has changed, so an event listener should also be set up. The listener would call the function that will update the scope variable, say $scope.nameFromService. And then in your HTML you'd need only this inside the controller part:
{{nameFromService}}
Again, to make it clear, this is what happens:
another controller changes the value in the service
$watch gets triggered and calls a function that (among other things) triggers an event
the event listener in your controller receives the event and updates the scope variable
Angular automatically updates the DOM
UPDATE
If you can change your service, I'd actually avoid the whole $watch thing and just modify the name setter function so it triggers an event when called. That way, all your controllers can be aware of the name changes.
I created a custom directive with an isolate scope that uses two-way data binding back to the parent scope. The scope/bindings all appear to be working correctly, but the template/view is not automatically updating the bound properties in the dom when they change. I can force the dom to update by reading the directive's model.
http://plnkr.co/edit/vPGm1oO0sSaHVvcrp2Ev
Note: In this plunkr example I use am using the isActive property on the wiglet 1's scope as the bound property. Note that I print the value to the console when the scope is created and also when it is updated 2 seconds later via a window.timeout... so you can see at this point that while the data has changed, the dom has not. To see the dom change click the 'print' button on either of the wiglets, which simply prints the value of isActive to console again. This causes the dom to update.
Is this a bug, or am I doing something wrong?
AngularJS only updates bound variables during a scope digest cycle; this normally happens automatically for Angular managed events (ng-click, etc.), but in asynchronous code, such as a setTimeout, you must manually call Scope#$apply() or Scope#$digest():
$window.setTimeout(function(){
$scope.$apply(function() {
$scope.wiglets[0].isActive = true;
console.log("Wiglet 1 isActive:", $scope.wiglets[0].isActive);
});
}, 2000);
This is so common with setTimeout that AngularJS has a built in service, called $timeout, that does this for you:
$timeout(function(){
$scope.wiglets[0].isActive = true;
console.log("Wiglet 1 isActive:", $scope.wiglets[0].isActive);
}, 2000);
Example: http://plnkr.co/edit/XDK06uhYMNcI6fs6SuHP?p=preview
I'm having an issue getting a watch to work within a directive. I've put together a simple example here. http://plnkr.co/edit/A7zbrsh8gJhdpM30ZH2P
I have a service and two directives. One directive changes a property in the service, and another directive has a watch on that property. I expected the watch to fire when the property is changed but it doesn't.
I've seen a few other questions like this on the site, but the accepted solutions on them have not worked here. I've considered using $broadcast or trying to implement an observer, but it seems like this should work and I don't want to over complicate things if possible.
Mark Rajcok' answer is incomplete. Even with angular.copy(), $watch listener will be called once and never again.
You need to $watch a function:
$scope.$watch(
// This is the important part
function() {
return demoService.currentObject;
},
function(newValue, oldValue) {
console.log('demoService.currentObject has been changed');
// Do whatever you want with demoService.currenctObject
},
true
);
Here the plunker that works: http://plnkr.co/edit/0mav32?p=preview
Open your browser console to see that both the directive and the demoService2 are notified about demoService.currentObject changes.
And btw angular.copy() is not even needed in this example.
Instead of
this.currentObject = newObject;
use
angular.copy(newObject, this.currentObject);
With the original code, the viewer directive is watching the original object, {}. When currentObject is set to newObject, the $watch is still looking for a change to the original object, not newObject.
angular.copy() modifies the original object, so the $watch sees that change.
I would like to subscribe to an ngChange event, but from code rather than the markup. That is, given a $scope and an expression that is bindable via ngModel, I want to subscribe to any changes made to that expression by any ngModel directive that binds to that expression. Is this possible?
something like:
$scope.field = "hello";
$scope.onButtonClick = function() {
$scope.field = "button clicked!";
}
// this callback is only when the user types in an input bound to field
// not when they click the button with ng-click="onButtonClick()"
$scope.$watchNgChange("field", function() {
console.log("user caused field to change via a binding");
});
// this callback is called for both ngModel binding changes and onButtonClick.
$scope.$watch("field", function() {
console.log("field was changed");
});
I can't just use $watch, because that will capture all changes, including those from loading the data from the database, from ng-click callbacks, and changes initiated from $watch callbacks for other expressions (in this case, if there are any circular references, then it's too easy to have $watch callbacks to get into an infinite loop and error out after 10 digest cycles), and who knows what else.
First, anytime I have tried to do something like this is turned out to be a bad idea - I was just working around design problems in my code or logic problems in my business logic. In general, the code should not care HOW the data was changed, only that it HAS changed.
Second, $watch can give you both the old and new value - this has been enough for me. if the old value not equal to the new value, I want to update the related data model(s). If that old and new value are equal, I want to ignore the update.
Finally, You may consider using resolve with your routes eliminate "database loading" as the fully located data can be passed into your controller (assuming you return a promise).
.
Jeremy you just describe what in Angular is known as a Directive. It is best practice to always use directives each time you need to touch the DOM. This logic should never live in the controller or even the Service.
directives are a big tricky but there is tons of documentation for it.
Visit docs.angularjs.org/directives
Don't do it, it sounds like you are trying to introduce the DOM logic (e.g. if the user has interacted with a DOM element) into the controller.
If you read the source code of a ngChange directive, you will found it requires a ngModel which is used as the bridge between the view and the controller.
I recommend creating a copy of the model and used the copy for data binding using ngModel+ngChange in your view, and then you can $watch that copy and do whatever you want.
$scope.field = "hello"; //the field you care
$scope.fieldCopy = $scope.field; //use 'fieldCopy' for databinding
In the html code you can have multiple way of changing the model fieldCopy
<input ngModel="fieldCopy" name='foo' />
<input ngModel="fieldCopy" name='foo2' />
You then watch the fieldCopy for changes related to user interaction and copy the change to 'field':
$scope.$watch("fieldCopy", function() {
$scope.field = $scope.fieldCopy;
console.log("user caused field to change via a binding");
});
If you want to keep fieldCopy in sync with field, add another watch:
$scope.$watch("field", function() {
$scope.fieldCopy = $scope.field;
});