How to remove flash of template in angularjs - angularjs

I am using filters (of web.xml) to check if the session (http session on server side) is valid or not. If the session is not valid, then the response return error 401, and I handle it in angularjs and redirect the route to login page.
So, the situation is like this:
There is a url localhost:8585/xyz. To access this url, login is required.
When the request is made to access this url (without logging in), the filter check whether the session is valid or not. Meanwhile (as the session is being checked), the template of url localhost:8585/xyz is displayed. As the user is not logged in, session is not valid, so filter send 401 error. I handle the error in angularjs and redirect the route to login page.
I don`t want the template to be displayed before getting the actual response from filters and redirecting the route to login page.

When you're defining your routes you can use the resolve property on your route (https://docs.angularjs.org/api/ngRoute/provider/$routeProvider).
This will allow you to specify a function which returns a promise and the route will only be changed once the promise resolves. As an added bonus the value coming back from the resolve promise can be injected into the handling controller.
Here is a plnkr showing how you can use a global function for any of the routes you need.

You can use ng-cloak to hide flash of unstyled content for Angular if need be.
Your main problem seems to happen after Angular is already loaded, though. In the controller that displays the template you can have a setting:
this.loaded = false;
Then in the template itself something like:
<throbber ng-if=!ctrl.loaded/>
<content ng-if=ctrl.loaded>
<!-- template content -->
</content>
Then you can set this.loaded = true when you get the response from the filter. If you get a 401 the redirect happens anyway and this.loaded doesn't get set to true anyway.

Without any example code, it's difficult to know this is possible for you, but the ng-show directive can be used to hide an element if it's provided value is falsy:
<div ng-show="authenticated">
<!-- content you wish to hide here -->
</div>
In your controller, simply set the value to true when the (successful) response is returned
myApp.controller('MyController', function ($scope) {
$scope.init = function() {
// check auth here
$scope.authenticated = true;
}
$scope.init();
});

Related

Run a function only the first time when AngularJS gets loaded

I'm using angular-seed template (AngularJS) and I have routeProvider setup and such, now I want to execute a function (which resides in a factory), but only the very first time when the page gets loaded. I found many solutions for this, but I don't want to execute the code each time the users switches between tabs (via routeProvider of course, page doesn't get reloaded) - the code must be executed only when the whole page gets (re)loaded.
How should I approach this? I tried to call the function from run and then broadcast the event when page gets loaded, but there are no event listeners - I guess that is because the run part gets executed before the controllers are setup, so there are no listeners attached at the time when the event gets broadcast.
So, any suggestions how to approach this?
UPDATE
Use case:
when user types the url in the page, the page gets loaded
when pages gets loaded, a $http.get request is performed, which gets a random content
this content can be changed only by clicking a button, to explicitly request a change of content.
if users clicks to a different page e.g. view2 (routeProvider) and then back to the view1, the content must not change
when users refreshes the page (F5), the content changes again (or as already stated, by a click of a button)
Use the run method:
app.run(function(yourService){
yourService.cacheRandomContent();
});
It runs only once after the app is bootstrapped and services are created.
To access the data in controller, just inject the same service:
app.controller('someCtrl',function($scope, yourService){
yourService.getCachedRandomContent().then(function(resp){
$scope.data = resp.data;
});
});
Your service would be something like:
app.service('yourService',function($http){
var promise;
return {
cacheRandomContent: function(){
promise = $http.get();
},
getCachedRandomContent : function(){
return promise;
}
}
});

Angular $location.path not working as expected

I am triggering a URL change using $location.path with parameters, however those parameters are NOT getting passed to the target page.
My source page is sidebar.html, and my target page is dashboard.html.
In my sidebar.html I render a Kendo UI treeview widget as follows :
<span id="treeview" kendo-tree-view="tree"
style="color:white;"
k-template="vm.treeItemTemplate"
k-options="vm.treeOptions"
k-data-source="vm.reportsTree"
k-on-change="vm.onTreeSelect(kendoEvent)">
</span>
and in my sidebar.js I set vm.treeItemTemplate with a click event:
vm.treeItemTemplate = "<a href='\' ng-click='vm.treeItemClick(this)'> {{dataItem.text}} </a>";
then once I click on the treeview item in the browser, I trigger the vm.treeItemClick event to change the URL and eimit the "reptname" parameter:
vm.treeItemClick = function (item) {
console.log("tree view clicked !");
$location.path('/index.html').search({reptname:'MTM'});
}
This is quite painful, and I would appreciate some advice.
I need use the Kendo treeview object in my left nav bar to allow the user to select a variety of report options, which in turn will redirect to the dashboard.html with specific "reptname" parameter values.
Now when I break on $location.path() inside sidebar.js (using Chrome dev tools), I clearly see the correct URL property on the $location object.
$location
LocationHashbangUrl {$$protocol: "http", $$host: "localhost", $$port: 49479, $$parse: function, $$compose: function…}
$$absUrl: "http://localhost:49479/index.html#/index.html?reptname=MTM"
$$host: "localhost"
$$parse: function (url) {
$$path: "/index.html"
$$protocol: "http"
$$replace: false
$$rewrite: function (url) {
$$search: Object
$$url: "/index.html?reptname=MTM"
__proto__: Object
However, once it's REDIRECTED to dashboard.html (which is defined in my routes as url:"/"), I DO NOT see the parameter list in the $location object, nor in the $routeParams object.
This is where I'm stuck !
----- UPDATE -------
When I manually refresh the index.html page with parameters, the $location object contains the parameters.
ex/ URL: Link entered manually
However if I redirect from sidebar.js using $location.path('/index.html').search({reptname:'MTM'}); , I get nothing !
If you see the parameter in the console but not in your executing code, it might be because Chrome tends to evaluate the console.log with a bit of delay, enought for the async requests to finish and have the data populated, therefor it's not a reliable way of debugging.
Can you listen for the $viewContentLoaded event to be fired and then check the path? That is the view is fully loaded and all variables should be set (including $location.path())
I'm not getting any real answers on this issue, so I ended up solving it this way:
In the Source page, sidebar.js, I have defined the treeview's onSelect event() :
function onTreeSelect(e) {
vm.selectedReport = e.sender._current.text();
$location.url('index.html#/?reptname=' + vm.selectedReport);
}
and on the target page, dashboard.js i read the query string using the hash() method:
vm.reptNameParam = $location.$$hash.split("=");
It seems that $location.url() is the ONLY way I can successfully redirect with a parameters list. Using $location.path() does NOT work for me; meaning, I cannot read the query string in my target page using $location.hash() or any other method as it returns an empty string.

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 (Restangular): Making a promise block? Need to use it for validating a token

I have stumbled upon Restangular for making calls to a rest service. It works great and returns a promise. I need to be able to have the call block. The reason for this is on a fresh page reload I am technically not loggged in but I may have a token stored in a cookie. i would like to validate this token against a rest service. Problem is that I need it to block.
If a timeout occurs or if its not valid that i can treat teh user as not authenticated.
This is the reason for wanting to block is that i would like to redirect them using $location.path to a new URL it not a valid token.
This doesn't happen on a specific route so i can't use resolve which is blocking. It technically happens on every route - I use the $on.$routeChangeStart and check an internal variable got LoggedIn or not, if not logged in i check for the stored token.
This happens on each Page refresh but not while navigating inside the application.
The affect I am trying to get is how Gmail works.
Look forward to any insight anyone has on this
Thanks
Basically you need to ensure that some asynchronous action occurs prior to any route change occurring, and in this case the action is authenticating a user.
What you can do is use the $routeChangeStart event that's emitted in order to add a property to the resolve object on the route like so:
function authenticate() {
if ( user.isAuthenticated ) {
return;
}
// Just fake it, but in a real app this might be an ajax call or something
return $timeout(function() {
user.isAuthenticated = true;
}, 3000);
}
$rootScope.$on( "$routeChangeStart", function( e, next ) {
console.log( "$routeChangeStart" );
next.resolve = angular.extend( next.resolve || {}, {
__authenticating__: authenticate
});
});
Since angular will wait for any promises in the resolve object to be fulfilled before proceeding, you can just use a pseudo dependency as in the example. Using something like that, you should be able to guarantee that your user is authenticating prior to any routes successfully executing.
Example: http://jsfiddle.net/hLddM/
I think the best way to do this might be to push the user around with $location.path, You can use .then() to effectively force a wait by leaving the user on a loading page.
var currentPath = $location.path();
$location.path(loadingScreen);
//Assuming you have some sort of login function for ease.
Restangular.login(token).then(
function(result) {
$location.path(currentPath)
},
function(error) {
$location.path(logInScreen)
}
);
If you're using ui-router, you could move to another state with the same URL, where you'd use that Restangular.login with the then, and in case of success go back to the "logged in" state, otherwise, go to the "log in" state where the user must enter his username and password.
If you're not using ui-router, you could implement something like that with some ng-switch.
So, upon arrival to the screen, you do that Restangular.login and by default you show loading page by setting some boolean to true. Then, if it doesn't succedd, you send him to the login, otherwise, you set loading to false and show page.
Anyway, I'd strongly recommend using ui-router, it rocks :)
Hope this works!

github redirect_uri with angularjs

I'm trying to add login with github in my app. So while testing on my local machine, I set my callback url like this http://localhost:8000/login/callback/.
And after that, I add a login link to my page like this, Login, and also
$routeProvider.when('/login/callback', {
controller: 'loginCtrl'
});
It success returned me a code with a link like this: http://localhost:8000/login/callback?code=cd07ff3b70f5d1d1b8a2. But angularjs can not response correctly.
Error response
Error code 404.
Message: File not found.
Error code explanation: 404 = Nothing matches the given URI.
I've also tried to set my redirect_url to http://localhost:8000/#/login/callback, but the return link is like this http://localhost:8000/?code=61e9c8b73c073a0bccc0/#/login/callback
I need the code to be appear at the end of the url but not in the middle so that I can use $location.search().code to get my code. How can I set it properly?
Like change my redirect_uri or change my router?
#lowerkey I still don't know how to handle this but I think that's because the page was reloaded.
When you are not setting the html5Mode to true, then will be # at the url, and if you reload the page, the # will track the information to the $scope to work with the htmlHistory api so that the app can work properly even with the page refresh.
But once you set the html5Mode to true, then there's no # to store any $scope information, and when the page reload, angular can not found the accordingly scope so it return 404.
You can check $location section on angularjs guide, it has a section explaining why this happen.
Page reload navigation
The $location service allows you to change only the URL; it does not allow you to reload the page. When you need to change the URL and reload the page or navigate to a different page, please use a lower level API, $window.location.href.
we should somehow push github to send params in html5-friendly way, i.e.
/login/callback/:code
for now I've created my hack-solution for it:
var code = parseUrlParam($location.absUrl(), 'code');
function parseUrlParam(url, param){
var temp = url.match(param + '=(\\w*)');
if(!temp[1]){
return false;
}
return temp[1];
}

Resources