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
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 using new angular uigrid. But I think documentation is really poor for searching. I did not find necessary infos for filtering. I will use server side filtering . SO I have to get changed filter text for each column. Please help me?
I tried this code. But probably it was for ng grid(old grid)
$scope.$watch('completedgridOptions.filterOptions.filterText', function (newVal, oldVal) {
if (newVal !== oldVal) {
console.log("Filter");
}
}, true);
if you want to check what is what:
$scope.gridApi.core.on.filterChanged($scope, function () {
var grid = this.grid;
$.each(grid.columns, function (index, column) {
switch (column.field) {
case 'externalIdentifier':
$scope.identifier = column.filters[0].term;
break;
case 'name':
$scope.name = column.filters[0].term;
break;
case 'status':
$scope.status = column.filters[0].term;
break;
....
....
....
}
});
//you have to move it by hand, if you are on the third page and filter, it will stay on the third page, even though you will not have any data there
grid.options.paginationCurrentPage = 1;
//use the timeout so that you don't get a db call at every key pressed
if (angular.isDefined($scope.filterTimeout)) {
$timeout.cancel($scope.filterTimeout);
}
$scope.filterTimeout = $timeout(function () {
getPage(); //this will call your data from the db
}, 500);
});
var getPage = function () {
itemsPerPage = paginationOptions.pageSize;
offset = (paginationOptions.pageNumber -1) * paginationOptions.pageSize;
getDataByFilter($scope.name, $scope.identifier, $scope.status, offset, itemsPerPage)
}
I am new using angular as well but try this:
$scope.gridApi.core.on.filterChanged($scope, function () {
var grid = this.grid;
for (var i = 0; i < objGrid.columns.length; i++) {
term = objGrid.columns[i].filter.term;
field = objGrid.columns[i].field;
console.log('Field: ' + field + '\nSearch Term: ' + term);
}
});
$scope.gridOptions.data = $scope.data;//initialiazing grid with data
$scope.gridApi.core.on.filterChanged($scope, function () {
var grid = this.grid;
//do your stuff here
});
Basically I was able to use the filterchanged feature on grid only after the grid gets initialized with data.
So put the snippet as suggested in the thread only after initializing grid with data.
I have used $timeout as below for my convinience.
$timeout(function () {
$scope.getData();//get your data from ajax call or from where ever your data is
$scope.assignDatatoGrid();//assign the data to grid or initialize the grid with data
$scope.$apply();
//this feature is available only after the grid gets populated with data
$scope.gridApi.core.on.filterChanged( $scope, function() {
var grid = this.grid;
});
}, 2000);
It worked for me
Thank you
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.
I'm trying to get the following findTimelineEntries function inside an Angular controller executing after saveInterview finishes:
$scope.saveInterview = function() {
$scope.interviewForm.$save({employeeId: $scope.employeeId}, function() {
$scope.findTimelineEntries();
});
};
The save action adds or edits data that also is part of the timeline entries and therefore I want the updated timeline entries to be shown.
First I tried changing it to this:
$scope.saveInterview = function() {
var functionReturned = $scope.interviewForm.$save({employeeId: $scope.employeeId});
if (functionReturned) {
$scope.findTimelineEntries();
}
};
Later to this:
$scope.saveInterview = function() {
$scope.interviewForm.$save({employeeId: $scope.employeeId});
};
$scope.saveInterview.done(function(result) {
$scope.findTimelineEntries();
});
And finaly I found some info about promises so I tried this:
$scope.saveInterview = function() {
$scope.interviewForm.$save({employeeId: $scope.employeeId});
};
var promise = $scope.saveInterview();
promise.done(function() {
$scope.findTimelineEntries();
});
But somehow the fact that it does work this way according to http://nurkiewicz.blogspot.nl/2013/03/promises-and-deferred-objects-in-jquery.html, doesn't mean that I can use the same method on those $scope.someFuntcion = function() functions :-S
Here is a sample using promises. First you'll need to include $q to your controller.
$scope.saveInterview = function() {
var d = $q.defer();
// do something that probably has a callback.
$scope.interviewForm.$save({employeeId: $scope.employeeId}).then(function(data) {
d.resolve(data); // assuming data is something you want to return. It could be true or anything you want.
});
return d.promise;
}