Why promise doesnt work as expected in AngularJS - angularjs

In my AngularJS application on every request to change the page i run :
$rootScope.$on('$locationChangeStart', function (event, next, current) {
var user;
$http.get('/api/Authentication/UserAuthenticated').then(function (data) {
console.log("call");
user = data.data;
});
console.log("end of call");
});
When i run application and test what is happening i see in console that "end of call" is returned before console.log("call"); which means that user is not set. Which means that if i want to check if user is logged in on change of route user will be undefined.
How do i make Angular run-> http request and only then keep going?

I misunderstood the question a bit. You can let the $routeProvider resolve the $http promise:
var app = angular.module("myApp");
app.config(["$routeProvider", function($routeProvider) {
$routeProvider.when("/",{
templateUrl: "myTemplate.html",
controller: "MyCtrl",
resolve: {
user: ["$http", "$q", function($http, $q) {
var deferred = $q.defer();
$http.get('/api/Authentication/UserAuthenticated').success(function(data){
deferred.resolve(data.data);
}).error(function(error) {
deferred.resolve(false);
});
return deferred.promise;
}]
}
});
}]);
If the code to fetch the user data is too complex, you could create a service for it, and inject that service in the $routeProvider's resolve function.
In your controller, you just inject the promise (which will be resolved):
app.controller("MyCtrl",["$scope","user", function($scope, user) {
if (!user) {
alert("User not found");
}
...
}]);

