AngularJS and ui-router: wrong routes behaviour after refresh - angularjs

I'm developing an application with AngujarJS and ui-router and I'm having a problem loading the routes. To facilitate the explanation I will delete parts of the code. My rotes file looks like this:
app.config(function($stateProvider, $urlRouterProvider,
$locationProvider) {
$stateProvider
.state('dashboard', {
url: '/dashboard',
templateUrl : 'pages/dashboard.html',
controller : 'DashController'
})
.state('dashboard.vendas', {
url: '/vendas',
templateUrl : 'pages/vendas.html',
controller : 'vendasController'
})
;
$urlRouterProvider.otherwise('/');
$locationProvider.html5Mode({
enabled: true,
requireBase: false
});
});
I load this route /dashboard after the user login. On this route, I load, among other things, an http method that returns the companies that the user will have access to, through the factory below:
app.factory("factoryEmpresas",
function($http,urlBase,isAuthenticated) {
return {
getEmpresas: function() {
return $http({
url: urlBase.getUrl() + '/empresas',
method: "GET",
headers: {
'X-Token': isAuthenticated.getJWT()
}
}).then(function successCallback(response) {
return response.data;
}, function errorCallback(response) {
return response;
});
}
}
});
When accessing the route /vendas (which is related to the state dashboard.vendas), I make another http call using a variable that is in the factoryEmpresas return as follows:
app.factory("factoryVendas",
function($http,urlBase,isAuthenticated) {
return {
getVendas: function(id_empresa) {
return $http({
url: urlBase.getUrl() + '/vendas?id_empresa=' + id_empresa,
method: "GET",
headers: {
'X-Token': isAuthenticated.getJWT()
}
}).then(function successCallback(response) {
return response.data;
}, function errorCallback(response) {
return response;
});
}
}
});
Everything works perfectly, until the user decides to refresh (F5) when on the route /vendas. At that moment the loading of the two routes /dashboad and /vendas is triggered. However, when making the http call at factoryVendas, the factoryDashboard has not yet returned the getEmpresas function. Thus, the variable "empresa_id" is not filled with the value that it should, thus resulting in an undefined call in place of "empresa_id".
The question I ask is, how can I make factorySales wait for the factoryDashboard to return so that I can make the call with the variable duly filled in?
Any help will be welcome.
Thank you!

It sounds like vendas has a direct dependency on empresa. Would it make sense to make empresaId part of the route parameters for accessing vendas? That way the information needed to load the page would always be accessible (from $stateParams), even on reload
.state('dashboard.vendas', {
url: '/:empresaId/vendas/',
templateUrl : 'pages/vendas.html',
controller : 'vendasController'
});
The other thing you could do is define load order by creating a resolve on the parent that the child consumes. Then uirouter knows it needs to wait for the parent resolve before attempting to load the child.
$stateProvider
.state('dashboard', {
url: '/dashboard',
templateUrl : 'pages/dashboard.html',
controller : 'DashController',
resolve: {
Empresas: function(factoryEmpresas) {
return factoryEmpresas.getEmpresas();
}
}
})
.state('dashboard.vendas', {
url: '/vendas',
templateUrl : 'pages/vendas.html',
controller : 'vendasController',
resolve: {
Vendas: function(Empresas, factoryVendas) {
var id_empresa = Empresas[0].id; // just kind of guessing here
return factoryVendas.getVendas(id_empresa);
}
}
})
Vendas and Empresas can be injected into their respective controllers.
While this would work I'd actually discourage this method because now your user is waiting around more for content to appear. Studies have shown its better to show your users incremental progress, your app will feel faster. I try to avoid resolves when possible and have initialization logic in my controller. That way, the page loads immediately and fills in as data becomes available.

Related

Get "initially loaded" (not AJAX call) Response Headers with AngularJS

