Subscribe to Property Change in AngularJS - javascript

In knockoutJS it is possible to subscribe to a change of an observable viewmodel property, e.g., like:
myViewModel.personName.subscribe(function(newValue) {
alert("The person's new name is " + newValue);
});
I am currently learning AngularJS, and I was wondering if there is an equivalent to this in AngularJS? I have tried searching for this, but without any luck.

The scope object in AngularJS has a special method called $watch for 'watching' scope properties.
It accepts a callback that receives the new and the old value of the model:
$scope.$watch('modelName', function(newValue, oldValue){
// Do anything you like here
});
The callback is on initialization and each time the model changes. Therefore it may be good to add an extra check for equality like this:
$scope.$watch('modelName', function(newValue, oldValue){
// Check if value has changes
if(newValue === oldValue){
return;
}
// Do anything you like here
});
This allows you to 'watch' your model and perform some action if needed.
One extra remark: if you're watching a model that contains an object, you should use an extra third parameter that tells AngularJS to compare both values by object equality and not by reference (as the reference would not change and thus not trigger the watcher) like this:
$scope.$watch('modelName', function(newValue, oldValue){
// Do anything you like here
}, true); // Add extra 'true' parameter to check for object equality
You can read more documentation on the AngularJS scope page.
Hope that helps!

For API calls and async data triggered by user actions or application events, you are better off using
$rootScope.$broadcast('updateSearch', value);
directly in the callback of the service function and leveraging it in your controllers like:
$scope.$on('updateSearch', function(event,value) {
$scope.search = value;
});

You can check when a property of a scope object changes using $watch.
http://docs.angularjs.org/api/ng.$rootScope.Scope#$watch

Related

How to trigger directive once again after response from the server

I have a directive that displays the logo of the selected component object.
<div avatar component-id="3"
component-object="Modio.selectedFacility"
image-url="Modio.selectedFacility.logo_url"
bind="true"
style="display: inline-block"
wrapper-style="{'display': 'inline-block' }"
allow-change-photo="true"
size="50"
change-photo="modioModal.showTeamProfileModal"
label="Profile">
</div>
When the user selects an object from the dropdown list, the method setselectedFacility is called from the service. In this method I call another method getFacilityLogoUrl that sends a request to the server to get the logo and as long as there is no response, a directive is called and set default image. When I get the response the update of logo does not occur.
Directive seems doesn't know that something happened.
this.setSelectedFacility = function(facility) {
this.selectedFacility = facility;
if (this.selectedFacility) {
this.getFacilityLogoUrl(this.selectedFacility.id).then(function(url) {
_this.selectedFacility.logo_url = url;
}, modioException.errorFn);
}
//Save to local storage
localStorageService.set('selected-facility', this.selectedFacility);
};
How can I say to a directive to update an old value.
Watcher in my directive:
if (scope.bind) {
scope.$watch('componentObject', function(newValue, oldValue){
if (newValue) {
render();
}
});
} else {
render();
}
I'm not certain because you haven't posted the code for your selectedFacility watcher, but the problem could be that you are using a shallow watcher, but you need a deep watcher, because you are watching object properties.
Try using the third argument to the $watch function. For example:
$scope.$watch('selectedFacility', function (newVal, oldVal) { /*...*/ }, true);
By default, $scope.$watch() will only check for value changes, where the value for primitives is their value, and the value for objects is their reference. Therefore, default watches will only trigger when the object being watched is reassigned.
For objects and arrays you can use a deep watcher, as above, or $scope.$watchCollection, which will watch for reference changes one level deep.
Another solution is to use immutable objects, so that all changes to an object require creation of a new object (and therefore a reference change.) This works very well with AngularJS and Angular's dirty-checking change-detection strategy.

Why is $doCheck called twice when the view changes, and what can I do about it?

