AngularJS $http.delete and $window.location.reload() - javascript

I have an interesting issue with the $http.delete() and $window.location.reload() functions. Basically I am calling a delete method that in turn calls the $http.delete() call which - using a REST API interface - removes data from a database (mongoDB). For reasons unknown this is what's happening:
delete is successful in the database
this is verified as the data is no longer in the database
also monitored using Chrome DevTools it shows status: 200
$window.location.reload() gets called and nothing happens
Chrome DevTools shows a GET call to the root of the domain but the status is 'pending'
The page does not time out, it basically keeps on loading and loading and loading. Once I hit refresh / CTRL-F5 all goes back to normal and I can see that my item has been deleted.
Some excerpts from my code:
app.js
angular.module('contacts', ['ngRoute', 'contacts.factory', 'contacts.filters', 'ui.bootstrap']).
config(['$routeProvider', '$locationProvider', function($routeProvider, $locationProvider) {
$routeProvider.
when('/', {
templateUrl: '/p/list',
controller: ListCtrl,
}).
otherwise({
redirectTo: '/'
});
$locationProvider.html5Mode(true);
}]);
controllers.js
function ListCtrl($scope, $modal, contactFactory) {
//code removed
$scope.delete = function(c) {
var id = c._id;
var modalInstance = $modal.open({
templateUrl: 'deleteContactModal',
controller: deleteContactModalCtrl,
resolve: {
contact: function() {
return contactFactory.getContact(id);
}
}
});
}
}
var deleteContactModalCtrl = function($scope, $route, $modalInstance, $window, contact, contactFactory) {
$scope.name = contact.data.contact.name;
$scope.deleteContact = function() {
contactFactory.deleteContact(contact.data.contact._id).success(function() {
//have also tried: $window.location.reload(true);
//as well as window.location.href('/') - didn't work either
$modalInstance.close($window.location.reload());
});
};
}
factory.js
angular.module("contacts.factory", []).
factory('contactFactory', function($http){
return {
//code removed
deleteContact: function(id) {
return $http.delete('/api/contact/' + id);
}
}
});
backend - app.js
//usual express setup
app.delete('/api/contact/:id', api.delete); //delete contact
backend - api.js
//code removed
exports.delete = function (req, res) {
var id = req.params.id;
if (id) {
ContactModel.findById(id, function (err, contact) {
contact.remove(function (err) {
if (!err) {
res.json(true);
} else {
res.json(false)
console.log(err);
}
});
});
}
};
At the moment I'm not even sure if this issue is related to the frontend or to the backend. As mentioned before - the backend portion is working fine - the data is deleted from the DB.

Okay the solution seems to be the following:
$scope.deleteContact = function() {
contactFactory.deleteContact(contact.data.contact._id).success(function() {
$modalInstance.close();
contactFactory.getContacts().success(function(contacts) {
return $scope.contacts = contacts;
});
$window.location.reload();
});
};
I need to get the getContacts() method from my factory again during the delete method.

Related

need help understanding angular ui-router specifically passing mongodb data from state to state?

