Evaluate a value after a watcher is run Angularjs - angularjs

I have watcher code which gets triggered whenever an item is changed from a dropdown. I am trying to get the value of $scope.from_date after the event occurs but the value being returned from the console log is the value before the event is triggered.
$scope.$watch('strSelectedDateRange', function(strNewRange, strOldRange) {
if (strNewRange && strNewRange !== strOldRange) {
if ($scope.strSelectedDateRange === 'custom') {
$scope.bShowDateRange = true;
checkHourlyCustomDateRange();
$scope.setCustomDateRange();
} else {
$scope.bShowDateRange = false; // for safety
$scope.strCurDateRange = $scope.strSelectedDateRange;
$this.getReportData($scope.deal);
}
}
console.log($scope.from_date)
});

This the the link to example code:
JSFiddle link
angular.module('watchApp', []).controller('watchCtrl', function($scope) {
$scope.watchVariable = "hghj";
$scope.$watch('watchVariable', function(newVal, oldVal) {
// do something here
$scope.oldVal = oldVal;
$scope.newVal = newVal;
}, true);
});

Related

Remove $watch and bring the logic out of the $watch in AngularJS

I have a code in AngularJS which looks like below :
$scope.startWatching = function () {
return $scope.$watch('form', function (n, o) {
var timeoutPromise;
$timeout.cancel(timeoutPromise); //does nothing, if timeout alrdy done
timeoutPromise = $timeout(function () {
if (n !== o) {
if ($scope.isLegacy) {
$scope.showCompleteBtn = $scope.showCompleteButton2();
} else {
$scope.showCompleteBtn = $scope.showCompleteButton();
}
}
}, 400);
}, true);
So whenever form changes, either $scope.showCompleteButton2() is called or $scope.showCompleteButton() is called.
The problem is that the $watch() gets called many number if times, so I need to bring these two methods out of the $watch().
Watchers like event listeners should only be added once when the DOM is built. And removed when the DOM is torn down.
If the code needs to enable or disable the actions performed by the watcher, provide a state in the model to do so:
var enableWatch = false;
$scope.startWatching = function () {
enableWatch = true;
};
var timeoutPromise;
$scope.$watch('form', function (n, o) {
if (!enableWatch) return;
//ELSE
timeoutPromise && $timeout.cancel(timeoutPromise);
timeoutPromise = $timeout(function () {
if (n !== o) {
if ($scope.isLegacy) {
$scope.showCompleteBtn = $scope.showCompleteButton2();
} else {
$scope.showCompleteBtn = $scope.showCompleteButton();
}
}
}, 400);
}, true);
The watcher ignores changes when the enableWatch variable is false. Set the variable to true to enable the specified actions.

Trying to bind a variable to a row in a Factory array (angular)

I have a factory that is storing an array where objects are pushed in as buttons are pressed. I want to bind a variable on a page to the first object in this array. Here's my factory:
angular.module('myApp')
.factory('errorHandler', function () {
var errorArray = [];
function compareObjs(a,b) {
if(a.type === "Alert" && b.type === "Info") {
return -1;
}
else if (a.type === "Info" && b.type === "Alert") {
return 1;
}
else {
if(a.timestamp > b.timestamp) {
return -1;
}
else if (a.timestamp < b.timestamp) {
return 1;
}
else {
return 0;
}
}
}
errorArray.addError = (function (type, message) {
var timestamp = Date.now();
errorArray.push({type: type, message: message, timestamp: timestamp});
errorArray.sort(compareObjs);
});
errorArray.removeError = (function (index) {
if(errorArray.length >= index) {
errorArray.splice(index, 1);
}
});
return errorArray;
})
And Controller:
.controller('ErrorModalCtrl', ['errorHandler', '$scope', function (errorHandler, $scope) {
$scope.errorModal = {
title: 'Notification Centre',
errorList: []
};
$scope.addError = function(type, message) {
errorHandler.addError(type, message);
console.log(errorHandler.length+"just added");
};
$scope.errorModal.errorList = errorHandler;
$scope.mostRecentError = errorHandler[0];
}]);
So when the page loads the array will be empty, but as I press these buttons $scope.addError is triggered and objects are pushed to the array. I have checked this is working correctly. However, on my HTML (within the scope of the controller) I have
{{mostRecentError.message}}
and this never populates. Why is this the case? Have I misunderstood the dependencies somewhere?
Thanks
The line
$scope.mostRecentError = errorHandler[0];
is executed when your controller is instantiated. At that moment, the error hasn't pressed any button yet. So errorHandler[0] is undefined, and $scope.mostRecentError is thus initialized to undefined.
When the user pressed the button errorHandler will have its first element, but that line of code won't be magically reexecuted, so $scope.mostRecentError wil stay undefined.
Instead of initializing a variable, you should define a function in the controller, that returns the most recent error. That, or $scope.mostRecentError must be initialized inside the addError() function of the controller, to be updated every time an error is added.

Alert and Confirmation dialog box AngularJs

I'm trying to implement Alert/Confirmation dialog boxes using the bootstrap modal service in my angular app. I want to make it generic, based on the parameter that I pass to the modal's controller it will either behave as the alert box where the user can just close it on reading the message on the modal and also it can behave like a confirmation dialog where the user should say either 'OK' or 'Cancel' I need to capture the response from the user which I'm able to. Now, the problem here is that i've list of items say in a grid and when ever user wants to delete an item from the list I need to show the confirmation message box and based on the user response I need to either delete the item or leave it if users wishes to leave it in the list, but my item is being deleted first and then the confirmation dialog is showing up, I've tried using the call back but still no use. Please help me if anyone came across this situation.
Method that shows the alert:
$scope.showAlertMessage = function (modalName,commands,callback)
{
var modalOptions = {};
var alertMessageText;
var okBtn;
var cancelBtn;
var autoCloseTimeout;
$scope.modalResp === false;
if (modalName === 'ItemNotEligible')
{
modalOptions['template'] = 'application/Views/ItemNotEligibleAlert.html';
modalOptions['cntrlr'] = 'itemAlertsController';
modalOptions['winClass'] = 'item-alert-win';
alertMessageText = commands.alertMessage.text;
okBtn=true;
cancelBtn = false;
autoCloseTimeout=commands.alertMessage.autoDismissalTimeout;
}
else if (modalName === 'ItemDelete')
{
modalOptions['template'] = 'application/Views/ItemNotEligibleAlert.html';
modalOptions['cntrlr'] = 'itemAlertsController';
modalOptions['winClass'] = 'item-alert-win';
alertMessageText = commands.alertMessage.text;
okBtn = true;
cancelBtn = true;
autoCloseTimeout = commands.alertMessage.autoDismissalTimeout;
}
var params = { alertMessage: alertMessageText};
var modalInstance=$modal.open({
templateUrl: modalOptions.template,
controller: modalOptions.cntrlr,
windowClass: modalOptions.winClass,
resolve: {
modalParams: function () {
return params;
}
}
});
modalInstance.result.then(function (selected) {
$scope.modalResp = selected; //Response from the dialog
});
callback($scope.modalResp);
}
Method where the delete item logic exists and call to show alert method is made
this.voidItem = function (skuid) {
alertMessage.text = 'Are you sure you want to remove <strong>' + itemdata[itmid].split('|')[0] + '</strong> from your list?';
$scope.showAlertMessage('ItemDelete', commands, function (userresp) {
if (userresp === true) {
var lineId = 0;
for (var i = itemListState.length - 1; i >= 0; i--) {
if (parseInt(itemListState[i].item) === itmid && Boolean(itemListState[i].isItemVoid) != true) {
lineId = itemListState[i].Id;
if (lineId != 0) {
break;
}
}
}
poService.DeleteItem(itmid, lineId, function (modal) {
virtualtable = modal;
$scope.itemCount = $scope.itemCount - 1;
$scope.createlist(itmid);
});
}
});
}
The problem is that you are executing the callback instead of chain the method to the result of the promise
modalInstance.result.then(function (selected) {
callback($scope.modalResp); //Once the promise is solved, execute your callback
$scope.modalResp = selected; //Why you need to save the response?
// If the user press cancel o close the dialog the promise will be rejected
}, onUserCancelDialogFn);
A more simple way:
modalInstance.result
.then(callback, onErrBack);

binding to service variable, doesnt refresh (service changes the var frequently)

In my Service i have the vars i want to display and the getters for it:
var docsLoaded = 0;
var docsToLoad = null;
pouchService.getDocsLoaded = function () {
return docsLoaded;
};
pouchService.getDocsToLoad = function () {
return docsToLoad;
};
While the service is syncing, i want to count the synced docs
pouchService.syncNow = function () {
var foundLastSeq = false;
docsLoaded = 0;
docsToLoad = null;
remoteDB.info().then(function (remoteInfo) {
function findOutDiff(localPosition) {
docsToLoad = (remoteInfo.update_seq - localPosition) + 1;
console.log("docs to load: " + docsToLoad);
}
// start Sync progress
sync = localDB.sync(remoteDB, {live: false})
.on('change', function (info) {
console.log('AI change: ');
console.log(info);
if (info.direction === 'pull') {
if (foundLastSeq === false) {
foundLastSeq = true;
findOutDiff(info.change.last_seq);
}
}
console.log(docsLoaded + " from " + docsToLoad);
docsLoaded++;
})
In my HTML i want to display the progress like this:
{{pouchService.getDocsLoaded()}} from {{pouchService.getDocsToLoad()}}
Now i get sometimes a value from getDocsLoaded, but mostly its zero. When I cancel the Syncprogress i get the value where it's stopped.
So i get the value before it really starts and when it's over, but i want it during the sync progress. (on the console my my progressinfos are working as expected)
Any ideas?
The problem is in applying scope. Jim wrote a nice article about this problem:
jimhoskins.com/2012/12/17/angularjs-and-apply.html
Solved it:
$rootScope.$apply(function () {
docsLoaded++;
});

How to watch for a keypress combination in Angularjs? [duplicate]

This question already has answers here:
AngularJS - Multiple keypress at the same time
(4 answers)
Closed 4 years ago.
I'm trying to get my controller to watch for a combination of keys. For argument's sake, let's say: up up down down left right left right b a. How can I get angular to look out for these regardless of where in the page the user currently is?
Looks like you can use the ng-keydown to do this.
Here is a working plunker.
For this sample, I just bound ng-keydown to <body>. Works pretty well to catch all the keyboard events globally.
As #charlietfl points out, ng-keydown registers a lot of keyboard events so to make this usable would be a lot of work. For example, if you were trying to listen for a combination (like ctrl + r), then the ctrl key will register many times.
JS:
var myApp = angular.module('myApp', []);
myApp.controller('Ctrl', function($scope) {
$scope.keyBuffer = [];
function arrays_equal(a,b) { return !(a<b || b<a); }
$scope.down = function(e) {
$scope.keyBuffer.push(e.keyCode);
var upUp = [38, 38, 40, 40, 37, 39, 37, 39, 66, 65];
if (arrays_equal(upUp, $scope.keyBuffer)) {
alert('thats it!');
}
};
});
HTML:
<body ng-controller="Ctrl" ng-keydown="down($event)">
I'm using a different way to do it.
$scope.keyboard = {
buffer: [],
detectCombination: function() {
var codes = {};
this.buffer.forEach(function(code) {
codes['key_' + code] = 1;
});
if ((codes.key_91 || codes.key_93) && codes.key_8) {
// I'm looking for 'command + delete'
}
},
keydown: function($event) {
this.buffer.push($event.keyCode);
this.detectCombination();
},
keyup: function($event, week) {
this.buffer = [];
}
};
Detecting Backspace-Key (Mac) and Del-Key (PC):
<body ng-controller="Ctrl" ng-keydown="keyDown($event)">..<body>
$scope.keyDown = function(value){
if(value.keyCode == 46 || value.keyCode == 8) {
//alert('Delete Key Pressed');
}
};
This is all untested, but you could use ng-keypress
<body ng-keypress="logKeys($rootScope,$event)">...</body>
To call a function something like:
appCtrl.$scope.logKeys = function($rootScope,$event){
$rootScope.keyLog.shift(); // Remove First Item of Array
$rootScope.keyLog.push($event.keyCode); // Adds new key press to end of Array
if($scope.$rootScope.keyLog[0] !== 38) { return false; } // 38 == up key
if($scope.$rootScope.keyLog[1] !== 38) { return false; }
if($scope.$rootScope.keyLog[2] !== 40) { return false; } // 40 = down key
if($scope.$rootScope.keyLog[3] !== 40) { return false; }
if($scope.$rootScope.keyLog[4] !== 27) { return false; } // 37 = left key
if($scope.$rootScope.keyLog[5] !== 39) { return false; } // 39 = right key
if($scope.$rootScope.keyLog[6] !== 37) { return false; }
if($scope.$rootScope.keyLog[7] !== 39) { return false; }
if($scope.$rootScope.keyLog[8] !== 65) { return false; } // 65 = a
if($scope.$rootScope.keyLog[9] !== 66) { return false; } // 66 = b
$rootScope.doThisWhenAllKeysPressed(); // Got this far, must all match!
return true;
}
Outside an input field, I don't think ng-keypress works, but the keypress from angular-ui might.
I'm sure there should be an array diff kinda function too, but the specific call evades me right now.
Here's my take on it:
var app = angular.module('contra', []);
app.directive('code', function () {
function codeState() {
this.currentState = 0;
this.keys = [38, 38, 40, 40, 37, 39, 37, 39, 66, 65];
this.keyPressed = function (key) {
if (this.keys[this.currentState] == key) {
this.currentState++;
if (this.currentState == this.keys.length) {
this.currentState = 0;
return true;
}
} else {
this.currentState = 0;
}
return false;
};
};
return {
restrict: 'A',
link: function (scope, element, attrs) {
var cs = new codeState();
scope.isValid = "NO";
element.bind("keydown", function (event) {
scope.$apply(function () {
if (cs.keyPressed(event.which)) {
scope.isValid = "YES";
console.log("CODE ENTERED");
} else {
scope.isValid = "NO";
}
});
});
}
}
});
What's different about this is it's a directive so if you attach this on the body, it'll apply to the whole page. This also allows for entering the code multiple times.
Plunkr:
http://plnkr.co/edit/tISvsjYKYDrSvA8pu2St
Check out this plunker. I've implemented a simple '2 UP keystrokes in a row' scenario.
You can do it in plain jQuery and communicate the event with a $rootScope.$broadcast.
Register the jQuery code in and Angular run callback (guarantees that angular has already bootstraped):
app.run(function($rootScope) {
var upHitOnce = false;
$(document).keyup(function(event) {
if (event.which == 38) {
if (upHitOnce) {
$rootScope.$broadcast('DoubleUpFired');
$rootScope.$apply();
upHitOnce = false;
} else {
upHitOnce = true;
}
} else {
upHitOnce = false;
}
});
});
and then any controller can listen to this event like:
$scope.$on('DoubleUpFired', function() {
$scope.fired = true;
});
Binding an ng-keydown action callback to body is ok, but has a small disadvantage. It fires a $digest on every keystroke. What you really want is a $digest only when the sequence has been entered when you somehow need to update the UI.
EDIT
See comments on how to remove actual jQuery dependency.
If you are trying 'ctrl+s' or 'commond+s' ( change the commondKey ) to do save, maybe can use like it :
directive :
(function () {
'use strict';
var lastKey = 0;
//var commondKey = 17;
var commondKey = 91;
angular
.module('xinshu')
.directive('saveEnter', function () {
return function (scope, element, attrs) {
element.bind("keydown", function (event) {
if (event.which != commondKey && event.which != 83) {
lastKey = 0;
}
if (lastKey == commondKey && event.which == 83) {
scope.$apply(function () {
scope.$eval(attrs.saveEnter);
});
event.preventDefault();
}
lastKey = event.which;
});
};
});
})();
element :
<input id="title" save-enter="vm.saveTitle()"/>
You can rename the saveEnter in directive, with change the save-enter in html.
The 'vm.saveTitle()' is the fuc your want to do.

Resources