In the following Angular 1.7.5 code, I hook up a text box to a certain controller field with ng-model and use $doCheck to do something whenever the value changes. Note that in my actual project, the value I'm watching is an object and I need to find out whenever any of its fields change, so I have to use $doCheck. I know it's overkill for a textbox.
HTML
<div ng-app="app" ng-controller="TheController as ctrl">
<input ng-model="ctrl.value" />
</div>
JS
angular.module("app", [])
.controller("TheController",
class TheController
{
constructor()
{
this.value = "";
}
$doCheck()
{
console.log("check: " + this.value);
}
}
);
Whenever the value in the text box changes, you can see that $doCheck is called twice (both times with the new value). Why is this? How can I filter out the "pointless" $doCheck calls and just get the ones where the value actually changed?
The $digest loop keeps iterating until the model stabilizes, which means that the $evalAsync queue is empty and the $watch list does not detect any changes.
This means that $doCheck will called a minimum of two times. Once to detect changes and again to detect that there are no new changes. If there are more changes or functions added to the $evalAsync list. It will be called again and again to the limit set by $rootScopeProvider.digestTtl(limit).
If detecting changes, you must store the previous value(s) for comparison to the current values.
this.$doCheck = function() {
var newValue;
var oldValue;
oldValue = newValue;
newValue = itemOfInterest();
if (newValue != oldValue) {
//Code to act on changes
};
};
For more information, see
AngularJS Developer Guide - Integration with the browser event loop
AngularJS $compile Service API Reference - Life-cycle Hooks

Attach ngChange to all inputs?

Maybe I'm doing this wrong so please tell me...
I have several views that use one Controller, we'll call it the Page controller.
Inside the views, I have a pile of input/selects/textarea elements. When they change, I want to execute a function inside the controller scope. Now, I know I can apply ng-change attribute to every single input but we're talking... many inputs.
In my pre-angular days, I would just do something like
$("#pageOne input").on("change", function(){
parentScopeFunction();
});
I've been trying to get the
$scope.$watch()
to work but I can't seem to get it to work... Here's some code examples of what I'm trying to achieve.
I feel like I'm missing something about how $watch works. In my real application, that Device service is a connection with a web socket. But I've simplified it for this question.
https://jsfiddle.net/t1pcgkux/5/
Your issue is due to the shallow watching done on the object. It will not track for any changes in the properties of the object unless you set it for deep watch using the third argument in the watch function. Watch iterator function will run only if both the object (references) are different, which does not happen in your case since it is just property change.
$scope.$watch(function () {
return $scope.Device.stageDevice;
}, function (n, o) {
console.log("Device changed", n, o); // This never seems to happen.
}, true); //<-- here
But you really do not need to add a watch instead you could use ng-change and bind to a function on the scope as well.
Example:-
$scope.deviceChange = function(){
console.log("Device changed", $scope.Device.stageDevice);
}
and
<input class="form-control" ng-model="Device.stageDevice.name"
ng-change="deviceChange()"/>
<textarea class="form-control" ng-model="Device.stageDevice.desc"
ng-change="deviceChange()"></textarea>
Both examples Demo

X-Editable & Backbone model previousAttributes()

