It seem $watch not work with cookies expires. Here is the code I tried
$scope.$watch(function(){
return $cookies.get('cookies')
},function(newVal,oldVal){
if(!newVal){
console.log('ok')
}
})
I can check it with $interval. But I dont want call digest cycle every 2s , or 5s.
Unfortunately, it is not possible to read the cookie expiration time in JS so my first idea of iterating over them and executing action on their expiration dates is not possible.
If you don't want to start a digest on every "cookie check", you can disable invoking it with the fourth parameter for the $interval set to false. See the $interval docs.
Example code:
.run(function($cookies, $interval) {
var previouslyAvailableCookies = Object.keys($cookies.getAll());
var watchCookieChanges = function() {
var availableCookies = Object.keys($cookies.getAll());
var expiredCookies = previouslyAvailableCookies.filter(function(cookieName) { return availableCookies.indexOf(cookieName) < 0; });
var newCookies = availableCookies.filter(function(cookieName) { return previouslyAvailableCookies.indexOf(cookieName) < 0; });
if (expiredCookies.length || newCookies.length) {
document.writeln("<p>New cookies: " + newCookies
+ "<br>Expired cookies: " + expiredCookies + '</p>');
}
previouslyAvailableCookies = availableCookies;
};
$interval(watchCookieChanges, 3000, 0, false);
})
And a codepen.
Related
I've done extensive research on this subject, but no matter what I do, I find it extremely difficult to achieve this objective.
I want to execute code when all elements have been fully rendered in AngularJS web application. I think I found solution suggesting to use routers and views, but I could not make that work on my case, as it seems it requires certain configuration.
When you have ng-repeat and a lot of nested directives that will generate HTML/Content based on various conditions using ng-if, I noticed that HTML rendering continues even after document ready event is fired or view content have been loaded ie $viewContentLoaded event is triggered.
The closest idea I have is to use $watch over the length of the children of the element of a given directive. Every time the $watch is executed, increment counter renderCount. Then, in another timer event, check if the counter renderCount didn't change over the past say 3-5 seconds, then we can make an assumption that rendering is done.
The code to watch for the children, and check if no more rendering is taking place, could be as follows:
app.directive('whenRenderingDone', function($interval, $parse){
return {
link: function (scope, el, attrs) {
var renderingCount = 0;
function watchForChildren() {
scope.$watch(function(){
return $(':input', el).length;
}, function(newVal, oldVal){
if (newVal) {
renderingCount++;
}
})
}
watchForChildren();
//Check counter every 3 seconds, if no change since last time, this means rendering is done.
var checkRenderingDone = $interval(function(){
var lastCount = lastCount || -1;
if (lastCount === renderingCount) {
var func = $parse(attrs.whenRenderingDone);
$interval.cancel(checkRenderingDone);
func(scope);
}
lastCount = renderingCount || -1;
}, 3000);
}
}
});
I will try to implement the above approach, and if you have feedback please let me know.
Tarek
I developed the following directive which is working well under Chrome and IE11:
app.directive('whenRenderingDone', function($timeout, $parse){
return {
link: function (scope, el, attrs) {
var lastCount;
var lastTimer = 5000; // Initial timeout
//Check counter every few seconds, if no change since last time, this means rendering is done.
var checkRenderingDone = function (){
var mainPromiseResolved = scope.mainPromiseResolved;
lastCount = lastCount || -1;
if (lastCount === el.find('*').length && mainPromiseResolved) {
console.log('Rendering done, lastCount = %i', lastCount);
var func = $parse(attrs.whenRenderingDone);
func(scope);
} else {
lastCount = el.find('*').length;
console.log('mainPromiseResolved = %s, lastCount %i', mainPromiseResolved, lastCount)
console.log('Rendering not yet done. Check again after %i seconds.', lastTimer/1000.00);
stopCheckRendering = $timeout(checkRenderingDone, lastTimer);
lastTimer = lastTimer - 1000;
if (lastTimer <= 0) {
lastTimer = 1000;
}
return stopCheckRendering;
}
}
var stopCheckRendering;
stopCheckRendering = checkRenderingDone();
el.on('$destroy', function() {
if (stopCheckRendering) {
$timeout.cancel(stopCheckRendering);
}
});
}
}
});
I hope this will be of help to you, and if you have any comment to improve, please let me know. See this to give you an idea about how it is working.
Tarek
You can use $$postDigest to run code after the digest cycle completes. You can read more about the scope lifecycle here
// Some $apply action here or simply entering the digest cycle
scope.$apply(function () { ... });
...
scope.$$postDigest(function () {
// Run any code in here that will run after all the watches complete
// in the digest cycle. Which means it runs once after all the
// watches manipulate the DOM and before the browser renders
});
I have a few spots where things happen in the UI on a delay using $timeout or $interval. Here's a simplified example:
Controller code:
$timeout(function() {
$scope.showElement = true;
}, 10000);
HTML:
<div id="myElement" ng-show="showElement"></div>
I want to be able to create an end-to-end Protractor test that tests whether #myElement gets displayed after a 10 second wait. The only way I have found to do this is to call browser.sleep(10000), which results in an actual 10-second delay in my test. This works, but these pauses add up add up and significantly increase the duration of my tests. Imagine a situation where you wanted to test whether a modal pops up after 30 minutes of inactivity.
Is there a way to simulate the passage of a specific amount of time, similar to $timeout.flush() in a jasmine test?
You can decorate $timeout and $interval to override the delay supplied to them:
lower-wait-time.js
exports.module = function() {
angular.module('lowerWaitTimeDecorator', [])
.config(function($provide) {
$provide.decorator('$timeout', function($delegate) {
return function() {
// The second argument is the delay in ms
arguments[1] = arguments[1] / 10;
return $delegate.apply(this, arguments);
};
});
})
};
Usage
beforeAll(function() {
var lowerWaitTime = require('lower-wait-time');
browser.addMockModule('lowerWaitTimeDecorator', lowerWaitTime.module);
});
afterAll(function() {
browser.removeMockModule('lowerWaitTimeDecorator');
});
it('My-sped-up-test', function() {
});
You could do this potentially using async.whilst. The idea is keep on looking for the element until the timeout is reached. If the element is found BEFORE timeout reaches or if element is NOT found within the timeout, test fails otherwise it passes. I haven't tested this but you get the idea. For example,
var driver = browser.driver,
wd = browser.wd,
async = require('async'),
start = Date.now(),
found = false,
diff;
async.whilst(
function() {
var diff = Date.now() - start;
return diff <= 10000 && !found;
},
function(callback) {
driver.findElement(wd.By.id('myElement')).then(function() {
found = true;
callback();
},function(err) {
found = false;
callback();
});
},
function (err) {
var isTesrPassed = !err && found && diff>=10000;
assertTrue(isTestPassed, 'element visibility test failed');
}
);
I'm an atmosphere & Angular newbie and I'm really struggling to find an answer to this! Maybe I'm asking the wrong question.
I am setting up notifications using Atmosphere. I can open the websocket and watch the updates happen if I post the API URL directly into my browser.
In Angular I have an ng-repeat loop, which I would like to run as each new update adds a new object to the websocket.
<li ng-repeat="notification in notifications track by $index">
I am using angular watch to check for updates, but it doesn't pick up the new objects being added to the array. Here is my code:
// notification alerts
$scope.notifications = [];
notificationsService.notificationAlerts().then(function success(response) {
var jsonStringArray = response.data.split('|');
$scope.notifications = $.map(jsonStringArray, function(n, i){
if (n !== ""){
return JSON.parse(n);
}
});
console.log('Connect', response);
});
$scope.$watch('notifications', function(newVal, oldVal){
console.log('Watch', $scope.notifications);
}, true);
Hopefully I've made myself clear, let me know if I need to elaborate, or if I'm asking the wrong question. Thanks!
OK, I managed to solve this, for anyone stumbling across it later. Here is the final JS:
// add number of notifications to ".notifications-number"
function updateNumberOfNotifications(){
var numberOfNotifications = $("ul.notifications-list li").not(".nocount").length;
if (numberOfNotifications < 1) {
$(".notifications-number, .notifications-list").addClass("hidden");
} else {
$(".notifications-number").html(numberOfNotifications);
$(".notifications-number, .notifications-list").removeClass("hidden");
}
}
// notification alert variables
$scope.notifications = [];
var socket = atmosphere;
var subSocket;
// subscribe
function subscribe() {
var request = {
url : "/service/notifier",
transport: 'long-polling'
};
request.onMessage = function (response) {
//console.log('response', response);
var jsonStringArray = response.responseBody.split('|');
// console.log('json string array', jsonStringArray);
$.each(jsonStringArray, function(index, elem){
if (elem != ""){
$scope.notifications.push(JSON.parse(elem));
console.log("object", JSON.parse(elem));
}
});
//$scope.notifications.push($scope.newNotification);
$scope.$apply();
updateNumberOfNotifications();
// console.log('$scope.notifications', $scope.notifications);
};
subSocket = socket.subscribe(request);
}
function unsubscribe(){
socket.unsubscribe();
}
// subscribe on load and update notifications
updateNumberOfNotifications();
subscribe();
i am trying to make an animation on an html table.
i use $interval to display each row one by one.
var loadList = function() {
Obj.query(function(obj){
$scope.objs = [];
$interval(function() {$scope.objs.push(obj.shift())}, 200, obj.length);
});
}
then there is a function to remove each row one by one,
and finally it's looping and reload the table again :
var cleanList = function() {
var delay = 200;
var n = $scope.objs.length;
if (n!==0) {
$interval(function() {$scope.objs.shift()}, delay, n);
}
$interval(function() { loadList() }, delay*n, 1);
}
loadList();
$interval(cleanList, 7000);
The code is working here (here is the plunker), but i guess there is a way to do something nicer with a kind of "callback" or "promise" to trigger when the cleanList function is completed ?
How can i do that ?
$interval returns a promise so you can simply call then() on it.
For example:
$interval(function() {$scope.objs.push(obj.shift())}, 200, obj.length).then(cleanList);
Here's a working plunkr example:
http://plnkr.co/edit/rYzXjM?p=preview
OK switching my code to angularjs and the angular 'way', not sure what I am doing wrong.
A select list is not getting updated when the model changes unless I call $apply, and I find myself calling apply a lot.
index.html has this:
<div id='rightcol' data-ng-include="'partials/rightSidebar.html'"
data-ng-controller="rightSidebarController">
</div>
and rightSidebar.html has this:
<select id='srcList' size='10'
data-ng-model="data.source"
data-ng-click='srcOnclick()'
data-ng-options="s.title for s in data.srcList | filter:{title:data.srcFilter} | orderBy:'title'"></select>
rightSidebarController.js has this:
$scope.data = {};
$scope.data.srcList = dataProvider.getSourceList();
$scope.data.source = dataProvider.getSource();
dataProvider is a service that makes an asynchronous database call (IndexedDB) to populate srcList, which is what gets returned in dataProvider.getSource().
Is it the asynchronous database call that forces me to call $apply, or should the controller be ignorant of that?
Is there a 'better' way to do this?
Edited to add service code.
Another controller calls dataProvider.refreshSourceList:
myDB.refreshSourceList = function() {
myDB.getRecords("source", function(recs) {
myDB.srcList = recs;
$rootScope.$broadcast('SrcListRefresh');
});
};
myDB.srcList is the field being bound by $scope.data.srcList = dataProvider.getSourceList();
myDB.getRecords:
myDB.getRecords = function(storeName, callback) {
var db = myDB.db;
var recList = [];
var trans = db.transaction([storeName], 'readonly');
var store = trans.objectStore(storeName);
var cursorRequest = store.openCursor();
cursorRequest.onerror = myDB.onerror;
cursorRequest.onsuccess = function(e) {
var cursor = cursorRequest.result || e.result;
if (cursor === false || cursor === undefined) {
if (callback !== undefined) {
$rootScope.$apply(function() {
callback(recList);
});
}
} else if (cursor.value !== null) {
recList.push(cursor.value);
cursor.continue();
}
};
cursorRequest.onerror = myDB.onerror;
};
Anything you do async needs to be wrapped in $scope.$apply(). This is because angular works in a similar fashion to a game loop, however instead of constantly running, it knows to end the loop when an action is taken, and $scope.$digest() is called.
If you are using IndexedDB, I would recommend creating an angular wrapper for it, like so:
(forgive my IndexedDB code, I'm not experience with it)
angular.module('app',[])
.factory('appdb', function($rootScope){
var db = indexedDB.open('appdb', 3);
return {
get : function(table, query, callback) {
var req = db.transaction([table])
.objectStore(table)
.get(query);
req.onsuccess(function(){
$rootScope.$apply(function(){
callback(req.result);
});
});
}
};
});
This way you can be sure that any data retrieve and set on a controller scope inside of callback will have $scope.$digest() called afterward.