Add "intermediary" page prior to full page load Angular - angularjs

My problem
I am using Angular to create a Phonegap application. Most of my pages are fairly small and the transition/responsiveness is quick and smooth. However I have one page that is fairly large that I am having an issue with.
The method for changing to this page is straightforward:
<button ng-click="$location.url('/page2')"></button>
When you "tap" the button above it takes about 1-2s to respond and change pages. I have double checked all areas for improvement on this page and determined that the delay is caused by Angular compiling and parsing the DOM of this page prior to changing the page. Please note that I am testing this on a real device so it is not due to emulator speeds.
The question
Is there a way to automatically or manually intercept page changes and put them in a sort of "loading" page so the response to the button click is immediate and page change is visible but the page content loads in a second or 2 later onto this "loading" page.
Its only an issue cause it is very awkward to click something and have nothing happen. I am having a very hard time finding any resources on this matter so if someone can even point me in the right direction to look I would be grateful.
Edit:
A super hacky solution I found was to use an ng-include on wrapper page and delay the include for a little bit.
myBigPageWrapper.html:
<div ng-include="page"></div>
Controller:
$scope.page = '';
setTimeout(function() { $scope.page='/pages/myBigPage.html'; $scope.$apply(); }, 1000);
Then navigate to your wrapper page instead: $location.url('/myBigPageWrapper')
This is obviously not ideal... But I hope this helps clarify what I am attempting to do.
Page2.html
This is the section that causes the page to slow down, commenting this out makes the page load very quickly. There are 13 pages in the "auditPages" array each containing about 50 lines of html mostly containing form input elements. Quite a bit of logic however it runs great once it is loaded. I am not going to include all the pages as it would be overload.
<div class="page-contents">
<form name="auditPageForm">
<div ng-repeat="(pageKey, pageData) in auditPages " ng-show="currentAuditPage.name==pageData.name">
<audit-form page="pageData">
<ng-include src=" 'partials/audit/auditSections/'+pageData.name+'.html'" onload="isFormValid(pageKey)"></ng-include>
</audit-form>
</div>
</form>
</div>

To sum up my comments above:
Your question was:
Is there a way to automatically or manually intercept page changes and
put them in a sort of "loading" page?
A lot of people asks for this question since Angular doesn't seem to provide a nice handling of a loading transition.
Indeed, the possible nicest solution would have been to "play" with the resolve property of angular's module configuration.
As we know, resolve allows to run some logic before the targeted page is rendered, dealing with a promise. The ideal would be to be able to put a loading page on this targeted page, while the resolve code is running.
So some people have nice ideas like this one:
Nice way to handle loading icon while route is changing
He uses $routeChangeStart event, so the loading icon would happen on the SOURCE page.
I use it and it works well.
Also, there is another way: make use of $http interceptor (like #oori answer above), to have a common code allowing to put a loading icon but...I imagine you don't want the same icon on every kind of http request the page does, it's up to you.
Maybe in the future, a solution would come directly associated to the resolve property.

Angular has $httpProvider.responseInterceptors
// Original by zdam: http://jsfiddle.net/zdam/dBR2r/
angular.module('LoadingService', [])
.config(['$httpProvider', function ($httpProvider) {
$httpProvider.responseInterceptors.push('myHttpInterceptor');
var spinnerFunction = function (data, headersGetter) {
angular.element(document.getElementById('waiting')).css('display','block');
return data;
};
$httpProvider.defaults.transformRequest.push(spinnerFunction);
}])
// register the interceptor as a service, intercepts ALL angular ajax http calls
.factory('myHttpInterceptor', ['$q','$window', function ($q, $window) {
return function (promise) {
return promise.then(function (response) {
angular.element(document.getElementById('waiting')).css('display','none');
return response;
}, function (response) {
angular.element(document.getElementById('waiting')).css('display','none');
return $q.reject(response);
});
};
}])

Related

Google maps not always fully rendering in Ionic

Having trouble with always rendering google maps in my Ionic app. When I first land on a view from a list of items on the previous view, the map always renders in its complete state. However, if I go back to the previous view and tap a different business, or even the same one, it appears as if the map is only rendering 25% of the complete map. I'm having this issue on both the emulator and on my iPhone.
Example
Code
getData.getBusinesses()
.then(function(data) {
// get businesses data from getData factory
})
.then(function(data) {
// get businesses photo from getData factory
})
.then(function(data) {
// get some other business stuff
})
.then(function() {
// get reviews for current business from separate async call in reviews factory
})
.then(function() {
// instantiate our map
var map = new GoogleMap($scope.business.name, $scope.business.addr1, $scope.business.city, $scope.business.state, $scope.business.zip, $scope.business.lat, $scope.business.long);
map.initialize();
})
.then(function() {
// okay, hide loading icon and show view now
},
function(err) {
// log an error if something goes wrong
});
What doesn't make sense to me is that I'm using this exact code for a website equivalent of the app, yet the maps fully load in the browser every time. The maps also fully load when I do an ionic serve and test the app in Chrome. I did also try returning the map and initializing it in a following promise, but to no avail.
I've also tried using angular google maps, but the same issue is occurring. I think I might want to refactor my gmaps.js (where I'm creating the Google Maps function) into a directive, but I don't know if that will actually fix anything (seeing as angular google maps had the same rendering issue).
I don't think the full code is necessary, but if you need to see more let me know.
EDIT
It seems that wrapping my map call in a setTimeout for 100ms always renders the map now. So I guess the new question is, what's the angular way of doing this?
I'm seeing similar issues with ng-map in Ionic. I have a map inside of a tab view and upon switching tabs away from the map view and back again, I would often see the poorly rendered and greyed out map as you describe above. Two things that I did that may help fix your issue:
Try using $state.go('yourStateHere', {}, {reload: true}); to get back to your view. The reload: true seemed to help re-render the map properly when the map was within the tab's template.
After wrapping the map in my own directive, I found the same thing happening again and wasn't able to fix it with the first suggestion. To fix it this time, I started with #Fernando's suggestion and added his suggested $ionicView.enter event to my directive's controller. When that didn't work, I instead added a simple ng-if="vm.displayMap" directive to the <ng-map> directive and added the following code to add it to the DOM on controller activation and remove it from the DOM right before leaving the view.
function controller($scope) {
vm.displayMap = true;
$scope.$on('$ionicView.beforeLeave', function(){
vm.displayMap = false;
});
}
Hope that helps.
don't use setTimeout on this!
You need to understand that the map is conflicting with the container size or something (example: map is loading while ionic animation is running, like swiping).
Once you understand this, you need to set map after view is completely rendered.
Try this on your controller:
$scope.$on('$ionicView.enter', function(){
var map = new GoogleMap($scope.business.name,
$scope.business.addr1, $scope.business.city,
$scope.business.state, $scope.business.zip,
$scope.business.lat, $scope.business.long);
map.initialize();
});

