Accessing ngModel from controller and watch for changes - angularjs

i have make custom directive which is working good. It's plunker is at http://plnkr.co/edit/GxM78QRwSjTrsX1SCxF7?p=preview
in that directive there is ngModel call deptStation I want access it in controller so that i can use it as parameter in other function to make new array. I also want to watch it also so on every change I can call the function.
<plexus-select items="deptStations" header-text="Select station" text="Select departure..." text-icon="ion-chatbubble-working" text-field="City_Name_EN" text-field2="City_Code" value-field="City_Code" ng-model="deptStation">
</plexus-select>
I tried to write below code but it don't show console log
$scope.$watch('deptStation', function(newValue, oldValue) {
if(oldValue != newValue) {
// perform something
console.log('New Value ' + newValue);
}

I'm not exactly sure about ionic directives, but your issue is probably because one or more of the ionic directives creates a new scope, so just doing ng-model="deptStation" creates a new property in that scope rather than your controller's.
To avoid these issues you should make sure not to bind to primitives, but arrays/objects instead. You should create the property like so (renamed to selectedStation for clarity):
$scope.selectedStation = {value: null};
Then your watch will work:
$scope.$watch('selectedStation.value', function (station) {
console.log('watch', station);
});
http://plnkr.co/edit/96VgPzEXZuzxmHt32Afy?p=preview
As #m59 said though, it seems you'd be better just using two-way binding than ng-model.

Related

Alternative to $scope.watch in angular 1.5 x

currently I am using watch in angular Js ,but now we have upgraded to angular 1.5x and I don't want to use watch instead I am looking for some thing thing alternative to $scope.watch
I am using a global value defined in the App and I need some alternative to watch which is like a method to be called up when the value of the value is changed any where in the application.
There are basically 2 way of doeing without $watch...
1st) explicitly call the function For exemple if your variable is binded to a n input use ng-change to call the function you need.
2nd) use property setters. For exemple if you want to bind property p of your scope you can use this code in your controller:
var _p = 38;
Object.defineProperty(
$scope,
"p",
{
get : function(){ return _p; },
set : function(val){
_p = val;
callSomeFunction(); // Do what you have been doing with $watch
},
);
But as already said in comments $scope.$watch is not the "evil thing" to avoid

Angularjs watch input binding change for Array when using ControllerAs

I have an AngularJs component having bindings to heroes, which is an array. How to watch this input for array changes? I tried $scope.watch("heroes", ...) and $onChanges, but didn't work so far.
bindings: {
heroes: '<',
}
Here is my plunker: https://plnkr.co/edit/J8xeqEQftGq3ULazk8mS?p=preview
The ControllerAs structure needs a special watch expression, since the attributes are not on the $scope.
//This one works and is the best one (> AngularJs 1.5)
$scope.$watch("$ctrl.heroes.length", function () {
console.log("ControllerAs syntax"); // Triggers once on init
});
//This one works as well
var ctrl = this;
$scope.$watch(() => {
return ctrl.heroes.length;
}, (value) => {
console.log("complex watch"); // Triggers once on init
});
See example here: https://plnkr.co/edit/J8xeqEQftGq3ULazk8mS?p=preview
The issue occurs because $scope.$watch by default doesn't deeply watch objects. Which means since you never destroy/recreate your array, the reference doesnt really change therefore $scope.$watch doesnt see any change. If you watched heroes.length, that primitive would change and your $scope.$watch would fire the corresponding listening function. By using $scope.$watch with the true option you are telling the angular engine to deeply watch all properties.
This is pretty intensive to do for large objects because $scope.$watch using angular.copy to track changes
If you were to use $scope.$watchCollection angular would create a shallow copy and would be less memory intensive. So I feel your 3 main options are
Watch heroes.length , add true or use $watchCollection
I feel that using heroes.length would be your best bet, so the code would look like
$scope.$watch('heroes.length',function(){});
The other two options are described below
$scope.$watch('heroes',function(){
//do somthing
},true)
or
$scope.$watchCollection
The benefit of using watchCollection is, that it requires less memory to deeply watch an object.
Shallow watches the properties of an object and fires whenever any of
the properties change (for arrays, this implies watching the array
items; for object maps, this implies watching the properties). If a
change is detected, the listener callback is fired.
The obj collection is observed via standard $watch operation and is
examined on every call to $digest() to see if any items have been
added, removed, or moved. The listener is called whenever anything
within the obj has changed. Examples include adding, removing, and
moving items belonging to an object or array.

How can I get notifications of rootScope changes?

I've got a global variable in my rootScope for AngularJS which has properties updated in other various places (outside of angular). For example, lets say the property 'name' is updated on it. It seems like it updates on the root scope fine and after doing an apply or firing a controller function on any child function the view eventually updates, but this is a problem.
How can I get the controllers to update the templates to reflect the rootScope changes immediately?
How can I observe any changes whatsoever on this object, and invoke apply?
You can create a $watch as so:
// Assuming that $rootScope.name exists
$rootScope.$watch('name', function(newValue, oldValue)
{
// You have access to both the newValue
// and the oldValue
});
To $watch for objects, taking this from Angular's Site, a third boolean character is needed
objectEquality (optional) boolean: Compare object for equality rather
than for reference.
// Assuming that $rootScope.obj exists
$rootScope.$watch('obj', function(newValue, oldValue)
{
// You have access to both the newValue
// and the oldValue
}, true);
But know that it is not a good idea to use the $rootScope to store content. You should consider using a service or a factory for that purpose. Read more about this here.
Create a watch for it on rootscope ($rootScope.$watch('varName', function (newValue) {}[,true])).
The varName parameter accepts expressions with '.' for sub-objects and '[]' for array indexes. The third parameter indicates listening fire changes "inside" the object.

Changed value on AngularJS Service not triggering watch within directive

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.

$scope.$watch, but just for ngChange events?

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;
});

Resources