Angular $watch only triggered once with onclick - javascript

I have a function in an angular service containing an ajax call doing the following:
function performCall($scope){
$scope.dataFail = false;
var promise = doAjaxCall();
promise.then(function(data){
if(data.rootElement){
//process...
}else{
$scope.dataFail=true;
}
}
}
And a $watch expression in my controller watching dataFail and displaying a dialog with an option to call performCall when the dialog is confirmed:
$scope.$watch('dataFail', function(dataFail){
if(dataFail){
//open dialog
$( "#ajaxFailurePopup" ).dialog({
zIndex: 3003,
title: "Note:",
modal:true, // Disable controls on parent page
buttons: {
Ok: {
text: 'Retry >',
"class" : 'ppButton floatRight',
click:
function() {
service.performCall($scope);
$("#ajaxFailurePopup").remove();
}
}
}
});
};
});
This works fine on initialisation when the ajax call first fails. However, after this no changes made to dataFail are registered by the $watch. Does anybody have any suggestions?

Resolved by wrapping call to performCall in $scope.apply:
$scope.apply(
service.performCall($scope);
)
The dataFail flag was being set in the performCall method. Apologies.

There are couple of problems with your code:
In Angular, $watch expression callback function is called only if expression has changed the value. Since you're never resetting your 'dataFail' flag the expression never gets called in subsequent calls. You should set your flag variable to false inside the $watch expression.
In dialog you are calling $("#ajaxFailurePopup").remove(); which removes the #ajaxFailurePopup element from the DOM, hence the dialog is unable to initialize again. You should use $('#ajaxFailurePopup').dialog('destroy');
Working plnkr: http://embed.plnkr.co/wcooiJ

Since you say that the watch fires on the first failed request, but no on subsequent failed requests, is it possible that you're not resetting $scope.dataFail to false for successful requests? If $scope.dataFail is only set to false during initialization, the value never changes and your watch won't get called. Setting $scope.dataFail to true if it´s already true won't fire the watch since Angular can't tell that it changed.

Related

"setInitialFocusId" error after _oDialog.destroy()

I get the error following error when trying to open a Dialog fragment a second time after calling this._oDialog.destroy():
Uncaught TypeError: Cannot read property 'setInitialFocusId' of null
My problem is like the problem stated here: How to clear dialog/xmlfragment content after close? However, the solution apparently just seems to be "Don't use the property setInitialFocus", which I do not use anywhere in my code.
Controller
openDialog: function() {
if (!this._oDialog) {
this._oDialog = sap.ui.xmlfragment("myFragmentPath", this);
this.getView().addDependent(this._oDialog);
}
this._oDialog.open();
},
onExit: function () {
if (this._oDialog) {
this._oDialog.destroy();
}
},
afterClose: function () {
if (this._oDialog) {
this._oDialog.destroy();
}
},
handleClose: function (oEvent) {
this._oDialog.close();
}
Dialog Fragment
<Dialog xmlns="sap.m" afterClose=".afterClose">
<!-- ... -->
</Dialog>
Main XML View
<Button press=".openDialog" />
Additional info:
The error message occurs in the Controller line when this._oDialog.open(); is called.
I am using the sap library version 1.60.1.
if (this._oDialog) {
this._oDialog.destroy();
this._oDialog = null; // make it falsy so that it can be created next time
}
After close, the dialog is destroyed in your code. However, the this._oDialog is still there.
Since this._oDialog is not a falsy value but just a destroyed dialog instance, there is no new Dialog created in openDialog() second time. Hence you're trying to open a destroyed dialog.
When the dialog is destroyed, its internal oPopup is set to null, which explains the error message.
⚠️ Note
There is usually no need to destroy the dialog after closing. When the view gets destroyed, the dialog will be destroyed automatically since the fragment is dependent to the view. If the intention was to reset data values, try unbinding properties instead of destroying and recreating the entire fragment every time which is quite costly.
Since UI5 1.56, the factory function sap.ui.xmlfragment is deprecated because it fetches the fragment via sync XHR (blocking the main thread). Use one of the new asynchronous APIs.
A simpler option is to add the fragment declaratively in your view definition with <core:Fragment fragmentName="..." type="XML" /> to the <dependents> aggregation of a certain control. Like in this sample.

ng-hide is not getting updated dynamically

I have below div element with nghide
<div ng-hide="showdiv" class="btnshowall">
<a class="button button-block round outline"
style="background: transparent !important;" >
Show All
</a>
</div>
and controller as below
.controller('mapCtrl', ['$scope', '$stateParams','User','$cordovaGeolocation','geoFireFac','GoogleMapFac','ConnectivityMonitor','PhysioFac','User',
function ($scope, $stateParams,User,$cordovaGeolocation,geoFireFac,GoogleMapFac,ConnectivityMonitor,PhysioFac,User) {
console.log('called mapctrl');
GoogleMapFac.setUserLoc($scope.map);
$scope.showdiv = User.getShowDiv();
}])
and User service as
.service('User', ['ToastFac',function(ToastFac){
return {
showDiv : false,
changeShowDiv : function(){
console.log('in changeShowDiv before change '+this.showDiv);
this.showDiv = !this.showDiv;
console.log('in changeShowDiv after change '+this.showDiv);
},
getShowDiv : function(){
return this.showDiv;
}
I am invoking User.changeShowDiv() from google map's marker click event like below
google.maps.event.addListener(marker, 'click', function () {
alert('store id '+marker.get('store_id'));
if(User.showDiv){
console.log('in if');
User.changeShowDiv();
console.log('User.showDiv '+User.showDiv);
}
else{
console.log('in else');
User.changeShowDiv();
console.log('User.showDiv '+User.showDiv);
}
});
logs are coming as expected
in else
services.js:123 in changeShowDiv before change false
services.js:125 in changeShowDiv after change true
services.js:218 User.showDiv true
services.js:211 in if
services.js:123 in changeShowDiv before change true
services.js:125 in changeShowDiv after change false
services.js:213 User.showDiv false
services.js:216 in else
services.js:123 in changeShowDiv before change false
services.js:125 in changeShowDiv after change true
services.js:218 User.showDiv true
By default, as User.showDiv variable is false, showAll button is visible. But button is not hiding & coming by marker click events.
Could someone guide me what I am missing.
Events that come from outside the AngularJS framework need to be brought into the AngularJS framework with $apply:
google.maps.event.addListener(marker, 'click', function () {
alert('store id '+marker.get('store_id'));
if(User.showDiv){
console.log('in if');
User.changeShowDiv();
console.log('User.showDiv '+User.showDiv);
}
else{
console.log('in else');
User.changeShowDiv();
console.log('User.showDiv '+User.showDiv);
}
//IMPORTANT
$scope.$apply();
});
AngularJS modifies the normal JavaScript flow by providing its own event processing loop. This splits the JavaScript into classical and AngularJS execution context. Only operations which are applied in the AngularJS execution context will benefit from AngularJS data-binding, exception handling, property watching, etc... You can also use $apply() to enter the AngularJS execution context from JavaScript. Keep in mind that in most places (controllers, services) $apply has already been called for you by the directive which is handling the event. An explicit call to $apply is needed only when implementing custom event callbacks, or when working with third-party library callbacks.
— AngularJS Developer Guide - Integration with the browser event loop
ALSO
Be sure to fix the ng-hide and the controller:
<div ng-hide="showdiv()" class="btnshowall">
$scope.showdiv = function() {
return User.getShowDiv();
};
In the above code, the ng-hide directive will execute the showdiv() function on each digest cycle and update the visibility of the element accordingly.
You're retrieving value from User.getShowDiv method once only. But when it gets change your are not updating showdiv scope variable. To update value each time you can directly bind the reference of User.getShowDiv method to showdiv scope variable like below
$scope.showdiv = User.getShowDiv;
There after call showdiv method on HTML, which will eventually evaluate value on each digest cycle unlike other bindings.
ng-hide="showdiv()"
Even above would not solve your problem. Basically you're updating some variable from outside context Angular which is click event. So you have to run digest cycle manually right after updating value from click event listener ran. Just do use $timeout(angular.noop) to fire digest cycle safely.
google.maps.event.addListener(marker, 'click', function () {
alert('store id '+marker.get('store_id'));
if(User.showDiv){
//Code here
}
else{
//Code here
}
//manually triggering digest loop to make binding in sync
$timeout(angular.noop); //It will run new digest cycle.
});

Angular ng-show not reflecting change to variable

I have an angular controller and a timeout calling a function that is setting a variable that an ng-show relies on. It seems the variable is successfully being changed, but the html element is still showing up.
The JS in my controller is:
setTimeout(function () {
console.log('showAlert is - ' + $scope.showAlert);
$scope.showAlert = false;
console.log('showAlert now is - ' + $scope.showAlert);
$scope.message = '';
}, 3000);
which is happening in the success function of an $http.post call (if that matters.
and the HTML is:
<h3 ng-show="showAlert">{[{message}]}</h3>
What appears in the console is:
showAlert is - true
showAlert now is - false
So it's being changed successfully, it just doesn't seem the template is following suit. It is correctly hidden when the page is loaded, and $scope.showAlert is originally set to false.
This seems like a very straightforward example, I don't know why this wouldn't be working. It acts the same if I put the tag into a contain as well.
Thank you!
As you are making changes to the scope after 3 sec by calling settimeout funtion, the DOM might be already loaded and uses the initial value of $scope.showalert, if the value got changed later, inorder to apply that new change, you can try placing $scope.$apply() at the end of our function.

Avoid "ifChanged" event handler when setting checkbox via Javascript

for iCheck plugin, is there a way to avoid "ifChanged" event handler to fire up when setting the checkbox from Javascript?
Old question, but I found a better method is to check the event.target.checked property and only run your code if it returns true. iCheck fires ifChanged twice - first for the un-checked option, then secondly for the checked option. So if you only run your code if event.target.checked === true, you will get the result you are after.
You could create a variable ignoreChange and when subscribing to the event handler, checking whether that variable is true and if it is, then set it to false and stop the function. If it is not true, then you can execute your normal code.
JS code:
var ignoreChange = false;
$('input').on('ifChanged', function(event){
if (ignoreChange) {
ignoreChange = false;
return;
}
// do stuff
});
// When changing the checkbox
ignoreChange = true;
Basically, whenever you set the variable ignoreChange to true, the next event call is ignored. This is quite a hacky workaround, however necessary, as I did not find a way to solve your problem trough the iCheck library.

How can I tell if a model has not been changed in Backbone.js?

This may be a result of misuse of the component, though I don't think so.
I have an issue where a View updates a model in Backbone JS and calls the model's Set method so that it may verify it's input.
In theory there are two results to such an action: Error and Change.
Both events work as prescribed.
But in fact there is a third event: No change.
That is, if the input has not been changed at all, I can't tell after calling Set because no error will be thrown but nor will a change event, as nothing has actually changed- but I still want to know about such a case.
Is there a way for me to do this?
The reason is that there is an action I want performed only if no error occurs, but there is no way for me to know (without a change event) that the model has attempted to set the new values and ended with no result as it all happens asynchronously.
Thanks!
Every Backbone model has a hasChanged method:
hasChanged model.hasChanged([attribute])
Has the model changed since the last "change" event? If an attribute is passed, returns true if that specific attribute has changed.
Perhaps you can use that to check your third possibility.
BTW, the callbacks aren't asynchronous. The error and changed callbacks are triggered and return before set returns:
set : function(attrs, options) {
//...
// Run validation.
if (!options.silent && this.validate && !this._performValidation(attrs, options)) return false;
//...
// Update attributes.
for (var attr in attrs) {
var val = attrs[attr];
if (!_.isEqual(now[attr], val)) {
now[attr] = val;
delete escaped[attr];
this._changed = true;
if (!options.silent) this.trigger('change:' + attr, this, val, options);
}
}
The _performValidation call triggers the error callbacks, the this.trigger calls will call the per-attribute callbacks.
In this case, you may need to dance around Model.set() a little bit to get where you want. If you are using this functionality, then you should have defined a validate() method on your model.
http://documentcloud.github.com/backbone/#Model-validate
So you can call this method directly...
// something happens and we need to update the model to "newvalues"
if (model.validate(newvalues)) {
model.trigger('error')
} else {
model.trigger('change')
}
model.set(newvalues)
That way you will always at least get 'change' or 'error' out of it, even if it's the same. You will also still get the existing events from set.

Categories

Resources