What is a good practice for redirecting in an angular SPA

I am building an angular app and have encountered several instances where I would like to redirect the user to a certain page. But that information about whether a user should be redirected or not is typically received after a server side request.
In the time it takes to do a server request, the original page starts rendering and hence creates a bad UX.
A case in point would be redirecting to login page when user is unauthorized.
Question 1 I know how to handle these cases individually. But was wondering if there is some standard pattern I can follow to solve this issue.
Question 2 Is there a standard pattern to control when to start rendering the page when information is being fetched from server. for instance
my view has
{{user.name}}
and controller has following code:
userService.load_user().then(function(user) {
$scope.user = user;
});
I don't want anything displayed till user is loaded, maybe just a loading sign. Currently i can do it as such:
//controller
userService.load_user().then(function(user) {
$scope.user = user;
$scope.loaded = true;
});
and
<!-- view -->
<div ng-show="!loaded">
<img src="loading.gif"/>
</div>
<div ng-show="loaded">
real code here.
</div>
This gets complicated when I want to wait on more than one requests.
Use the resolve property of the routes. Am assuming you're using ngRoutes or ui-router. Both include the resolve property on their routes.
To add a spinner or something similar while you wait for them to resolve, listen for the view change events within the shell view controller (assuming you have one) and add/remove the spinner accordingly.
By shell controller I just mean the highest level view within which the others are nested. It may or may not have a controller, but usually does. You might have a showSpinner property on that scope:
myApp.controller('mainCtrl', function($scope){
$scope.showSpinner = false;
$scope.$on('$stateChangeStart', function(){
$scope.showSpinner = true;
});
$scope.$on('$stateChangeSuccess', function(){
$scope.showSpinner = false;
});
});
Then you could just use ng-show='showSpinner' or ng-show='!showSpinner' on the spinner html element and the view element respectively.
This is the basic idea. You will probably end up with something more elaborate.
Here is a Plunker. There is a little more going on there (abstract state etc) than you requested, but you will quickly see how the resolve property is used and how the state is diverted.

AngularJS register controller once

That's what I'm doing. There is application with pages and different controls that may be put on pages by site admin/editor. All pages share one ng-app defined on master page. All controls are supplied with .js files with angular controllers. Let's suppose that I have an image gallery block:
<div ng-controller='imageGalleryCtrl'>
do something amazing here
</div>
<script src='imageGallery.js'></script>
Inside script there is a simple controller registration like:
angular.module('myApp').controller('imageGalleryCtrl', ... );
So. If I have 10 image galleries, I'll execute controller registration 10 times. It looks like this will work, but hell - I don't want it to be so =)
For now I just have all controls' scripts registration on a master page, but I don't like it as well, because if there is no image gallery on a page, I don't want it's script be downloaded during page load.
The question is - is there any proper way to understand if controller have been registered in a module already and thus prevent it from re-registering?
---------------
Well, though I've found no perfect solution, I must admit that the whole idea isn't very good and I won't think about it before my site will grow too big to assemble whole angular app on master page.
You should declare your controller but once. Instead of having one controller per gallery, have your single controller handle all image galleries. The controller should make a request to the REST backend to fetch the images of the desired gallery.
I see that instead of ng-view, you're using the ng-controller directive, indicating that probably you're not using Angular's routing. Try switching to using routes.
Have a look at Angular.js routing tutorial. It shows you how to use the ngRoute module. Then, in the next chapter, the use of $routeParams is described. Via the $routeParams service, you can easily say which gallery should be displayed by providing its ID in the URL; only one controller will be necessary for all your galleries.
If you really must check whether a given controller has been declared, you can iterate through the already declared controllers (and services... and pretty much everything else) by checking the array angular.module("myApp")._invokeQueue. The code would probably look something like this (not tested!):
var isRegistered = function(controllerName)
{
var i, j, queue = angular.module("myApp")._invokeQueue;
for (i = 0, j = queue.length; i < j; ++i) {
if (
queue[i][0] === "$controllerProvider"
&& queue[i][1] === "register"
&& queue[i][2][0] === controllerName
) {
return true;
}
}
return false;
};
Bear in mind however that while this may (or may not) work, it's far from being the correct thing to do. It's touching Angular's internal data that's not meant to be used in your code.