use async:false. It is working for me
Try this code, instead of your code
$rootScope.$on('$locationChangeStart', function (event, next, current) {
$http({method: 'GET',
url: '/api/Authentication/UserAuthenticated',
async: false
}).success(function (data) {
console.log("call");
user = data.data;
}
console.log("end of call");
});

Related

MVC and Angularjs : promise does not waiting data

i'm newby in angularjs i researched on the internet but i couldn't find any suitable solution for my problem. I made an http call to get some data from controller. The controller side is okay. But the client-side, promise does not waiting data. Here codes that I wrote ;
//service code
angular.module("myApp").service('$myService', function ($http, $q) {
this.getDataArray = function () {
var deferred = $q.defer();
$http.get('../Home/GetDataArray')
.success(function success(response) {
deferred.resolve(response);
})
.error(function () {
console.log("error getting data array");
deferred.reject();
});
return deferred.promise;
};
}
// controller-code
angular.module("myApp").controller('dataController', function ($scope, $http, $myService) {
$scope.getDataFromService = function () {
$myService.getDataArray().then(function (response) {
$scope.dataArray = response.data;
});
};
});
}
When i call the getDataFromService method at first $scope.dataArray is empty, but the second call, $scope.dataArray is filled with data. Where is the problem? Thanks for helps.
Not an angular expert myself. This is just how I did it when I ran into the same problem. Try this:
Controller:
angular.module("myApp").controller('dataController',[ '$scope', 'Service1', '$http', function ($scope, Service1, $http) {
var deferred = Service1.getDataArray().$promise;
return deferred.then(function successCallback(data, status, headers, config) {
// this callback will be called asynchronously
// when the response is available
$scope.dataArray = response.data;
}, function errorCallback(response) {
// called asynchronously if an error occurs
// or server returns response with an error status.
})
}])
and service:
var service = angular.module("myApp").service('myService', ['ngResource']);
myService.factory('Service1', ['$resource',
function ($resource) {
return $resource('../Home/GetDataArray', {}, {
get: { method: 'GET', isArray: true },
});
}])
The idea is that your service isn't the one that should wait for a return, your controller is. So you should wait for the promise in your controller not your service. In my example I am using factories because, well, that's how I got around it in my project, you can try and implement this directly if you don't want to use a factory.

angularjs redirect to login page if not authenticated with exceptions

EDIT: forgot to mention that i've been working with AngularJs for a week only, so if you see something you think should be changed for the better and is not related to the question itself feel free to tell me on the comments section.
ok, so I have my authentication Controllers and providers which I won't show because they're irrelevant for the scope of the question.
Then I have an interceptor to check if the user is authenticated when a Call is made. If so I set the Authentication header on the request to include the user's Token if not I redirect the user to the login page and don't even make the request to the server (obviously if someone bypasses this theres also an Authorize on the API).
What I want is to add a few exceptions, meaning there are some pages I want to allow even if the user has no Auth Token. I'm able to this if it's a specific path, but I want to allow my 404 page to be accessed and it's in the Routing that I'm specifying .otherwise to go to the 404 page, how can I make so that my interceptor only redirects to login if it's not going to this page.
The interceptor
.factory('authInterceptorService', ['$q', '$location', 'localStorageService', function ($q, $location, localStorageService) {
var authInterceptorServiceFactory = {};
var authData = localStorageService.get('authorizationData');
var _request = function (config) {
config.headers = config.headers || {};
if (authData) {
config.headers.Authorization = 'Bearer ' + authData.token;
} else if ($location.path != '/accounts/login' && $location.path != '/accounts/register') {
$location.path('/accounts/login');
}
return config;
}
var _responseError = function (rejection) {
if (rejection.status === 401) {
$location.path('/accounts/login');
}
return $q.reject(rejection);
}
authInterceptorServiceFactory.request = _request;
authInterceptorServiceFactory.responseError = _responseError;
return authInterceptorServiceFactory;
}])
and in my Routing
$urlRouterProvider.otherwise('/page-not-found');
$stateProvider
(...)//rest of the states
.state('page-not-found', {
url: '/page-not-found',
templateUrl: '/Content/partials/error/404.html',
data: {
displayName: false
}
})
(...)//rest of the states
I tried to add '/page-not-found' to my if but it won't work as expected because by the time the location is checked for the first time it's still not redirected.
edit
As sugested by charlietfl I'm now trying to use resolve but it's not even passing my function.
I removed this code from my interceptor:
else if ($location.path != '/accounts/login' && $location.path != '/accounts/register') {
$location.path('/accounts/login');
}
and add a new service to the authentication module:
.service('authCheckService', ['$http', '$q', 'localStorageService', function ($http, $q, localStorageService) {
var self = {
'onlyLoggedIn': function ($state, $q) {
var deferred = $q.defer();
var authData = localStorageService.get('authorizationData');
console.log(authData);
if (authData) {
deferred.resolve();
} else {
deferred.reject();
$state.go('login');
}
return deferred.promise;
}
}
return self;
}]);
and i'm trying to call it as:
.state('smo-dashboard', {
url: '/dashboard',
templateUrl: '/Content/partials/dashboard.html',
resolve: authCheckServiceProvider.onlyLoggedIn
})
notice that i'm trying to log authData var to check if it's working but it isn't and there's no error on the console also.
Finally figured out how to solve it using resolve.
first of all I completely removed the interceptor I was using before.
then I made a function inside my Routing .config to use with every resolve for the authentication. finally to handle my resolve I'm using $stateChangeError to redirect to the login state
the Routing Config
.config(function ($stateProvider, $urlRouterProvider) {
// function to check the authentication //
var Auth = ["$q", "authService", function ($q, authService) {
authService.fillAuthData;
if (authService.authentication.isAuth) {
return $q.when(authService.authentication);
} else {
return $q.reject({ authenticated: false });
}
}];
/* if the state does not exist */
$urlRouterProvider
.otherwise('/page-not-found');
$stateProvider
// state that allows non authenticated users //
.state('home', {
url: '/',
templateUrl: '/Content/partials/home.html',
})
// state that needs authentication //
.state('smo-dashboard', {
url: '/dashboard',
templateUrl: '/Content/partials/dashboard.html',
resolve: {
auth: Auth
}
})
// errors //
.state('page-not-found', {
url: '/page-not-found',
templateUrl: '/Content/partials/error/404.html'
})
// accounts //
.state('login', {
url: '/accounts/login',
templateUrl: '/Content/partials/account/login.html'
})
// OTHER STATES //
}
);
in the MainController
$scope.$on("$stateChangeError", function (event, toState, toParams, fromState, fromParams, error) {
$state.go("login");
});
An error service like this could help to handle what to do according to status in responses:
'use strict';
/**
* Error Service
*/
angular.module('app.errorService', [])
.factory("errorService", function ($route, $location) {
return {
checkAndReturnError: function(a,b,c) {
if (a.status === 401){
(function(){
return $location.path("/accounts/login");
}());
return;
}
if (a.status === 404)
return;
alert("Error \n *" + a.data.message);
}
};
});
Then when you do your calls if the response status is 401 it will redirect. The vbad thing agout this is you have to add it to all calls:
$scope.pageChanged = function() {
$scope.Promise = Resource.get({}, function(response) {
}, errorService.checkAndReturnError);
};

AngularJS Resolve a route based on a service promise

I am using ngRoute and and trying to get my routes to resolve based on the result of a function in a service I have defined
My service is as follows:
app.factory('AuthService', function ($q,$location,$http) {
var isLoggedIn = false;
return {
hasLoginSession: function(){
var defer = $q.defer();
if(isLoggedIn) {
//User has a valid session from a previous GetSession.json request
defer.resolve(isLoggedIn);
} else {
return $http.get('/session/GetSession.json').success(function(data, status, headers, config) {
isLoggedIn = data.success;
if(isLoggedIn) {
defer.resolve(isLoggedIn);
}
else {
defer.reject("EX_LOGIN_SESSION_IS_UNKNOWN");
}
}).
error(function(data, status, headers, config) {
isLoggedIn=false;
defer.reject("EX_LOGIN_SESSION_IS_UNKNOWN");
});
}
return defer.promise;
}
};
});
So as you can see I just have a simple session check function which sets a property based on the result of a http request.
I then have the routing setup like so, with a resolve just on the route path for testing at the moment:
var app = angular.module('pinpointersApp', ['ngRoute']);
app.config(
function($routeProvider,$httpProvider) {
//$httpProvider.interceptors.push(interceptor);
$routeProvider.
when('/login', {
templateUrl: 'login.html',
controller: 'LoginController'
}).
when('/map', {
templateUrl: 'partials/map.html',
controller: 'MapController'
}).
when('/locations', {
templateUrl: 'partials/locations.html',
controller: 'LocationsController'
}).
when('/', {
templateUrl: 'partials/locations.html',
controller: 'LocationsController',
resolve: {
checkSession: function ($q,AuthService) {
//var defer = $q.defer();
//defer.reject("EX_LOGIN_SESSION_IS_UNKNOWN");
//return defer.promise;
return AuthService.hasLoginSession();
}
}
});
});
app.run(['$rootScope', 'AuthService', function ($rootScope, AuthService) {
$rootScope.$on("$routeChangeError", function (event, current, previous, error) {
console.log(error);
//Perform other stuff here, e.g. redirect to login view
});
}]);
The server side request is being made, and I am seeing a pause in the view loading until the response is received. In my test I am returning a fail case and so reject the state of the promise in order to cause the $routeChangeError to be fired, but it never does and my view continues to load.
If I use the commented out lines of test code in my resolve block instead of my service routine call, so
resolve: {
checkSession: function ($q,AuthService) {
var defer = $q.defer();
defer.reject("EX_LOGIN_SESSION_IS_UNKNOWN");
return defer.promise;
//return AuthService.hasLoginSession();
}
}
then the routeChangeError event is fired, so what am I missing in order to just use the result of my service routine call?
OK, I figured out what I did wrong, in my service I had one too many return statements, I just needed to remove the return keyword before my opening $http request and now it works as required.

how to block users that are not authenticated from specific pages?

i want to implement an authentication/authorization system in angularjs, i found a couple of tutorials, and a lot of them using $routeChangeStart event and testing for a specific pages, and then ask a service to test if this user is authenticated by sending the token to the api.
and that's what i have done, but i didn't get the results that i need.
app.js
var app = angular.module("KhbyraApp", ['ngRoute', 'ngCookies']);
app.config(function ($routeProvider, $locationProvider, $httpProvider) {
$routeProvider
.when("/Register", {
controller: "RegisterController",
templateUrl: "/app/views/register.html",
authenticate : false
})
.when("/Login", {
controller: "LoginController",
templateUrl: "app/views/login.html",
authenticate: false,
})
.when("/Articles", {
controller: "ArticlesController",
templateUrl: "app/views/article.html",
authenticate: true
})
.otherwise({ redirectTo: '/Login' });
$locationProvider.html5Mode(true);
});
app.run(function ($rootScope, $location, $cookieStore, AuthService) {
$rootScope.$on("$routeChangeStart", function (event, next, current) {
if (next.authenticate) {
if (!AuthService.isAuthenticated()) {
$location.url("/Login");
};
};
});
AuthService.js
app.factory('AuthService', function ($http, $q, $window, $cookieStore) {
var factory = {};
var loginUrl = 'http://localhost:2399/Token';
var authUrl = 'http://localhost:2399/Authenticate';
var email;
var token;
factory.Authenticate = function (email, password) {
console.log("AuthService -" + email);
var deferred = $q.defer();
var user = {
email: window.btoa(email),
password: window.btoa(password)
};
$http({
method: 'POST',
url: loginUrl,
data: user
}).success(function (data) {
console.log("AuthService - " + data);
token = data.replace('"', '').replace('"', '');
email = user.email;
deferred.resolve(token);
$cookieStore.put('token', token);
$cookieStore.put('email',user.email);
}).error(function () {
deferred.reject();
});
return deferred.promise;
};
factory.Email = email;
factory.Token = token;
factory.isAuthenticated = function () {
var request = $http({
method: 'GET',
url: authUrl,
headers: { 'Authorization': 'Token '+ $cookieStore.get('email') + ":"+ $cookieStore.get('token') }
}).then(function () {
return true;
}
,function () {
return false;
});
};
return factory;
});
the problem here is in the routeChangeStart even if the AuthService.isAuthenticated() returns true, in the if statements something goes wrong, i think it's about $http returns a promise.
It's normal that the route is loaded even if you're condition fails : Angular doesn't rollback and go back to the previous page. The RouteChangeStart event is actually called just after the URL is changed, when angular detects it.. but the redirection has already been made (there is actually no BeforeRouteChange event)
So you'll have to handle it yourself depending on your needs. For example, in this case, you'll typically force a redirection to the login page. YOu could also display a login popup on top of your page and wait for the login to be successfull to re-execute the previously failed request (which should now works because you're are now logged again). This behavior is exaclty the one of the http-auth-interceptor.
See also angular-app which implements something similar (based on http-auth-interceptor also)

Share async data between controllers without making multiple requests

I'm trying to make a single $http request to get one of my JSON files and use the data across all my controllers.
I saw on egghead.io how to share data across multiple controllers, and I've also read this StackOverflow question: "Sharing a variable between controllers in angular.js".
However, the answers there don't use the $http module. When using $http, the controllers don't have the data to work on, and by the time the response is received it's already too late.
I then found the method $q.defer and this question on StackOverflow: "AngularJS share asynchronous service data between controllers"
The solution posted there works fine, BUT it has two issues:
Each controller will trigger the $http request to obtain the same data already used in another controller; and,
If I try to manipulate the data received I have a then function.
Below you can see my code:
controllers.js
'use strict';
/* Controllers */
function appInstallerListCtrl($scope, Data) {
$scope.apps = Data;
}
function appInstallerDetailCtrl($scope, $routeParams, Data) {
$scope.appId = $routeParams.appId;
$scope.apps = Data;
console.log($scope.apps); // <-- then function
console.log(Data); // <-- then function with $vv data returned but I can't access it
for (var i in $scope.apps) // <--- no way, baby!
console.log(i);
}
app.js
var app = angular.module('appInstaller', []);
app.factory('Data', function($http, $q) {
var defer = $q.defer();
$http.get('apps.json').then(function(result) {
defer.resolve(result.data.versions.version);
});
return defer.promise;
});
app.config(['$routeProvider', function($routeProvider) {
$routeProvider.
when('/app', {templateUrl: 'partials/app-list.html', controller: appInstallerListCtrl}).
when('/app/:appId', {templateUrl: 'partials/app-detail.html', controller: appInstallerDetailCtrl}).
otherwise({redirectTo: '/app'});
}]);
What I'd like to have is that when launching the app, the $http request will be performed and the response will be used throughout the app across all controllers.
Thanks
I like to store my data in the service, and return a promise to the controllers, because usually you need to deal with any errors there.
app.factory('Data', function($http, $q) {
var data = [],
lastRequestFailed = true,
promise;
return {
getApps: function() {
if(!promise || lastRequestFailed) {
// $http returns a promise, so we don't need to create one with $q
promise = $http.get('apps.json')
.then(function(res) {
lastRequestFailed = false;
data = res.data;
return data;
}, function(res) {
return $q.reject(res);
});
}
return promise;
}
}
});
.controller('appInstallerListCtrl', ['$scope','Data',
function($scope, Data) {
Data.getApps()
.then(function(data) {
$scope.data = data;
}, function(res) {
if(res.status === 500) {
// server error, alert user somehow
} else {
// probably deal with these errors differently
}
});
}]);
Any callbacks that are registered after a promise has been resolved/rejected will be resolved/rejected immediately with the same result/failure_reason. Once resolved/rejected, a promise can't change (its state). So the first controller to call getApps() will create the promise. Any other controllers that call getApps() will immediately get the promise returned instead.
Since you are using a promise, to access the data returned by promise use the callback syntax
function appInstallerDetailCtrl($scope, $routeParams, Data) {
$scope.appId = $routeParams.appId;
Data.then(function(returnedData) {
$scope.apps=returnedData;
console.log($scope.apps);
for (var i in $scope.apps)
console.log(i)
});
}
Make sure this
defer.resolve(result.data.versions.version);
resolve returns array, for the above code to work. Or else see what is there in data and ajust the controller code.
I found the way not sure weather it is a best approach to do it or not.
In HTML
<body ng-app="myApp">
<div ng-controller="ctrl">{{user.title}}</div>
<hr>
<div ng-controller="ctrl2">{{user.title}}</div>
</body>
In Javascript
var app = angular.module('myApp', []);
app.controller('ctrl', function($scope, $http, userService) {
userService.getUser().then(function(user) {
$scope.user = user;
});
});
app.controller('ctrl2', function($scope, $http, userService) {
userService.getUser().then(function(user) {
$scope.user = user;
});
});
app.factory('userService', function($http, $q) {
var promise;
var deferred = $q.defer();
return {
getUser: function() {
if(!promise){
promise = $http({
method: "GET",
url: "https://jsonplaceholder.typicode.com/posts/1"
}).success(function(res) {
data = res.data;
deferred.resolve(res);
})
.error(function(err, status) {
deferred.reject(err)
});
return deferred.promise;
}
return deferred.promise;
}
}
});
This will exactly make only 1 HTTP request.
My issue was that I didn't want to wait for resolve before loading another controller because it would show a "lag" between controllers if the network is slow. My working solution is passing a promise between controllers via ui-router's params and the data from promise can be loaded asynchronously in the second controller as such:
app.route.js - setting the available params to be passed to SearchController, which shows the search results
.state('search', {
url: '/search',
templateUrl: baseDir + 'search/templates/index.html',
controller: 'SearchController',
params: {
searchPromise: null
}
})
landing.controller.js - controller where the user adds search input and submits
let promise = SearchService.search(form);
$state.go('search', {
searchPromise: promise
});
search.service.js - a service that returns a promise from the user input
function search(params) {
return new Promise(function (resolve, reject) {
$timeout(function() {
resolve([]) // mimic a slow query but illustrates a point
}, 3000)
})
}
search.controller.js - where search controller
let promise = $state.params.searchPromise;
promise.then(r => {
console.log('search result',r);
})

Resources