Complete beginner to AngularJs, how do I redirect to a certain page in this service method?
The destination should be "/#/login", I've tried with $location and $window but I might very well be missing something.
Thanks
angular.module('app.services', ['ngRoute'])
.service('TaskPlannerApiService', [
'$http', '$q', '$timeout', function taskPlannerApiService($http, $q, $timeout) {
var me = this;
me.getDataAsync = function () {
var logincheck = true;
var apiCall = $q.defer();
var timeoutHanlder = $timeout(function () {
apiCall.reject('Timed out');
console.log('timed out');
}, 2000);
$http.get('/api/login/check').success(function (chk) {
console.log(chk);
logincheck = chk;
console.log(logincheck);
if (chk === false) {
logincheck = false;
}
if (logincheck == true) {
console.log("/api/taskplanner");
$http.get('/api/taskplanner/').success(function (data) {
$timeout.cancel(timeoutHanlder);
apiCall.resolve(data);
console.log(data);
}).error(function (data) {
$timeout.cancel(timeoutHanlder);
apiCall.reject(data);
console.log(data);
});
}
else {
console.log("redirect");
}
})
$timeout(function () { apiCall.notify('Retrieving Data . . .'); }, 0);
return apiCall.promise;
};
}
])
$location Inject this your controller
Next, if you want the place to use the below code.
$location.path('/login');
If you are using angularjs internal routes than you have to inject $location in your controller and give this code below.
$location.path( "/login" );
If you are using ui-router which is quite famous in angularjs then you will be dealing with states. so in such case you have to inject $state in controller and change your state using this:
$state.go('state name')
Related
I am currently using $rootScope to store user information and whether or not the user is logged in. I have tried using $window.localStorage, but with no success. My goal is to have items in my navbar appear through an ng-show once a user is logged on, have their username appear in the navbar, individual user profile view, all users view, etc. I need a persistent login. I have the navbar working with $rootscope, but whenever I try and transition over to $window.localStorage, it fails. Here is the code using $rootScope:
mainModule
angular.module('mainModule', [
'ui.router',
...
])
.config(configFunction)
.run(['$rootScope', '$state', 'Auth', function($rootScope, $state, Auth) {
$rootScope.$on('$stateChangeStart', function(event, next) {
if (next.requireAuth && !Auth.getAuthStatus()) {
console.log('DENY');
event.preventDefault();
$state.go('login');
} else if (Auth.getAuthStatus() || !Auth.getAuthStatus()) {
console.log('ALLOW');
}
});
}]);
Auth Factory
angular.module('authModule').factory('Auth', ['$http', '$state', function authFactory($http, $state) {
var factory = {};
var loggedIn = false;
var userData = {};
factory.getAuthStatus = function() {
$http.get('/api/v1/auth')
.success(function(data) {
if (data.status == true) {
loggedIn = true;
} else {
loggedIn = false;
}
})
.error(function(error) {
console.log(error);
loggedIn = false;
});
return loggedIn;
}
return factory;
}]);
Login Controller
function SigninController($scope, $rootScope, $http, $state) {
$scope.userData = {};
$scope.loginUser = function() {
$http.post('api/v1/login', $scope.userData)
.success((data) => {
$scope.userData = data.data;
$rootScope.loggedIn = true;
$rootScope.userData = data;
$state.go('home');
})
.error((error) => {
console.log('Error: ' + error);
});
};
}
Nav Controller
function NavbarController($scope, Auth) {
$scope.loggedIn = Auth.getAuthStatus();
}
EDIT EDIT EDIT
Here is how I am using local storage. These are the only things that changed.
Login Controller
function SigninController($scope, $window, $http, $state) {
$scope.userData = {};
$scope.loginUser = function() {
$http.post('api/v1/login', $scope.userData)
.success((data) => {
$scope.userData = data.data;
$window.localStorage.setItem('userData', angular.toJson(data));
$window.localStorage.setItem('loggedIn', true);
$state.go('home');
})
.error((error) => {
console.log('Error: ' + error);
});
};
}
Auth Factory
angular
.module('authModule')
.factory('Auth', ['$http', '$window', '$state', function authFactory($http, $window, $state) {
var factory = {};
factory.getAuthStatus = function() {
$http.get('/api/v1/auth')
.success(function(data) {
if (data.status == true) {
$window.localStorage.setItem('loggedIn', true);
} else {
$window.localStorage.setItem('loggedIn', false);
}
})
.error(function(error) {
console.log(error);
$window.localStorage.setItem('loggedIn', false);
});
return $window.localStorage.getItem('loggedIn');
}
return factory;
}]);
I see a potential problem with your use of localStorage.getItem('loggedIn').
Because localStorage only stores strings, what you get back is actually a stringified version of the boolean that you put in. If the string 'false' gets returned, your check of !Auth.getAuthStatus() in main module for example will always evaluate to boolean false because any non-empty string in JavaScript is "truthy".
i.e. !'false' === false (the same as !true === false)
You can get over this by using JSON.parse on the value in localStorage. e.g. JSON.parse(localStorage.getItem('loggedIn')) would parse the string 'false' to the Boolean false.
Simply replace $window.localStorage with window.localStorage and you should be fine.
For example:
function SigninController($scope, $window, $http, $state) {
$scope.userData = {};
$scope.loginUser = function() {
$http.post('api/v1/login', $scope.userData)
.success((data) => {
$scope.userData = data.data;
window.localStorage.setItem('userData', angular.toJson(data));
window.localStorage.setItem('loggedIn', true);
$state.go('home');
})
.error((error) => {
console.log('Error: ' + error);
});
};
}
This being said, storing authenticated status in localStorage (or sessionStorage) is not a good path to go down. Both key/value pairs can be read in the developer pane and then altered (aka spoofed) via the console. A better solution is to return a unique value (GUID) after a successful login and store it in a cookie (set to expire in a short amount of time, like 20 minutes) that can be read on the server and verified there. You can and should use $cookie for this. Your user login state should be controlled server-side, never client-side. The client should always have to prove that it is authenticated.
To persist login, create a service that handles your visitor and let that service handle the login/logout and provide the proof of being logged in. That proof of being logged in should always be a private value that is held internally by the service and not accessible outside of it.
(function () {
'use strict';
var visitorModelService = ['$http', function ($http) {
var loggedIn = false,
visitorModel = {
login:function(){
//do login stuff with $http here
//set loggedIn to true upon success
},
loggedIn:function(){
return loggedIn;
},
logout:function(){
//do logout stuff with $http here
//no matter what, set loggedIn to false
}
};
return visitorModel;
}];
var module = angular.module('models.VisitorModel', []);
module.factory('VisitorModel', visitorModelService);
}());
Doing this, you can simply check for visitor.loggedIn in your ng-show and have everything centralized. Such as:
<a ng-click='visitor.logout' ng-show='visitor.loggedIn'>Log Out</a>
Better yet, put the elements that are only visible to authenticated users in a div tag and hide/show them en-mass.
I am building a single page application on AngularJS and I have a controller set up with a function that is run on a button click. This function runs with a promise. When the function is resolved I am updating a root variable and changing the $location path. But the root variable and $location dont seem to be updating.
Please note this all code is exampled from production
DOM:
<div ng-controller="ExampleController">
<button ng-click="button_function('I am a variable')">Run function</button>
</div>
Controller:
app.controller('ExampleController', ['$scope', '$location', function($scope, $location) {
$scope.button_function = function(variable) {
$scope.$root.show_something = true;
my_function.something(variable).done(function(data) {
if (data) {
$scope.$root.show_something = false;
$location.path('/go-to-path');
} else {
alert('Something went wrong');
}
}]);
};
}]);
This is the my_function code:
var my_function = {
something: function(variable) {
var deferred = $.Deferred();
var window = window.open('http://dynamic.url/', '_blank');
$(window).on('loadstart', function(e) {
var url = e.originalEvent.url;
if (url === 'http://dynamic.url/expected_response') {
window.close();
deferred.resolve({
key_1: 'data',
key_2: 'more data'
});
}
});
return deferred.promise();
}
};
All looks good right? But when the my_function.something(variable) is "done" the $location and $scope.$root.show_something don't seem to update.
Am I doing something wrong?
Thanks
You should return deferred.promise instead of deferred.promise().
--edit: my bad I didn't see you are not using $q as I misread.
I have found the fix.
In my controller after the deferred is "done" I wrapped my variables in $timeout
app.controller('ExampleController', ['$scope', '$location', '$timeout', function($scope, $location, $timeout) {
$scope.button_function = function(variable) {
$scope.$root.show_something = true;
my_function.something(variable).done(function(data) {
if (data) {
$timeout(function() {
$scope.$root.show_something = false;
$location.path('/go-to-path');
}, 1);
} else {
alert('Something went wrong');
}
}]);
};
}]);
Answer found here
Okay, so I'm pretty sure I've found the answer multiple times - I just don't understand it.
I'm building a single page application which on each $routeChangeStart checks if the user is logged in. If the user isn't log in, redirect to login page.
My issue is that configService.isLoggedIn() is using $http, meaning it's asynchronous and the rest of the code won't wait for it to be resolved.
So the question in short: I need the isLoggedIn() function to be resolved before continuing with any other code.
I've seen a lot on $q.defer() but I can't wrap my head around it.
Thanks in advance.
app.service('configService', ['$http', '$q', '$location', '$rootScope', function ($http, $q, $location, $rootScope) {
var self = this;
this.isLoggedIn = function () {
$http.get('/internalAPI.php?fn=login').then(function (result) {
if (result.data.isLoggedIn === true) {
$rootScope.isLoggedIn = true;
}
else {
$rootScope.isLoggedIn = false;
}
}, function() {
$rootScope.isLoggedIn = false;
});
return $rootScope.isLoggedIn;
}
}]);
app.service('navigationService', ['$rootScope', '$location', '$timeout', 'configService', function ($rootScope, $location, $timeout, configService) {
var self = this;
$rootScope.$on('$routeChangeStart', function (event, next, current) {
if (configService.isLoggedIn() !== true) {
// no logged in user, redirect to /login
if (next.templateUrl != "resources/views/login.php") {
$location.path("/login");
$rootScope.subTitle = 'Login';
}
//user is logged in but is trying to view the login page, redirect
} else if (next.templateUrl == 'resources/views/login.php') {
$location.path('/');
}
You can just return promise from your function and operate on this promise object. According to Angular $http documentation the $http object is based on promise mechanism
app.service('configService', ['$http', function ($http) {
var self = this;
this.isLoggedIn = function () {
return $http.get('/internalAPI.php?fn=login');
}
}]);
app.service('navigationService', ['$rootScope', '$location', '$timeout', 'configService', function ($rootScope, $location, $timeout, configService) {
var self = this;
$rootScope.$on('$routeChangeStart', function (event, next, current) {
configService.isLoggedIn().then(function(result) {
if (result !== true) {
// no logged in user, redirect to /login
if (next.templateUrl != "resources/views/login.php") {
$location.path("/login");
$rootScope.subTitle = 'Login';
}
//user is logged in but is trying to view the login page, redirect
} else if (next.templateUrl == 'resources/views/login.php') {
$location.path('/');
}
}
}
}
Many thanks for that. Went for the approach Rene M. mentioned regarding building an interceptor and let the server-side scripts handle the authentication.
Definitely does the trick, if the 403 status is returned then redirect the user to the login page and update the isLoggedIn variable. Refactoring the code to remove use of the rootScope, was a dirty workaround until I got a hang of the whole angular way of authentication.
Attached a simple example below in case anyone would stumble upon this in the future.
app.config(['$routeProvider', '$locationProvider', '$httpProvider', function ($routeProvider, $locationProvider, $httpProvider) {
$httpProvider.interceptors.push('responseObserver');
app.factory('responseObserver', ['$location', '$q', function ($location, $q) {
return {
'responseError': function (errorResponse) {
switch (errorResponse.status) {
case 403:
//Place logic here
break;
case 500:
//Place logic here
break;
}
return $q.reject(errorResponse);
}
};
}]);
I am trying to create an Angular Factory, this is based on a example from a plural site course http://www.pluralsight.com/training/player?author=shawn-wildermuth&name=site-building-m7&mode=live&clip=3&course=site-building-bootstrap-angularjs-ef-azure.
From debugging the code in Chrome it appears to run fine. I can see when I debug it that the service gets my data and puts it in my array but when I look at the controller in either $scope.data or dataService.data the arrays are empty. I don't see any javascript errors. I'm not sure what I'm doing wrong, any suggestions. I'm using AngularJS v1.3.15.
module.factory("dataService", function($http,$routeParams,$q) {
var _data = [];
var _getData = function () {
var deferred = $q.defer();
$http.get("/api/v1/myAPI?mainType=" + $routeParams.mainType + "&subType=" + $routeParams.subType)
.then(function (result) {
angular.copy(result.data,_data);
deferred.resolve();
},
function () {
//Error
deferred.reject();
});
return deferred.promise;
};
return {
data: _data,
getData: _getData
};});
module.controller('dataController', ['$scope', '$http', '$routeParams', 'dataService',function ($scope, $http, $routeParams, dataService) {
$scope.data = dataService;
$scope.dataReturned = true;
$scope.isBusy = true;
dataService.getData().then(function () {
if (dataService.data == 0)
$scope.dataReturned = false;
},
function () {
//Error
alert("could not load data");
})
.then(function () {
$scope.isBusy = false;
})}]);
On
return {
data: _data,
getData: _getData
};});
you have "data: _data," while your array is named just "data". Change the name of the variable to match and it will work:
var _data = [];
Why would you use deferred from $q this way?
The proper way to use $q:
$http.get("/api/v1/myAPI?mainType=" + $routeParams.mainType + "&subType=" + $routeParams.subType)
.success(function (result) {
deferred.resolve(result);
}).error(
function () {
//Error
deferred.reject();
});
And then in controller
dataService
.getData()
.then(function success(result) {
$scope.data = result; //assing retrived data to scope variable
},
function error() {
//Error
alert("could not load data");
});
In fact, there are some errors in your codes :
In your Service, you define var data = [];, but you return data: _data,. So you should correct the defination to var _data = []
you don't define _bling, but you use angular.copy(result.data,_bling);
One more question, why do you assigne the service to $scope.data : $scope.data = dataService ?
EDIT :
Notice that there 3 changes in the following codes:
comment the $scope.data = dataService;, because it makes no sense, and I think that $scope.data should be the data that the service returns.
$scope.data = dataService.data;, as I described in 1st point. You can see the result from the console.
In the if condition, I think that you want to compare the length of the returned data array, but not the data.
module.controller('dataController', ['$scope', '$http', '$routeParams', 'dataService',function ($scope, $http, $routeParams, dataService) {
// $scope.data = dataService;
$scope.dataReturned = true;
$scope.isBusy = true;
dataService.getData().then(function () {
if (dataService.data.length === 0){
$scope.dataReturned = false;
}else{
$scope.data = dataService.data;
console.log($scope.data);
}
},
// other codes...
})}]);
I am using the $cacheFactory to store a language JSON file for the app, and i run a factory method every new route like this:
index.js
$routeProvider
.when('/',{
'templateUrl':'views/home/index.html',
'controller':'Home',
'resolve': {
'onEnter': function ($rootScope, langFactory) {
return langFactory.getLangFile($rootScope.lang.appLang);
}
}
})
.when('/auth/login',{
'templateUrl':'views/auth/login.html',
'controller':'AuthLogin',
'resolve': {
'onEnter': function ($rootScope, langFactory) {
return langFactory.getLangFile($rootScope.lang.appLang);
}
}
})
factories.js
.factory('langFactory', ['$rootScope', '$window', '$http', '$cacheFactory', '$q', function ($rootScope, $window, $http, $cacheFactory, $q) {
var getLangFile = function (langCode) {
var deferred = $q.defer()
, cache = $cacheFactory('langCache');
if (!!!cache.get('cache' + $rootScope.lang.appLang)) {
$http.get(langCode + '-langFile-to-be-REMOVED.json').success(function (response) {
cache.put('cache' + $rootScope.lang.appLang, response);
deferred.resolve();
}).error(function (err) {
$window.console.error('Unable to retrieve app language: ' + err);
deferred.reject(err);
});
} else {
deferred.resolve();
}
return deferred.promise;
};
return {
'getLangFile':getLangFile
};
}])
On first page load it works , then if i browse, without refreshing, to auth/login i get a console error:
[$cacheFactory:iid] CacheId 'langCache' is already taken!
Seems like (since i call the factory method on every route) it cant use the same id !?
I actually dont know what to do to fix this, any help appriciated, thanks.
This is what worked for me:
cache = $cacheFactory.get('langCache') || $cacheFactory('langCache');
Ok thanks to a guy on IRC i fixed this problem, i just had to change this:
var getLangFile = function (langCode) {
var deferred = $q.defer()
, cache = $cacheFactory('langCache');
to
var cache = $cacheFactory('langCache')
, getLangFile = function (langCode) {
var deferred = $q.defer();