Angular detect if view is finished - javascript

I currently have a few ng-repeats in my view for building an agenda. Whenever I have a ng-repeat I can include a directive checking if the ng-repeat is done to trigger a method. Works perfect...
The problem is, I have like 5 ng-repeats, I don't want to include the directive for all 5 ng-repeats and check in the method if all 5 have called the method...
I just want a way to detect if all my ng-repeats (and other angular stuff) is finished building the view so for example I can put appointments in the agenda via JQuery.
Because ofcourse, putting appointments in the agenda (divs) before the agenda (divs) have been created won't work.
Thanks in advance!
UPDATE:
I'm using a REST backend to get the appointments from. So i can't just retrieve the appointments and try to show them in the agenda because the view might not be finished generating (using the ng-repeats)....
So I need something that is triggerd ones the view is done generating (ALL ng-repeats must be done) so i can start placing the appointments.

You should use $viewContentLoaded rather than any directive as this fires after everything in the view loaded.
Documentation :
https://docs.angularjs.org/api/ngRoute/directive/ngView
Code Example :
$scope.$on('$viewContentLoaded', function(event) {
// Your Code Goes Here
});
Note that this will only work if you are using ng-view from routing.

thereĀ“s a trick in Angular using a $timeout to delay execution of certain parts of the code until all directives have redered. I'm not sure it will work in this instance but you could try.
In your controller simply add
$timeout(function() {
// code to execute after directives goes here
});