What I have:
I have x-editable popup which should check input parameters with help of validate method
$(".vacancy-edit-select-status").editable({
value: $this.model.get('value').id,
source: source,
validate: function(value) {
$this.model.previousAttributes(); //UNDEFINED!!!
//....
},
success: function (response, newValue) {
$this.model.set('value', newValue);
}
});
What a problem:
Inside validate method previousAttributes() method return 'undefined'. (I've changed some model attributes before change x-editable popup's variables, so the history shouldn't be empty)
Question:
How can I access model history from validate method?
previousAttributes populating during set, validate calling before population of previousAttributes - reference
The answer is simple and maybe not clear enough from my question:
Backbone clones model state only on change event.
Moreover, when you have a collection as an model attribute there are also some nuances with previousAttributes. For example, to write attribute change to the history you have to rewrite the whole attribute. I mean
this.model.get('myCommentsCollection').push('newComment'); //DOESN'T WRITE ANYTHING TO previousAttributes
So to write something to model.previousAttributes you should do:
var comments = _.clone(this.model.get("comments"));
this.model.set('myCommentsCollection', comments);

Why does my ko computed observable not update bound UI elements when its value changes?

I'm trying to wrap a cookie in a computed observable (which I'll later turn into a protectedObservable) and I'm having some problems with the computed observable. I was under the opinion that changes to the computed observable would be broadcast to any UI elements that have been bound to it.
I've created the following fiddle
JavaScript:
var viewModel = {};
// simulating a cookie store, this part isnt as important
var cookie = function () {
// simulating a value stored in cookies
var privateZipcode = "12345";
return {
'write' : function (val) { privateZipcode = val; },
'read': function () { return privateZipcode; }
}
}();
viewModel.zipcode = ko.computed({
read: function () {
return cookie.read();
},
write: function (value) {
cookie.write(value);
},
owner: viewModel
});
ko.applyBindings(viewModel);?
HTML:
zipcode:
<input type='text' data-bind="value: zipcode"> <br />
zipcode:
<span data-bind="text: zipcode"></span>?
I'm not using an observable to store privateZipcode since that's really just going to be in a cookie. I'm hoping that the ko.computed will provide the notifications and binding functionality that I need, though most of the examples I've seen with ko.computed end up using a ko.observable underneath the covers.
Shouldn't the act of writing the value to my computed observable signal the UI elements that are bound to its value? Shouldn't these just update?
Workaround
I've got a simple workaround where I just use a ko.observable along side of my cookie store and using that will trigger the required updates to my DOM elements but this seems completely unnecessary, unless ko.computed lacks the signaling / dependency type functionality that ko.observable has.
My workaround fiddle, you'll notice that the only thing that changes is that I added a seperateObservable that isn't used as a store, its only purpose is to signal to the UI that the underlying data has changed.
// simulating a cookie store, this part isnt as important
var cookie = function () {
// simulating a value stored in cookies
var privateZipcode = "12345";
// extra observable that isnt really used as a store, just to trigger updates to the UI
var seperateObservable = ko.observable(privateZipcode);
return {
'write' : function (val) {
privateZipcode = val;
seperateObservable(val);
},
'read': function () {
seperateObservable();
return privateZipcode;
}
}
}();
This makes sense and works as I'd expect because viewModel.zipcode depends on seperateObservable and updates to that should (and does) signal the UI to update. What I don't understand, is why doesn't a call to the write function on my ko.computed signal the UI to update, since that element is bound to that ko.computed?
I suspected that I might have to use something in knockout to manually signal that my ko.computed has been updated, and I'm fine with that, that makes sense. I just haven't been able to find a way to accomplish that.
sigh, I found someone with my exact same problem
If dependentObservables don't notifySubscribers on write, why do they
even bother to do it on read? They get added to the observables list
and subscribed to, but then they never trigger on updates. So what is
the point of subscribing to them at all?
Ryan Niemeyer answers:
I think that for your scenario, dependentObservables may not be the
right tool for the job. dependentObservables are set up to detect
dependencies in the read function and re-evaluate/notify whenever any
of those dependencies change. In a writeable dependentObservable, the
write function is really just a place to intercept the write and allow
you to set any observables necessary, such that your read function
would return the proper value (write is typically the reverse of read
in most cases, unless you are transforming the value).
For your case, I would personally use an observable to represent the
value and then a manual subscription to that observable to update the
original value (the one that you may not have control over).
It would be like: http://jsfiddle.net/rniemeyer/Nn5TH/
So it looks like this fiddle would be a solution
var viewModel = {};
// simulating a cookie store, this part isnt as important
var cookie = function () {
// simulating a value stored in cookies
var privateZipcode = "12345";
return {
'write' : function (val) {
console.log("updated cookie value with: " + val);
privateZipcode = val;
},
'read': function () {
return privateZipcode;
}
}
}();
viewModel.zipcode = ko.observable(cookie.read());
// manually update the cookie when the observable changes
viewModel.zipcode.subscribe(function(newValue) {
cookie.write(newValue);
});
ko.applyBindings(viewModel);​
That makes sense and its somewhat simpler to use. Overall I'm not sure how great of an idea it is to treat a cookie as an observable since the server could edit it in an ajax request, etc.
Try making your internal privatezipcode an observable. See here: http://jsfiddle.net/KodeKreachor/fAGes/9/

Categories