In looking at How to read response headers in angularjs?, I see answers that involve making an http/ajax request after the page loads then getting the response headers.
I have an angularJS app that needs to render the page (immediately when it loads, not after) using some JSON in a custom response header key that I've added on my python backend.
Is there non-hacky a way to do this? Or is the very attempt at rendering a web page using AngularJS to parse response headers a hack in itself?
My headers look like this (I'm trying to get myjson) :
Keep-Alive:timeout=1, max=100
myjson:{"key2": "value2", "key1": "value1"}
Server:Apache
I think this is what you are aiming for, using $http and ngRoute. Forgive me if I'm misunderstanding. It's just loading data about this question via SO's API. If you click Load Data, you'll see the headers from SO on that page..
var app = angular.module('plunker',['ngRoute']);
app.config(['$routeProvider',
function($routeProvider) {
$routeProvider.
when('/home', {
template: '<h1>Test 1</h1>',
controller: 'HomeCtrl'
}).
when('/loaddata', {
templateUrl: 'load_data.html',
controller: 'LoadDataCtrl',
resolve: {
stackQuestion: function($http){
// We must return something with a promise. $http already does that, so does $resource.
// See: https://docs.angularjs.org/api/ng/service/$q
return $http({
method: 'GET',
url: 'https://api.stackexchange.com/2.2/questions/37491743?order=desc&sort=activity&site=stackoverflow'
})
}
}
}).
otherwise({
redirectTo: '/home'
})
}
]);
Then, in your controller:
app.controller('LoadDataCtrl',function(stackQuestion,$scope){
// now our data is here
$scope.question = stackQuestion.data;
$scope.headers = stackQuestion.headers();
});
http://plnkr.co/edit/ylOfSNqPAqjdg9rYxsbb?p=preview

AngularJS Time to load factory

Hi I've create an application angular, I would login user with a call http get to WS Java. I use a view in angular, to pass parameters between views i use a factory.
My code:
I create angular module
angular.module('contactMgr',['ngRoute'])
.controller('AppCtrl',['$scope',function ($scope){}])
.config(['$routeProvider','$locationProvider',
function($routeProvider,$locationProvider){
$routeProvider.when('/',{
controller: 'homePageCtrl',
templateUrl: 'assets/partials/homePage.html'
})
.when('/login',{
controller: 'loginCtrl',
templateUrl: 'assets/partials/login.html'
})
.when('/loginError',{
controller: 'loginErrorCtrl',
templateUrl: 'assets/partials/loginError.html'
});
}])
Then add controller for login that call WS to execute login. If the WS response ID i call method load in a factory to load the user, and finally i redirect to home page
.controller('loginCtrl',['$scope','$http','$location','UserLogged',
function($scope,$http,$location,UserLogged){
$scope.loginFunction = function(){
$http({method: 'GET',
url: 'http://localhost:8080/TaskManagerWS/login/'+$scope.lg_email+':'+$scope.lg_password
}).then(function success(data,status,xhr){
$scope.idUser = data.data;
if($scope.idUser!=0){
UserLogged.load($scope.idUser);
$location.path("/");
}
else{ $location.path("/loginError"); }
},
function error(data,status,xhr){ alert("Error on call function!"); }
);
};
}])
my factory to load user
.factory('UserLogged',['$http',function($http){
var userLogged;
return{
load: function(id){
$http({
method: 'GET',
url: 'http://localhost:8080/TaskManagerWS/user/id='+id
}).then(function success(data,status,xhr){
userLogged = data.data;
},
function error(data,status,xhr){
window.alert("Error on call function!");
}
);
},
get: function(){
return userLogged;
}
}
}])
And finally my controller for home page
.controller('homePageCtrl',['$scope','UserLogged',function($scope,UserLogged){
$scope.user = UserLogged.get();
}])
All call working fine but, when i redirect to home page I don't see the result.
My home page are only tag "h1" with {{user.name}}.
But if i navigation navbar and change view and then i return to home page the title see.
Why this behavior? I would load immediately factory.
I think that angular before load page and then load factory. There is any instruction to load before factory and then page?
I try to put in the var userLogged on factory a static string and then work fine, the title load immediately. Maybe the time problem are in the $http call.
There is any method to reload view after the response of $http service?
Thanks
SOLVED:
Thanks #Matthew Cawley
I change my loginCTRL and add
UserLogged.loadCall($scope.idUser).then(function success(data,status,xhr){
UserLogged.loadFactory(data.data);
$location.path("/");
},
function error(data,status,xhr){
window.alert("Error on call function!");
}
);
}
and then i change factory to return $http in the method load and finally i add method to loadFactory to load userLogged.
There is a solution that i don't really like, but seems is the only.
It looks like you're redirecting to the home page to soon, i.e. before the UserLogged service has finished loading and set it's ownuserLogged variable to the data returned by the $http call.
This means that when you call $scope.user = UserLogged.get(); in your homePageCtrl it goes straight to the service's get method and returns nothing (because at this point userLogged hasn't been set).
Then, moments later (without really noticing it)... the $http call completes and the userLogged variable becomes set with the results but it's too late.
Solution 1: Return a promise to use in the controller
Return the promise created by the $http service by adding the return keyword:
load: function(id){
return $http({
method: 'GET',
url: 'http://localhost:8080/TaskManagerWS/user/id='+id
})
and then in your loginCtrl, change:
UserLogged.load($scope.idUser);
$location.path("/");
to:
UserLogged.load($scope.idUser)
.then(function(){
$location.path("/");
});
This way you'll only get redirected once the UserLogged.load() method has carried out it's work.
Solution 2: Pass a callback function into the service (or factory).
Create a function in loginCtrl that does a redirect:
var redirectToHome = function(){
$location.path("/");
}
Then add an extra callback parameter to the service's load() function, and execute that callback function when the promise is resolved in the service (after userLogged has been set):
load: function(id, callback) { // <---- Add extra "callback" parameter
$http({
method: 'GET',
url: 'http://localhost:8080/TaskManagerWS/user/id='+id
}).then(function success(data,status,xhr){
userLogged = data.data;
callback(); // <---- Execute the callback function (whatever it contains)
},function error(data,status,xhr){
window.alert("Error on call function!");
});
}
Then in the loginCtrl when you call services login function, do so by passing in a reference to the redirectToHome function as the callback as follows:
UserLogged.load($scope.idUser, redirectToHome); // <--- redirectToHome becomes the callback.

Unable to retrieve the error associated and display in login page

Using AngularJS and Spring Security, when the authentication fails there is a redirect to the login page with error=true. I need to display this error on login page (AngularJS). I don't know how to do this.
Below is the declaration of authentication-failure-url in ssecurity-context.xml:
<security:form-login login-page="/login"
login-processing-url="/authenticate"
authentication-failure-url="/login?error=true"
username-parameter="username" password-parameter="password"/>
Could some please tell me how to retrieve the error and show on login page?
JS
angular.module('app.services')
.service('AuthenticateService'‌​,['$http','$location‌​',function($http,$lo‌​cation)
{
return
{
login : function(uname, pword)
{
var data = "username="+uname+"&password="+pword;
return $http.post("authenticate",data,
{
headers: { "Content-Type": "application/x-www-form-urlencoded" } })
.then(function (response)
{
console.log(response.status);
},function(error)
{
console.log(error.status);
});
}
}
}]);
When your login attempt fails, you're redirecting to /login/error/true
Then, you should have on your angularJS a route to that URL with the partial HTML that you want to display, alongside with an angularJS controller which will handle or do the appropriate actions upon that URL.
For example, let's say that your route file will look this:
var myApp = angular.module("myApp", [ "ngRoute" ]);
myApp.config(function ($routeProvider) {
$routeProvider
.when("/login/error/:errValue",
{controller: "LoginController", templateUrl: "/partials/login.html"})
});
Now all you have to do is tho check on LoginController whether :errValue is true or false. and act upon that value.
with wrong credentials, I tried checking the value of errValue in the controller, but its says undefined
Below are the routes declared
$stateProvider
.state("/login",{ //this for login page
url:'/login',
templateUrl:'./resources/js/baseapp/ContactLogin/views/contactLogin.html',
controller:'contactLoginCtrl'
})
.state("/home",{
url:'/home', // this is for home page
templateUrl:'./resources/js/baseapp/ContactHome/views/ContactHome.html',
controller:'contactHomeCtrl'
})
.state("/login:error/:errValue",{ // this is for login error page
url:'/home',
templateUrl:'./resources/js/baseapp/ContactLogin/views/contactLogin.html',
controller:'contactLoginCtrl'
})
$urlRouterProvider.otherwise("/login");

Angular.js: page flickering on authentication redirect

I'm implementing some simple client-side authentication logic in Angular.js. The pages involved are:
/account#/login (public)
/account (require login)
/account#/settings (require login)
When a user is not logged in and try to visit either /account or /account/#/settings, the app is supposed to redirect to the login page.
I have the following routes configured using ui-router:
$stateProvider
.state('overview', {
url: '/',
restricted: true
})
.state('settings', {
url: '/settings',
restricted: true
})
.state('login', {
url: '/login',
restricted: false
})
and upon URL change, I check if the upcoming page is a restricted page and whether the current user is not logged in. If so redirect to login.
app.run(function($rootScope, $location, $state, auth) {
$rootScope.$on('$stateChangeStart', function(event, next) {
if (next.restricted && !auth.isLoggedIn()) {
event.preventDefault();
$state.go('login');
}
});
});
auth is just a service that checks the login status and returns either true (logged in) or false (not logged in).
Here's my question:
Even though this (kind of) works, I see a page flickering issue when trying to visit a restricted page while not logged in. The page flashes the contents of the restricted page quickly before redirecting me to the login page.
I did a little bit researching online and some people have mentioned the potential solution could be using resolve when defining my states, since the page won't load unless it resolves successfully. However, when I try to add
resolve: {
load: function(auth) {
return auth.isLoggedIn();
}
}
It didn't work. What am I missing? Is using resolve the way to go?
The way you are currently doing it will check if the user is logged in or not and set load to true or false. Also controller gets instantiated before load is resolved which is why you see the flickering. You need to achieve two things here:
Make sure that load is resolved before the controller is instantiated.
If user is not logged in, redirect the user to the login page.
For the first part we need to use a promise as it will be resolved and converted to value before controller is instantiated. This is what the documentation says:
If any of these dependencies are promises, they will be resolved and
converted to a value before the controller is instantiated and the
$stateChangeSuccess event is fired.
Following code can do that for us:
var isLoggedin = ['auth', '$q',
function(auth, $q) {
var deferred = $q.defer();
//assuming auth.isLoggedIn returns a promise
var loginPromise = auth.isLoggedIn();
loginPromise.then(
function(response) {
deferred.resolve(response);
},
function(error) {
deferred.reject('Not logged in');
});
return deferred.promise;
}
];
And states will use isLoggedin:
$stateProvider
.state('overview', {
url: '/',
resolve: {
loggedin: isLoggedin
}
})
.state('settings', {
url: '/settings',
resolve: {
loggedin: isLoggedin
}
})
.state('login', {
url: '/login'
})
For the second problem, that is redirecting the user to login page, you can listen to $stateChangeError event which is fired in case the state is not resolved, and use $state.go to redirect the user.

Transition to state ui-router

I have a http interceptor which has to redirect to the login page if it has a statuscode of 401.
The code is as follow:
responseError: function(response) {
if (response.status === 401) {
$window.sessionStorage.removeItem('token');
$injector.get('$state').go('guest.login');
}
return response || $q.when(response);
}
Here I have the guest.login state defined:
.state('guest', {
abstract: true
})
.state('guest.login', {
templateUrl: 'views/login.html'
url: '/login',
controller: 'LoginCtrl'
})
When I the request returns a 401 error, I get a error:
Could not resolve 'guest.login' from state '...'
I've tried both $state.go and $state.transitionTo.
How am I suppose to go to my login page with ui-router?
Every state tries to populate the ui-view of its parent state. 'guest.login' has the parent 'guest' but the 'guest' state has no ui-view. Adding it in should fix this. This is a common pitfall with ui-router abstract states. I wish we had some better errors here (any errors).
state('guest', {
abstract: true,
template: '<ui-view/>'
})
Read the "Remember" part of this section: https://github.com/angular-ui/ui-router/wiki/Nested-States-%26-Nested-Views#abstract-states

Resources