You're probably better off not using jQuery to populate the agenda, but using Angular instead. This allows you to just wait until the data is loaded, which is much easier to detect, and Angular will make sure the DOM gets uploaded whenever it can do so.
In your case you can probably do something like this:
Controller:
$scope.days = [];
//Here we create the days for the calendar (7 in this case)
for (var i = 0; i < 7; i++) {
var hours = [];
//and the hours, with an empty appointment array for each hour
for (var i = 0; i < 24; i++) {
hours.push({ appointments: [] });
}
$scope.days.push({
hours : hours
});
}
//Then we can get the appointments from your api
getAppointments().then(function(appointments) {
//and add the results to the arrays created above
appointments.forEach(function(appointment) {
//This is some simplified logic that only uses the day of the week
//and hour of the appointment. Your logic would probably a bit more complex
//to properly put the appointment in the correct array
var day = appointment.date.getDay();
var hour = appointment.date.getHour();
$scope.days[day].hours[hour].appointments.push(appointment);
});
});
Template:
<div class="days" ng-repeat="day in days">
<div class="hours" ng-repeat="hour in day.hours">
<!-- I assume you have one hours div in which all appointments for that hour will go -->
<div class="appointments" ng-repeat="appointment in hour">
{{ appointment.title }}
</div>
</div>
</div>
That said, if you really want to detect when the view has finished loading then you have a couple of options:
Wait for all your data being loaded and then use a $timeout to ensure it has been rendered.
It would look something like this:
var somePromise = getPromise();
var someOtherPromise = getOtherPromise();
$q.all([somePromise, someOtherPromise])
.then(function() {
//At this point all data is available for angular to render
$timeout(function() {
//and now everything should actually be rendered
});
Listen for the $viewContentLoaded, but this only works if you use ng-view and might fire too early if your data is loaded asynchronous (I'm not entirely sure about the details here, since I usually avoid detecting when the view is loaded).
If all of the above fails you could just continuously check if the desired elements are loaded on the page.

You can use angularjs's directive to make it. With directive you can set the ready event for any dom element.
For more detail, refer to this great post and question.

Related

Manually entering text in angular-ui

My question comes from the selected answer here
<ui-select-choices repeat="hero in getSuperheroes($select.search) | filter: $select.search">
<div ng-bind="hero"></div>
works perfectly fine with:
$scope.getSuperheroes = function(search) {
var newSupes = $scope.superheroes.slice();
if (search && newSupes.indexOf(search) === -1) {
newSupes.unshift(search);
}
return newSupes;
}
However, when I put a breakpoint on $scope.superheroes I see its called as many times as my data. Data is very large. Its fetched from a $http.get() request and I'm handling not loading the data in the textbox all at once.
However, I don't want to call this until I actually start typing anything in the textbox. I tried to call getSuperheroes using
on-select="getSuperheroes($select.search)"
and similarly
refresh="getSuperheroes($select.search)"
but they do not help allowing manual entry of data.
What do I use to call the function and yet get the task done just like how it works in the referenced answer?
I recommend you to do this:
In order to improve performance get rid of repeat="hero in getSuperheroes($select.search) | filter: $select.search", since if your data doesn't change the function getSuperheroes will be triggered (anyway) every time a $digest is executed (no matter the var newSupes changes or not in getSuperheroes) (that's wahat's happening right now!). Instead put (for instance) repeat="hero in myHeroes" and then in your getSuperheroes func do this:
$scope.getSuperheroes = function(search) {
var aux = $scope.superheroes.slice();
if (search && aux.indexOf(search) === -1) {
aux.unshift(search);
}
$scope.myHeroes = aux;
}
Keep the refresh="getSuperheroes($select.search)", this will search the new values by calling getSuperheroes which will update the $scope.myHeroes var and then it will be refreshed in the view.
Add the property refresh-delay="500" (500 is just an example, put whatever fits your needs). This will delay the search that amount of milisecond. This is useful when you do not want start searching immediately. For instance if the user wants to search "Superman" and she/he type "Sup" in less than 500 milisec the search will start with the string "Sup". If you do not set that property the search would be executed 3 times (one for "S" another for "Su" and a third one for "Sup")
PS:
-- If you want to get the ui-select with some values initially, You must call the getSuperheroes func on your controller start.
-- You may need to declare $scope.myHeroes = []; at the beginning of your controller (it depends on your controller implementation).
-- You might want to read more info about $digest on the AngularJS official doc, on SO there are some related posts:
How exactly does the AngularJS Digest Loop work?
Why do i have to call $scope.$digest() here?
Angular $scope.$digest vs $scope.$apply
$apply vs $digest in directive testing

How to make listener on data attribute (angularJS)?

I have js pie timer which is getting time from node.js and rendering pie chart.
here is code:
here I have several data-...="" attributes which is rendering the pie chart
<div id="myStat2" data-dimension="104" data-text="45" data-percent="0"></div>
here is jQuery part of what i have it is somehow listener but not best solution.
var time = 0;
setInterval(function(){
time = $('#myStat2').attr('data-percent');
$('#myStat2').empty();
$('#myStat2').attr('data-percent', time);
$('#myStat2').attr('data-text', parseInt(time));
$('#myStat2').circliful({animationstep: 0});
}, 100);
here is the result of what is completely rendered:
here everything correct and working fine, but i suppose this is not best solution.
I thought that i can do this through angularJS but i have not idea how it is working.
Can any one offer me something better ?
A large concept of angular is to avoid these kind of DOM bindings as 2-way data binding is already provided. I assume myStat2 is inside an angular scope so why not set a scope value in your view:
<div id="myStat2" data-dimension="104" data-text="{{ dataText }}" data-percent="{{ dataPercent }}"></div>
You can then change it directly in the $scope:
$scope.dataPercent = time;
$scope.dataText = parseInt(time);
I don't really understand what you are trying to achieve however as this code seems pointless:
time = $('#myStat2').attr('data-percent');
$('#myStat2').empty();
$('#myStat2').attr('data-percent', time);
I would recommend getting a better understand of angular and then moving this chart into some kind of directive.

ExtJs 4.2 ref-selector vs Ext.getcmp() vs Ext.ComponentQuery.query()

I was asked to develop a tab panel with 6 tabs, each having 30 to 40 elements. Each tab is acting as a form in accumulating the details of a person and the last tab is a Summary page which displays all the values entered in the first five tabs. I was asked to provide summary as a tab because, the user can navigate to summary tab at any instance and look at the details entered by him/ or glace the summary. i am following ExtJs MVC pattern. Payload is coming from / going to Spring MVC Application. (JSON)
Using tab change event in controller and if the newtab is summary I am rendering the page with show hide functionality.
Method 1 :In controller I have used Ext.getCmp('id of each element inside the tabs') and show hide the components in summary tab based on the value entered by the user. This killed my app in IE8 popping a message saying that the "script is slow and blah blah..." i had to click on NO for 5 to 6 times for the summary tab to render and display the page.
Method 2 :In controller I used ref and selectos to acccess all the items in tabs. I have used itemId for each and every field in summary tab. like this.getXyz().show(). I thought it would be fast. Yes it was in Google chrome. but my app in IE8 is slow compared to goolge chrome/firefox
Any suggestions regarding this and plan to decrease page render time. The summary page has more than 1000 fields. Please feel free to shed ur thoughts or throw some ideas on this.
thank you!!
I've got a few suggestions you can try. First, to answer your title, I think the fastest simple way to lookup components in javascript is to build a hash map. Something like this:
var map = {};
Ext.each(targetComponents, function(item) {
map[item.itemId] = item;
});
// Fastest way to retrieve a component
var myField = map[componentId];
For the rendering time, be sure that the layout/DOM is not updated each time you call hide or show on a child component. Use suspendLayouts to do that:
summaryTabCt.suspendLayouts();
// intensive hide-and-seek business
// just one layout calculation and subsequent DOM manipulation
summaryTabCt.resumeLayouts(true);
Finally, if despite your best efforts you can't cut on the processing time, do damage control. That is, avoid freezing the UI the whole time, and having the browser telling the user your app is dead.
You can use setTimeout to limit the time your script will be holding the execution thread at once. The interval will let the browser some time to process UI events, and prevent it from thinking your script is lost into an infinite loop.
Here's an example:
var itemsToProcess = [...],
// The smaller the chunks, the more the UI will be responsive,
// but the whole processing will take longer...
chunkSize = 50,
i = 0,
slice;
function next() {
slice = itemsToProcess.slice(i, i+chunkSize);
i += chunkSize;
if (slice.length) {
Ext.each(slice, function(item) {
// costly business with item
});
// defer processing to give time
setTimeout(next, 50);
} else {
// post-processing
}
}
// pre-processing (eg. disabling the form submit button)
next(); // start the loop
up().down().action....did the magic. I have replaced each and every usage of Ext.getCmp('id'). Booooha... it's fast and NO issues.
this.up('tabpanel').down('tabName #itemIdOfField').actions.
actions= hide(), show(), setValues().
Try to check deferredRender is true. This should only render the active tab.
You also can try a different hideMode. Especially hideMode:'offsets ' sounds promising
Quote from the sencha API:
hideMode: 'offsets' is often the solution to layout issues in IE specifically when hiding/showing things
As I wrote in the comment, go through this performance guide: http://docs.sencha.com/extjs/4.2.2/#!/guide/performance
In your case, this will be very interesting for you:
{
Ext.suspendLayouts();
// batch of updates
// show() / hide() elements
Ext.resumeLayouts(true);
}

How to get Angular model data back to a large number of DOM elements?

I'm working on a calendar app, and for convenience, I'm rendering the (largish) DOM in a Sinatra app. Whenever you click a day in the calendar, that day is toggled as "flagged" in the model.
This is pretty straight forward without much code:
<body ng-controller="CalendarCtrl" ng-click="toggle_day($event)">
and in the CalendarCtrl:
$scope.toggle_me = function($event) {
var target = angular.element($event.target);
if (target.attr("data-date")) {
$scope.toggle(target.attr("data-date"));
}
}
$scope.toggle = function(date) {
$scope.days[date] = 1 - ($scope.days[date] | 0);
}
What I have a problem with is getting the data back to the DOM elements. I'd like to add a class "active" to all days that are flagged in the model.
I could do this with a tedious:
<td data-date="20130101" ng-class="'active' if day is marked">1</td>
(never mind the pseudo code in the ng-class expression) but I'd have to do this for 365 days. Ugh.
I sense that a custom directive could solve this, but I don't know how to proceed. Are there any other solutions to this problem?

Showing/Hiding a div based on data, without loading non-displayed data in AngularJS

In my app I have a search page which loads in data based on a user's query. Each result (comes in waves of 10) has two fields I want to dynamically switch between: "imgs" and "smallImgs".
In short, if a result's smallImgs.length == true then I want to load up those images. If it has no length then I want to load up its imgs.
I'm aware of ng-hide and ng-show, and I could do a simple true/false statement in the template to show or hide the imgs/smallImgs. But from what I can tell, even if I have ng-hide enabled on an element it still loads in that data (and would hence download the images to the client).
The only way I've thought of achieving this is in my controller, on the $resource call that grabs my data:
$scope.itemSearch.get({query:$routeParams.query, page:$routeParams.page}, function(data){
$scope.posts = data.posts;
for(var i = 0; i<$scope.posts.length; i++){
$scope.posts[i].imgs = testImgs($scope.posts[i]);
}
});
var testImgs = function(data){
if (data.smallImgs.length){
return data.smallImgs;
} else{
return data.imgs;
}
}
Unfortunately I haven't gotten this working yet. Is this the best way to achieve this? Is there an Angular way to take care of this in the view?
What I'd do is add a method to your controller for this:
$scope.getImages = function () {
// add the logic to get the correct array here
};
Then just use that in the view, e.g.
<div ng-repeat="image in getImages()" />
Here's an example jsfiddle.
$scope.itemSearch.get({query:$routeParams.query, page:$routeParams.page}) - if this returns promise you can do so
$scope.getImages = $scope.itemSearch.get({query:$routeParams.query, page:$routeParams.page});
<div ng-repeat="image in getImages" />

Categories