so I'm trying to pass my mongodb data from state to state using ui-router but having trouble making the links and controller as i'm making an app where users have a profile and are able to click on other people profile to see them. I'm able to get the entire list of users profiles but when click, it doesn't get the data so the user profile is blank.
app.js
angular.module('MyApp', ['ui.router']).config(function($stateProvider, $urlRouterProvider, $authProvider) {
/**
* App routes
*/
$stateProvider
.state('home', {
url: '/',
controller: 'HomeCtrl',
templateUrl: 'partials/home.html'
})
.state('about', {
url: '/about',
templateUrl: 'partials/about.html'
})
.state('match', {
url: '/match',
controller: 'matchCtrl',
templateUrl: 'partials/match.html'
})
.state('match.list', {
url: '/list',
controller: 'matchCtrl',
templateUrl: 'partials/match.list.html'
})
//this part is where I need help on most with the controller as it is not working
.state('match.profile', {
url: '/:displayName',
templateUrl: 'partials/match.profile.html',
controller: function($scope, $stateParams) {
$scope.user = $scope.getUserProfile[$stateParams.displayName];
}
});
$urlRouterProvider.otherwise('/');
account.js
angular.module('MyApp').factory('Account',function($http,$stateParams) {
return {
getProfile: function() {
return $http.get('/api/me/:id');
},
getAllProfile: function() {
return $http.get('/api/me');
},
getUserProfile: function() {
return $http.get('/api/me' + $stateParams.displayName);
},
updateProfile: function(profileData) {
return $http.put('/api/me/:id', profileData);
}
};
});
this part works where the mongodb data shows up on the list of users
match.list.html
<div ng-repeat="user in user">
<div class="col-xs-12 col-sm-6 col-md-6">
<div class="well well-sm">
<div class="row">
<h1>{{user.displayName}}</h1>
<h1>{{user.age}} </h1>
<a ng-href="#/match/{{user.displayName}}">
See {{user.displayName}}!
</a>
</div>
</div>
</div>
</div>
the profile part doesn't work as clicking on the a ng-href only lead to a blank profile without data.
match.profile.html
<h1>{{user.displayName}}</h1>
<h1>{{user.age}}</h1>
etc...
how would I go about fixing this so when i click on a user profile using ng-href on the list part. It go to the user profile with the data? Are there any examples that I find similar to this problem with ui-router?
edit
does it have something to do with my controller?
match.js
angular.module('MyApp')
.controller('matchCtrl', function($scope, toastr, Account) {
// set up the filter
$scope.sortUser = 'displayName';
$scope.sortReverse = false;
$scope.searchUser = '';
// get all of the users
$scope.getAllProfile = function () {
Account.getAllProfile()
.then(function (response) {
$scope.user = response.data;
})
.catch(function (response) {
toastr.error(response.data.message, response.status);
});
};
$scope.getUserProfile = function () {
Account.getUserProfile()
.then(function(response) {
$scope.user = response.data;
})
.catch(function (response) {
toastr.error(response.data.message, response.status);
});
};
// get the users
$scope.getAllProfile();
$scope.getUserProfile();
});
the rest api i'm using on node
app.get('/api/me/', function(req, res) {
User.find(function(err, user) {
res.send(user);
});
});
app.get('/api/me/:id', ensureAuthenticated, function(req, res) {
User.findById(req.user, function(err, user) {
res.send(user);
});
});
app.put('/api/me/:id', ensureAuthenticated, function(req, res) {
User.findById(req.user, function(err, user) {
if (!user) {
return res.status(400).send({ message: 'User not found' });
}
user.picture = req.body.picture || user.picture;
user.displayName = req.body.displayName || user.displayName;
user.email = req.body.email || user.email;
user.save(function(err) {
res.status(200).end();
});
});
});
Your match.profile controller is never resolving the promise that's returned from the API by getUserProfile, which is why the UI is blank.
First off, the controller needs the Account service injected into it, as others have noted. The getUserProfile method needs to be called correctly (use () instead of []).
controller: function($scope, $stateParams, Account) {
$scope.user = Account.getUserProfile($stateParams.displayName);
}
I'm also not sure that defining your Account factory to rely on $stateParams is going to work properly, since a factory is a singleton and $stateParams may not update properly as you change states; you'd have to check your Network tab in developer tools to ensure the API endpoint is being built correctly (or just log $stateParams inside the getUserProfile method). I think the better option though would be to take in the url variable as an argument. You're trying to pass it in anyway, but the method isn't expecting any arguments.
getUserProfile: function(displayName) {
return $http.get('/api/me' + displayName);
}
So finally, your controller should look like this
controller: function($scope, $stateParams, Account) {
Account.getUserProfile($stateParams.displayName)
.then(function (profile) {
$scope.user = profile;
});
}
A few other tips with UI-Router
With UI-Router, you should be concerned primarily with states of the application, not URLs. The correct way to transition between states in UI-Router then is to use ui-sref instead of ng-href. Note that ui-sref takes a state name, not a url, so instead of <a ng-href="#/match/{{user.displayName}}">, it'd be better to do <a ui-sref='match.profile({displayName: user.displayName})'> (note how you can still pass in your displayName variable to the $stateParams as an argument.
Your match.profile state is a perfect use case for a resolve function. Resolve functions allow you to load data before the state loads. This ensures that your data is always available to your state before the UI ever renders.
.state('match.profile', {
url: '/:displayName',
templateUrl: 'partials/match.profile.html',
resolve: {
profile: function ($stateParams, Account) {
return Account.getUserProfile($stateParams.displayName)
.then(function (profile) {
return profile;
});
}
},
controller: function($scope, profile) {
$scope.user = profile;
}
});
Notice how you can name the resolve function to be whatever you want, in this case profile. You can inject this directly into your controller and know for certain that your data will already be available to the UI as soon as the controller loads. No loading data, no resolving promises. This is much closer to the proper separation of concerns for a controller in the MVC architecture of Angular where a controller should not be concerned with loading its own data.
You aren't calling method correctly getUserProfile, It not available there in $scope, you have to call it from Account service. Method call happens by parenthesis () not like []. Next thing is, you can get data from getUserProfile method by putting .then function over it.
Code
.state('match.profile', {
url: '/:displayName',
templateUrl: 'partials/match.profile.html',
controller: function($scope, $stateParams, Account) {
Account.getUserProfile($stateParams.displayName)
.then(function(res){
var data = res.data;
$scope.user = data;
}, function(error){
console.log(error);
});
}
});
getUserProfile is a method in Account service. You have used
$scope.getUserProfile[$stateParams.displayName]
Change it to
Account.getUserProfile($stateParams.displayName);
It looks something like this
.state('match.profile', {
url: '/:displayName',
templateUrl: 'partials/match.profile.html',
controller: function($scope, $stateParams, Account) {
$scope.user = Account.getUserProfile[$stateParams.displayName];
}
});
and you have missed a slash in getUserProfile function:
getUserProfile: function() {
return $http.get('/api/me' + $stateParams.displayName);
},
which should be
getUserProfile: function(){
return $http.get('/api/me/' + $stateParams.displayName).then(function(res){
return res.data;
});
}

Resolve user data after full page reload in AngularJS

In my angular application, after full page reload happens, I want to be able to retrieve the user information via $http.get and if the user is logged in($http.get returns user info) then I want to show the 'about me' page, if user is not logged in then they should see the login page.
Currently I tried doing this in application.run method as shown in the code below, but since $http is async, the $rootScope.currentUser does not get set for some time and I get transferred to the login page by my $routeChangeStart event handler even when i'm logged in.
myAPp.config(function ($routeProvider) {
$routeProvider.when('/', {
templateUrl: '/app/homeView/home.html',
controller: 'HomeViewController'
}).when('/login', {
templateUrl: '/app/loginView/login.html',
controller: 'LoginController'
}).when('/me', {
templateUrl: '/app/userInfoView/userInfo.html',
controller: 'UserInfoController',
access: {
requiresLogin: true
}
}).otherwise({
redirectTo: '/'
});
}
);
myApp.run(function ($rootScope, $cookies, $location, UserService) {
UserService.getCurrentUser().then(
function (response) {
$rootScope.currentUser = response;
},
function () {
$rootScope.currentUser = null;
}
);
$rootScope.$on('$routeChangeStart', function (event, next) {
if (next.access !== undefined) {
if (next.access.requiresLogin && !$rootScope.currentUser) {
$location.path('/login');
} else {
$location.path('/me');
}
}
});
});
What is the correct way to solve this problem?
Following what #FuzzyTree started the following should do what you need
myApp.run(function($rootScope, $cookies, $location, UserService) {
var userPromise = UserService.getCurrentUser().then(
function(response) {
$rootScope.currentUser = response;
},
function() {
$rootScope.currentUser = null;
}
);
$rootScope.$on('$routeChangeStart', function(event, next) {
if (next.access !== undefined) {
if (next.access.requiresLogin && !$rootScope.currentUser) {
// prevent this change
event.preventDefault();
// let user promise determine which way to go
userPromise.then(function() {
// will call another `$routeChangeStart` but
// that one will pass around the above conditional
$location.path('/me');// modify using `next.url` if app gets more robust
}).catch(function() {
$location.path('/login');
});
}
}
});
});
you can:
check user then bootstrap angular application https://docs.angularjs.org/api/ng/function/angular.bootstrap
show state loading until user checking is over

Retrieving Data from AngularFire

I'm trying to retrieve data from Angularfire using a service, and then setting the returned value to my scope in my controller.
When I run the code below, I get undefined back for scope.sessions.
SERVICE:
app.factory('sessions', function(){
var refToSessions = new Firebase('myFireBaseURL');
var allSessions = [];
return {
getSessions: function () {
refToSessions.on("value", function (sessions) {
allSessions.push(sessions.val());
return allSessions;
});
}
};
});
CONTROLLER:
app.controller('SessionsCtrl', ['$scope', '$state', 'Auth', 'sessions', function($scope, $state, Auth, sessions){
$scope.sessions = sessions.getSessions();
$scope.submitSession = function() {
console.log($scope.sessions);
}
});
You're trying to return asynchronous data.
You are logging allSessions to the console before the data has downloaded from Firebase.
Use $firebaseArray from AngularFire.
app.constant('FirebaseUrl', '<my-firebase-url>');
app.service('rootRef', ['FirebaseUrl', Firebase);
app.factory('Sessions', function(rootRef, $firebaseArray){
var refToSessions = ref.child('sessions');
return $firebaseArray('sessions');
}
Then injection Sessions into your controller:
app.controller('SessionsCtrl', function($scope, $state, Auth, Sessions){
$scope.sessions = Sessions; // starts downloading the data
console.log($scope.sessions); // still empty
$scope.submitSession = function() {
// likely by the time you click here it will be downloaded
console.log($scope.sessions);
$scope.sessions.$add({ title: 'new session' });
};
});
The data starts downloading once it's injected into your controller. When it's downloaded, $firebaseArray knows to trigger $digest, so it appears on the page.
Since you're using ui-router, you can use resolve to make sure the data exists before injecting it into your controller:
app.config(function ($stateProvider) {
$stateProvider
.state("session", {
controller: "SessionsCtrl",
templateUrl: "views/sessions.html",
resolve: {
sessions: function(Sessions) {
// return a promise that will fulfill the data
return Sessions.$loaded();
}
}
})
});
Now you would change your controller code to this:
app.controller('SessionsCtrl', function($scope, $state, Auth, sessions){
$scope.sessions = sessions; // data is available since injected by router
console.log($scope.sessions); // logs the appropriate data
$scope.submitSession = function() {
$scope.sessions.$add({ title: 'new session' });
};
});

$routeParams undefined when passing to new view & controller

Fairly new to AngularJS and WebAPI here, and figure the best way to learn is by doing. Apologies in advance if this question seems simple - I've spent a day flipping through StackOverflow and tried them all.
I currently have a separate Master & Detail view, both with their own controller. I am trying to pass the selected ID through to the Details controller so I can query my database using the ID, though am getting "undefined" on my $routeParams. I'm unsure if I am missing something simple, or whether I'm even approaching this correctly.
The controller doesn't seem to like it when I inject '$routeParams' either.
My app.js module:
var app = angular.module("ProjectDashboardModule", ["ngRoute"]);
app.config(['$routeProvider', '$locationProvider', function ($routeProvider, $locationProvider) {
$routeProvider
.when("/", { templateUrl: "/Home/Index" })
.when("/Project", { templateUrl: '/Project/Index', controller: 'ProjectCrudController' })
.when("/Project/project/:id", {templateUrl:'/Project/project', controller: 'ProjectTaskController' });
$routeProvider.otherwise({redirectTo: '/home' });
$locationProvider.html5Mode(true);
}]);
my Factory.js:
app.factory('projectFactory', ['$http', function ($http) {
var urlBase = '/api/Projects/';
var projectFactory = {};
projectFactory.getProjects = function () {
return $http.get(urlBase);
};
projectFactory.getSingleProject = function (id) {
return $http.get(urlBase + '/' + id);
};
return projectFactory;
}]);
my ProjectTaskController.js:
app.controller('ProjectTaskController', ['$scope', "$routeParams", 'projectFactory', function ($scope, $routeParams, projectFactory) {
alert($routeParams.id)
$scope.project;
$scope.message;
getProjectById($routeParams.id);
function getProjectById(id) {
projectFactory.getSingleProject(id)
.success(function (data) {
$scope.project = data;
})
.error(function (error) {
$scope.message = 'error retrieving project ' + error.message;
});
}
}]);
I found that my problem was that all my angular script references were scattered. I moved all my custom script references (controller, factory, module) to index.cshtml and fixed the issue.

AngularJS- Login and Authentication in each route and controller

I have an AngularJS application created by using yeoman, grunt and bower.
I have a login page that has a controller that checks for authentication. If the credentials are correct I reroute to home page.
app.js
'use strict';
//Define Routing for app
angular.module('myApp', []).config(['$routeProvider', '$locationProvider',
function($routeProvider,$locationProvider) {
$routeProvider
.when('/login', {
templateUrl: 'login.html',
controller: 'LoginController'
})
.when('/register', {
templateUrl: 'register.html',
controller: 'RegisterController'
})
.when('/forgotPassword', {
templateUrl: 'forgotpassword.html',
controller: 'forgotController'
})
.when('/home', {
templateUrl: 'views/home.html',
controller: 'homeController'
})
.otherwise({
redirectTo: '/login'
});
// $locationProvider.html5Mode(true); //Remove the '#' from URL.
}]);
angular.module('myApp').factory("page", function($rootScope){
var page={};
var user={};
page.setPage=function(title,bodyClass){
$rootScope.pageTitle = title;
$rootScope.bodylayout=bodyClass;
};
page.setUser=function(user){
$rootScope.user=user;
}
return page;
});
LoginControler.js
'use strict';
angular.module('myApp').controller('LoginController', function($scope, $location, $window,page) {
page.setPage("Login","login-layout");
$scope.user = {};
$scope.loginUser=function()
{
var username=$scope.user.name;
var password=$scope.user.password;
if(username=="admin" && password=="admin123")
{
page.setUser($scope.user);
$location.path( "/home" );
}
else
{
$scope.message="Error";
$scope.messagecolor="alert alert-danger";
}
}
});
On the home page I have
<span class="user-info">
<small>Welcome,</small>
{{user.name}}
</span>
<span class="logout">Logout</span>
In the loginController, I check the login info and if it's successful, I set the user object in the service factory. I don't know whether this is correct or not.
What I need is, When the user is logged in, It sets some value in the user object so that all other pages can get that value.
Whenever any route changes happen, the controller should check if the user is logged in or not. If not, it should reroute to the login page. Also, if the user is already logged in and come back to the page, it should go to home page. The controller should also check the credentials on all of the routes.
I have heard about ng-cookies, but I don't know how to use them.
Many of the examples I saw were not very clear and they use some kind of access roles or something. I don't want that. I only want a login filter.
Can someone give me some ideas?
My solution breaks down in 3 parts: the state of the user is stored in a service, in the run method you watch when the route changes and you check if the user is allowed to access the requested page, in your main controller you watch if the state of the user change.
app.run(['$rootScope', '$location', 'Auth', function ($rootScope, $location, Auth) {
$rootScope.$on('$routeChangeStart', function (event) {
if (!Auth.isLoggedIn()) {
console.log('DENY');
event.preventDefault();
$location.path('/login');
}
else {
console.log('ALLOW');
$location.path('/home');
}
});
}]);
You should create a service (I will name it Auth) which will handle the user object and have a method to know if the user is logged or not.
service:
.factory('Auth', function(){
var user;
return{
setUser : function(aUser){
user = aUser;
},
isLoggedIn : function(){
return(user)? user : false;
}
}
})
From your app.run, you should listen the $routeChangeStart event. When the route will change, it will check if the user is logged (the isLoggedIn method should handle it). It won't load the requested route if the user is not logged and it will redirect the user to the right page (in your case login).
The loginController should be used in your login page to handle login. It should just interract with the Auth service and set the user as logged or not.
loginController:
.controller('loginCtrl', [ '$scope', 'Auth', function ($scope, Auth) {
//submit
$scope.login = function () {
// Ask to the server, do your job and THEN set the user
Auth.setUser(user); //Update the state of the user in the app
};
}])
From your main controller, you could listen if the user state change and react with a redirection.
.controller('mainCtrl', ['$scope', 'Auth', '$location', function ($scope, Auth, $location) {
$scope.$watch(Auth.isLoggedIn, function (value, oldValue) {
if(!value && oldValue) {
console.log("Disconnect");
$location.path('/login');
}
if(value) {
console.log("Connect");
//Do something when the user is connected
}
}, true);
Here is another possible solution, using the resolve attribute of the $stateProvider or the $routeProvider. Example with $stateProvider:
.config(["$stateProvider", function ($stateProvider) {
$stateProvider
.state("forbidden", {
/* ... */
})
.state("signIn", {
/* ... */
resolve: {
access: ["Access", function (Access) { return Access.isAnonymous(); }],
}
})
.state("home", {
/* ... */
resolve: {
access: ["Access", function (Access) { return Access.isAuthenticated(); }],
}
})
.state("admin", {
/* ... */
resolve: {
access: ["Access", function (Access) { return Access.hasRole("ROLE_ADMIN"); }],
}
});
}])
Access resolves or rejects a promise depending on the current user rights:
.factory("Access", ["$q", "UserProfile", function ($q, UserProfile) {
var Access = {
OK: 200,
// "we don't know who you are, so we can't say if you're authorized to access
// this resource or not yet, please sign in first"
UNAUTHORIZED: 401,
// "we know who you are, and your profile does not allow you to access this resource"
FORBIDDEN: 403,
hasRole: function (role) {
return UserProfile.then(function (userProfile) {
if (userProfile.$hasRole(role)) {
return Access.OK;
} else if (userProfile.$isAnonymous()) {
return $q.reject(Access.UNAUTHORIZED);
} else {
return $q.reject(Access.FORBIDDEN);
}
});
},
hasAnyRole: function (roles) {
return UserProfile.then(function (userProfile) {
if (userProfile.$hasAnyRole(roles)) {
return Access.OK;
} else if (userProfile.$isAnonymous()) {
return $q.reject(Access.UNAUTHORIZED);
} else {
return $q.reject(Access.FORBIDDEN);
}
});
},
isAnonymous: function () {
return UserProfile.then(function (userProfile) {
if (userProfile.$isAnonymous()) {
return Access.OK;
} else {
return $q.reject(Access.FORBIDDEN);
}
});
},
isAuthenticated: function () {
return UserProfile.then(function (userProfile) {
if (userProfile.$isAuthenticated()) {
return Access.OK;
} else {
return $q.reject(Access.UNAUTHORIZED);
}
});
}
};
return Access;
}])
UserProfile copies the current user properties, and implement the $hasRole, $hasAnyRole, $isAnonymous and $isAuthenticated methods logic (plus a $refresh method, explained later):
.factory("UserProfile", ["Auth", function (Auth) {
var userProfile = {};
var clearUserProfile = function () {
for (var prop in userProfile) {
if (userProfile.hasOwnProperty(prop)) {
delete userProfile[prop];
}
}
};
var fetchUserProfile = function () {
return Auth.getProfile().then(function (response) {
clearUserProfile();
return angular.extend(userProfile, response.data, {
$refresh: fetchUserProfile,
$hasRole: function (role) {
return userProfile.roles.indexOf(role) >= 0;
},
$hasAnyRole: function (roles) {
return !!userProfile.roles.filter(function (role) {
return roles.indexOf(role) >= 0;
}).length;
},
$isAnonymous: function () {
return userProfile.anonymous;
},
$isAuthenticated: function () {
return !userProfile.anonymous;
}
});
});
};
return fetchUserProfile();
}])
Auth is in charge of requesting the server, to know the user profile (linked to an access token attached to the request for example):
.service("Auth", ["$http", function ($http) {
this.getProfile = function () {
return $http.get("api/auth");
};
}])
The server is expected to return such a JSON object when requesting GET api/auth:
{
"name": "John Doe", // plus any other user information
"roles": ["ROLE_ADMIN", "ROLE_USER"], // or any other role (or no role at all, i.e. an empty array)
"anonymous": false // or true
}
Finally, when Access rejects a promise, if using ui.router, the $stateChangeError event will be fired:
.run(["$rootScope", "Access", "$state", "$log", function ($rootScope, Access, $state, $log) {
$rootScope.$on("$stateChangeError", function (event, toState, toParams, fromState, fromParams, error) {
switch (error) {
case Access.UNAUTHORIZED:
$state.go("signIn");
break;
case Access.FORBIDDEN:
$state.go("forbidden");
break;
default:
$log.warn("$stateChangeError event catched");
break;
}
});
}])
If using ngRoute, the $routeChangeError event will be fired:
.run(["$rootScope", "Access", "$location", "$log", function ($rootScope, Access, $location, $log) {
$rootScope.$on("$routeChangeError", function (event, current, previous, rejection) {
switch (rejection) {
case Access.UNAUTHORIZED:
$location.path("/signin");
break;
case Access.FORBIDDEN:
$location.path("/forbidden");
break;
default:
$log.warn("$stateChangeError event catched");
break;
}
});
}])
The user profile can also be accessed in the controllers:
.state("home", {
/* ... */
controller: "HomeController",
resolve: {
userProfile: "UserProfile"
}
})
UserProfile then contains the properties returned by the server when requesting GET api/auth:
.controller("HomeController", ["$scope", "userProfile", function ($scope, userProfile) {
$scope.title = "Hello " + userProfile.name; // "Hello John Doe" in the example
}])
UserProfile needs to be refreshed when a user signs in or out, so that Access can handle the routes with the new user profile. You can either reload the whole page, or call UserProfile.$refresh(). Example when signing in:
.service("Auth", ["$http", function ($http) {
/* ... */
this.signIn = function (credentials) {
return $http.post("api/auth", credentials).then(function (response) {
// authentication succeeded, store the response access token somewhere (if any)
});
};
}])
.state("signIn", {
/* ... */
controller: "SignInController",
resolve: {
/* ... */
userProfile: "UserProfile"
}
})
.controller("SignInController", ["$scope", "$state", "Auth", "userProfile", function ($scope, $state, Auth, userProfile) {
$scope.signIn = function () {
Auth.signIn($scope.credentials).then(function () {
// user successfully authenticated, refresh UserProfile
return userProfile.$refresh();
}).then(function () {
// UserProfile is refreshed, redirect user somewhere
$state.go("home");
});
};
}])
The most straightforward manner of defining custom behavior for individual routes would be pretty easy:
1) routes.js: create a new property (like requireAuth) for any desired route
angular.module('yourApp').config(function($routeProvider) {
$routeProvider
.when('/home', {
templateUrl: 'templates/home.html',
requireAuth: true // our custom property
})
.when('/login', {
templateUrl: 'templates/login.html',
})
.otherwise({
redirectTo: '/home'
});
})
2) In a top-tier controller that isn't bound to an element inside the ng-view (to avoid conflict with angular $routeProvider ), check if the newUrl has the requireAuth property and act accordingly
angular.module('YourApp').controller('YourController', function ($scope, $location, session) {
// intercept the route change event
$scope.$on('$routeChangeStart', function (angularEvent, newUrl) {
// check if the custom property exist
if (newUrl.requireAuth && !session.user) {
// user isn’t authenticated
$location.path("/login");
}
});
});
I wrote a post a few months back on how to set up user registration and login functionality with Angular, you can check it out at http://jasonwatmore.com/post/2015/03/10/AngularJS-User-Registration-and-Login-Example.aspx
I check if the user is logged in the $locationChangeStart event, here is my main app.js showing this:
(function () {
    'use strict';
 
    angular
        .module('app', ['ngRoute', 'ngCookies'])
        .config(config)
        .run(run);
 
    config.$inject = ['$routeProvider', '$locationProvider'];
    function config($routeProvider, $locationProvider) {
        $routeProvider
            .when('/', {
                controller: 'HomeController',
                templateUrl: 'home/home.view.html',
                controllerAs: 'vm'
            })
 
            .when('/login', {
                controller: 'LoginController',
                templateUrl: 'login/login.view.html',
                controllerAs: 'vm'
            })
 
            .when('/register', {
                controller: 'RegisterController',
                templateUrl: 'register/register.view.html',
                controllerAs: 'vm'
            })
 
            .otherwise({ redirectTo: '/login' });
    }
 
    run.$inject = ['$rootScope', '$location', '$cookieStore', '$http'];
    function run($rootScope, $location, $cookieStore, $http) {
        // keep user logged in after page refresh
        $rootScope.globals = $cookieStore.get('globals') || {};
        if ($rootScope.globals.currentUser) {
            $http.defaults.headers.common['Authorization'] = 'Basic ' + $rootScope.globals.currentUser.authdata; // jshint ignore:line
        }
 
        $rootScope.$on('$locationChangeStart', function (event, next, current) {
            // redirect to login page if not logged in and trying to access a restricted page
            var restrictedPage = $.inArray($location.path(), ['/login', '/register']) === -1;
            var loggedIn = $rootScope.globals.currentUser;
            if (restrictedPage && !loggedIn) {
                $location.path('/login');
            }
        });
    }
 
})();
I feel like this way is easiest, but perhaps it's just personal preference.
When you specify your login route (and any other anonymous routes; ex: /register, /logout, /refreshToken, etc.), add:
allowAnonymous: true
So, something like this:
$stateProvider.state('login', {
url: '/login',
allowAnonymous: true, //if you move this, don't forget to update
//variable path in the force-page check.
views: {
root: {
templateUrl: "app/auth/login/login.html",
controller: 'LoginCtrl'
}
}
//Any other config
}
You don't ever need to specify "allowAnonymous: false", if not present, it is assumed false, in the check. In an app where most URLs are force authenticated, this is less work. And safer; if you forget to add it to a new URL, the worst that can happen is an anonymous URL is protected. If you do it the other way, specifying "requireAuthentication: true", and you forget to add it to a URL, you are leaking a sensitive page to the public.
Then run this wherever you feel fits your code design best.
//I put it right after the main app module config. I.e. This thing:
angular.module('app', [ /* your dependencies*/ ])
.config(function (/* you injections */) { /* your config */ })
//Make sure there's no ';' ending the previous line. We're chaining. (or just use a variable)
//
//Then force the logon page
.run(function ($rootScope, $state, $location, User /* My custom session obj */) {
$rootScope.$on('$stateChangeStart', function(event, newState) {
if (!User.authenticated && newState.allowAnonymous != true) {
//Don't use: $state.go('login');
//Apparently you can't set the $state while in a $state event.
//It doesn't work properly. So we use the other way.
$location.path("/login");
}
});
});
app.js
'use strict';
// Declare app level module which depends on filters, and services
var app= angular.module('myApp', ['ngRoute','angularUtils.directives.dirPagination','ngLoadingSpinner']);
app.config(['$routeProvider', function($routeProvider) {
$routeProvider.when('/login', {templateUrl: 'partials/login.html', controller: 'loginCtrl'});
$routeProvider.when('/home', {templateUrl: 'partials/home.html', controller: 'homeCtrl'});
$routeProvider.when('/salesnew', {templateUrl: 'partials/salesnew.html', controller: 'salesnewCtrl'});
$routeProvider.when('/salesview', {templateUrl: 'partials/salesview.html', controller: 'salesviewCtrl'});
$routeProvider.when('/users', {templateUrl: 'partials/users.html', controller: 'usersCtrl'});
$routeProvider.when('/forgot', {templateUrl: 'partials/forgot.html', controller: 'forgotCtrl'});
$routeProvider.otherwise({redirectTo: '/login'});
}]);
app.run(function($rootScope, $location, loginService){
var routespermission=['/home']; //route that require login
var salesnew=['/salesnew'];
var salesview=['/salesview'];
var users=['/users'];
$rootScope.$on('$routeChangeStart', function(){
if( routespermission.indexOf($location.path()) !=-1
|| salesview.indexOf($location.path()) !=-1
|| salesnew.indexOf($location.path()) !=-1
|| users.indexOf($location.path()) !=-1)
{
var connected=loginService.islogged();
connected.then(function(msg){
if(!msg.data)
{
$location.path('/login');
}
});
}
});
});
loginServices.js
'use strict';
app.factory('loginService',function($http, $location, sessionService){
return{
login:function(data,scope){
var $promise=$http.post('data/user.php',data); //send data to user.php
$promise.then(function(msg){
var uid=msg.data;
if(uid){
scope.msgtxt='Correct information';
sessionService.set('uid',uid);
$location.path('/home');
}
else {
scope.msgtxt='incorrect information';
$location.path('/login');
}
});
},
logout:function(){
sessionService.destroy('uid');
$location.path('/login');
},
islogged:function(){
var $checkSessionServer=$http.post('data/check_session.php');
return $checkSessionServer;
/*
if(sessionService.get('user')) return true;
else return false;
*/
}
}
});
sessionServices.js
'use strict';
app.factory('sessionService', ['$http', function($http){
return{
set:function(key,value){
return sessionStorage.setItem(key,value);
},
get:function(key){
return sessionStorage.getItem(key);
},
destroy:function(key){
$http.post('data/destroy_session.php');
return sessionStorage.removeItem(key);
}
};
}])
loginCtrl.js
'use strict';
app.controller('loginCtrl', ['$scope','loginService', function ($scope,loginService) {
$scope.msgtxt='';
$scope.login=function(data){
loginService.login(data,$scope); //call login service
};
}]);
You can use resolve:
angular.module('app',[])
.config(function($routeProvider)
{
$routeProvider
.when('/', {
templateUrl : 'app/views/login.html',
controller : 'YourController',
controllerAs : 'Your',
resolve: {
factory : checkLoginRedirect
}
})
}
And, the function of the resolve:
function checkLoginRedirect($location){
var user = firebase.auth().currentUser;
if (user) {
// User is signed in.
if ($location.path() == "/"){
$location.path('dash');
}
return true;
}else{
// No user is signed in.
$location.path('/');
return false;
}
}
Firebase also has a method that helps you install an observer, I advise installing it inside a .run:
.run(function(){
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
console.log('User is signed in.');
} else {
console.log('No user is signed in.');
}
});
}
For instance an application has two user called ap and auc. I am passing an extra property to each route and handling the routing based on the data i get in $routeChangeStart.
Try this:
angular.module("app").config(['$routeProvider',
function ($routeProvider) {
$routeProvider.
when('/ap', {
templateUrl: 'template1.html',
controller: 'template1',
isAp: 'ap',
}).
when('/auc', {
templateUrl: 'template2.html',
controller: 'template2',
isAp: 'common',
}).
when('/ic', {
templateUrl: 'template3.html',
controller: 'template3',
isAp: 'auc',
}).
when('/mup', {
templateUrl: 'template4.html',
controller: 'template4',
isAp: 'ap',
}).
when('/mnu', {
templateUrl: 'template5.html',
controller: 'template5',
isAp: 'common',
}).
otherwise({
redirectTo: '/ap',
});
}]);
app.js:
.run(['$rootScope', '$location', function ($rootScope, $location) {
$rootScope.$on("$routeChangeStart", function (event, next, current) {
if (next.$$route.isAp != 'common') {
if ($rootScope.userTypeGlobal == 1) {
if (next.$$route.isAp != 'ap') {
$location.path("/ap");
}
}
else {
if (next.$$route.isAp != 'auc') {
$location.path("/auc");
}
}
}
});
}]);
All have suggested big solution why you are worrying of session on client side.
I mean when state/url changes I suppose you are doing an ajax call to load the data for tempelate.
Note :- To Save user's data you may use `resolve` feature of `ui-router`.
Check cookie if it exist load template , if even cookies doesn't exist than
there is no chance of logged in , simply redirect to login template/page.
Now the ajax data is returned by server using any api. Now the point came into play , return standard return types using server according to logged in status of user. Check those return codes and process your request in controller.
Note:- For controller which doesn't require an ajax call natively , you can call a blank request to server like this server.location/api/checkSession.php and this is checkSession.php
<?php/ANY_LANGUAGE
session_start();//You may use your language specific function if required
if(isset($_SESSION["logged_in"])){
set_header("200 OK");//this is not right syntax , it is just to hint
}
else{
set_header("-1 NOT LOGGED_IN");//you may set any code but compare that same
//code on client side to check if user is logged in or not.
}
//thanks.....
On client side inside controller or through any service as shown in other answers
$http.get(dataUrl)
.success(function (data){
$scope.templateData = data;
})
.error(function (error, status){
$scope.data.error = { message: error, status: status};
console.log($scope.data.error.status);
if(status == CODE_CONFIGURED_ON_SERVER_SIDE_FOR_NON_LOGGED_IN){
//redirect to login
});
Note :- I will update more tomorrow or in future
You should check user authentication in two main sites.
When users change state, checking it using '$routeChangeStart' callback
When a $http request is sent from angular, using an interceptor.

Categories