How do I get /mylink#sectionid to work in angularjs?

If I wasn't using angular, then the route mylink would be loaded, then the browser would scroll down to the sectionid section.
In Angular it doesn't scroll. I read some completely crazy whacky solutions involving injecting multiple modules and having crazy unique URLs. I refuse to do things like this.
I want my href values to remain standard. Is there any way in Angular to do this?
Keep in mind, if "mylink" was already loaded, then the links work fine, but if I'm on a different page, say "home", then I navigate to mylink#sectionid, then the scrolling won't occur.
(I mean... if Angular can't do this, I would consider that a bug. It'd be absurd to not support a regularly used syntax since the 90s that is still used today)
EDIT: I think the issue may be the amount of AJAX on this website.
It is certainly possible, you will need to inject in $anchorScroll into your controller
The example from the angular site:
function ScrollCtrl($scope, $location, $anchorScroll) {
$scope.gotoBottom = function (){
// set the location.hash to the id of
// the element you wish to scroll to.
$location.hash('bottom');
// call $anchorScroll()
$anchorScroll();
};
}
From anther route you could handle this via parameter being passed into the route and scroll upon initialization based upon the route param.
I'm not a big fan of my solution, but I listen to onRouteChange, then inject anchorScroll and simply call anchorScroll after a 1000 ms timeout and because the hash is already set nothing more needs to be done. [giving time for all angular stuff to work its self out (the site I'm working on has entirely too much AJAX, but I don't have control of the data yet, so there is nothing I can do about that)]
Anywho, manually initiating anchor scroll works. If anyone knows a better way to do this, that'd be swell.

AngularJS calls $http constantly

Im very new to AngularJS (4 hours new) and I'm trying to get an http call working, however what it seems like its happening is Angular keeps calling the http get request over and over again. I'm sure this is because my approach is wrong. This is what I'm trying to do.
snippet of my controller file The webservice works fine. I am running this in a node.js app
function peopleController($scope,$http){
$scope.getPeople = function(){
$scope.revar = {};
$http.get('/location/-79.18925/43.77596').
success(function(data){
console.log(data);
$scope.revar = data;
});
}
}
My list.html file
<div ng-controller="busController">
<div class="blueitem">{{getPeople()}}</div>
</div>
I know I will not see the results since im not returing anything in my getPeople Method but I wanted to see the log output of the result which I did see in chrome, but a million times and counting since angular keeps calling that url method over and over again. Instead it keeps hitting.
How do I get angular to return the response just once?
The problem you are experiencing is linked to the way AngularJS works and - to be more precise - how it decides that a template needs refreshing. Basically AngularJS will refresh a template based on a dirty-checking of a model. Don't want to go into too much details here as there is an excellent post explaining it (How does data binding work in AngularJS?) but in short it will keep changing for model changes till it stabilizes (no more changes in the model can be observed). In your case the model never stabilizes since you are getting new objects with each call to the getPeople() method.
The proper way of approaching this would be (on of the possible solutions):
function peopleController($scope,$http){
$http.get('/location/-79.18925/43.77596').
success(function(data){
$scope.people = data;
});
}
and then, in your template:
<div ng-controller="busController">
<div class="blueitem">{{people}}</div>
</div>
The mentioned template will get automatically refreshed upon data arrival.
Once again, this is just one possible solution so I would suggest following AngularJS tutorial to get better feeling of what is possible: http://docs.angularjs.org/tutorial/
Couple of things. Welcome to angularjs, its a great framework. You probably shouldn't be calling getPeople from the webpage. Instead,
function peopleController($scope,$http){
var getPeople = function(){
$scope.revar = {};
$http.get('/location/-79.18925/43.77596').
success(function(data){
console.log(data);
$scope.revar = data;
});
}
getPeople();
}
and then in html
<div ng-controller="busController">
<div class="blueitem">{{revar|json}}</div>
</div>
Also, I would recommend you looking into the ngResource, especially if you are doing CRUD type applications.
Hope this helps
--dan

Resources