Reload a controller not managed by ngRoute on route change - javascript

My web application login process is as follows,
a. The user clicked on the login button, and the link take them to http://localhost:3030/login/facebook
b. The NodeJS backend receives the request, and with passport-facebook, directs the browser to the Facebook login page.
c. The Facebook then direct the user back to the callback with some user data. (the callback: http://localhost:3030/login/facebook/callback)
d. With the user data, an account either exist or will be created, and a token belonging to the account will be created (with JWT)
e. That token will be sent to the user by redirection to http://localhost:3030/#/got_token/the_actual_jwt_string
f. The AngularJS application will take the route with ngRoute, and save the token in localStorage.
The Token Issue Code in NodeJS,
server.get('/auth/facebook', passport.authenticate('facebook', {session: false}));
server.get('/auth/facebook/callback', passport.authenticate('facebook', {session: false}), function(req, res) {
var token = jwt.sign(/* issue a token */);
// put the token into database
res.header('Location', 'http://localhost:3030/#/got_token/' + token);
res.send(302);
});
The routing code in AngularJS,
.when('/got_token/:token*', {
redirectTo: function(parameters, path, search) {
localStorage.setItem('ngStorage-token', JSON.stringify({
token: parameters.token
}));
return '/';
}
})
This works great until that my view hierarchy looks like this,
<body ng-controller="NavigationViewController">
<div ng-view>
</div>
</body>
And the controller code,
controllers.controller('NavigationViewController', ['$scope', '$route', 'token', function($scope, $route, token) {
var _token = token.retrieve();
$scope.token = _token;
$scope.authenticated = (_token != '');
if ($scope.authenticated) {
var payload_string = b64utos(_token.split('.')[1]);
var payload = KJUR.jws.JWS.readSafeJSONString(payload_string);
$scope.displayName = payload.facebookDisplayName;
} else {
$scope.displayName = 'no';
}
$scope.logout = function() {
token.clear();
};
}]);
The routing done by ngRoute does not recreate my NavigationViewController, leaving the _token variable set to the previous state.
I need a way to have the NavigationViewController know that the token has changed since its not involved in routing.

Would like to suggest you two ideas.Here are these two.
(1.) You can add $watch to the token so that whenever it would be changed it will automatically reflect.
(2.) Assign the token into rootscope and update it whenever the token gets changed.
I would like to give you the examples as well if you would needed.
Regards,
Mahendra

Looks like you need to pass data between controllers. Here's a way you could do it. Listen on an event in your NavigationViewController for changed and emit the event it when it changes. Bind it on rootScope.
angular.module('app', [])
.controller('LoginCtrl', function ($scope, $rootScope) {
$scope.changeToken = function () {
$rootScope.$emit('changed', +new Date());
}
})
.controller('NavigationViewController', function ($scope, $rootScope) {
$scope.token = null;
$rootScope.$on('changed', function(event, token) {
$scope.token = token;
});
})

At the end, a simple solution is used -- Events.
As the title of my problem suggests, I want to reload a nested parent view controller on route change. Since the parent of the nested hierarchy is not managed by ngRoute, it won't do it automagically. However, ngRoute has a service ($route) which emit events, and that is the simplest answer.
Basically, I put all the initialization code to a function, and call that function on event.
controllers.controller('NavigationViewController', ['$scope', '$route', '$location', 'token', function($scope, $route, $location, token) {
var updateNavigationBar = function() {
var _token = token.retrieve();
$scope.token = _token;
$scope.authenticated = (_token != '');
if ($scope.authenticated) {
var payload_string = b64utos(_token.split('.')[1]);
var payload = KJUR.jws.JWS.readSafeJSONString(payload_string);
$scope.displayName = payload.facebookDisplayName;
} else {
$scope.displayName = 'no';
}
};
$scope.$on('$routeChangeSuccess', function(event, current, previous) {
if ($location.path() == '/events') {
updateNavigationBar();
}
});
updateNavigationBar();
}]);

Related

How store $http get response in variable accesible from outside $http function (Angularjs)?

I'm still learning angular and javascript and I find stack
overflow really helpful. Actually this is first time I couldn't find solution to my problem in other questions & answers here. I tried many solutions, but nothing is working for me.
In short: I want to save response from $http get (user data) in variable, so I could use it in other functions in this controller.
My factory with $http get function:
app.factory('getUserFactory', ['$http', function($http){
return {
getUserData : function(email, password) {
return $http.get('https://xxxxxxx.xxx.xx/api/v1/user/', {
headers: {
"Authorization": 'Basic '+ window.btoa(email +':'+password)
}
});
}
};
}]);
My controller with functions:
app.controller('UserController', ['$scope','$http', 'getUserFactory', function($scope, $http, getUserFactory) {
var user= {}; //Used to bind input fields from html file
$scope.user= user;
$scope.userData = {}; // <-- HERE I WANT TO STORE RESPONSE
$scope.logIn = function() { // function runs on click
getUserFactory.getUserData(user.email, user.password).success(function(response){
$scope.userData = response.objects[0];
});
};
And simple on click function I use to test if it's working:
$scope.consoleLog = function () {
console.log($scope.userData);
};
I assume my problem is connected with asynchrony of javascript, but I always call $http get first (user clicks 'log in' button) and then I try to use response to display user details. But outside $scope.logIn(), $scope.userData becomes an empty object again.
I assume you are calling login method in onclick.just try this:;
$scope.logIn = function() { // function runs on click
getUserFactory.getUserData(user.email, user.password).then(function(response){
$scope.userData = response.objects[0];
});
};

Get route params from url

I am trying to retrieve a reset pw token from a link in an reset pw email. The link sends the user back to my node/angular app with the token which I am trying to get.
Laravel API: email template
<td class="content">
Reset Your Password
</td>
Node/Angular app: ResetPassword.ejs template: I am using an angular controller:
<div ng-controller="resetPasswordCtrl">
...stuff
</div>
reset password Controller:
'use strict';
angular
.module('myApp')
.controller('resetPasswordCtrl', ['$scope', '$routeParams', '$location', function($scope, $routeParams, $location) {
console.log('ROUTE PARAMS', $routeParams); //object with a bunch of getters/setters
console.log('ROUTE PARAMS', $routeParams.token); //undefined
console.log('Location', $location.search('token')); //LocationHashbangUrl
console.log('Location', $location.search().token); //true
console.log('Location', $location.search()['token']); //true
console.log('Route current params', $route); //empty route object
}]);
For the LocationHashbangUrl ($location.search('token')), I know I am getting the correct url with the token because the $$absUrl shows it.
Why am I unable to retrieve the token param using one of those methods shown in the controller?
Can you post your $routeProvider? You could just add token as a parameter there, and then $routeParams.token would do what you want. Something like this:
$routeProvider.when('/reset-password/:token')
That would mean your reset password url would look like this though:
http://localhost:3000/reset-password/{{$token}}
Turns out without using html5/angular routing, the typical methods
$location.search()
$routeParams
will not work.
Since I am passing the params and accessing my node app externally (from the e-mail link distributed from laravel), I needed to parse the URI using javascript.
I found this resource which makes it easy. So, the following works:
'use strict';
angular
.module('myApp')
.controller('resetPasswordCtrl', ['$scope', '$window', function($scope, $route, $window) {
var getURIParams = function(variable) {
var query = $window.location.search.substring(1);
var vars = query.split('&');
for (var i = 0; i < vars.length; i++) {
var pair = vars[i].split('=');
if (decodeURIComponent(pair[0]) == variable) {
return decodeURIComponent(pair[1]);
}
}
console.log('Query variable %s not found', variable);
};
console.log('VAR', getURIParams('token')); //my token param
}]);

Am I correctly using promise?

I am new to angularJs and I am trying to implement login/logout in my application.
I have an AuthService which logs user in, and a SessionService which writes the auth token to local storage (I am using jwt)
Here the AuthService:
'use strict';
angular.module('App')
.factory('AuthService', ['ApiService', 'SessionService', '$q', '$timeout', 'jwtHelper', function (ApiService, SessionService, $q, $timeout, jwtHelper) {
// inherit
var service = Object.create(ApiService);
service.login = login;
service.logout = logout;
service.check = check;
service.user = user;
return service;
function login(credentials) {
return service.form('user.login', credentials)
.then(function success(response) {
SessionService.setToken(response.token);
return response;
});
}
function logout() {
// here we use a promise so it's easier to handle
// logout in the controller by chaining methods
var d = $q.defer();
$timeout(function () {
SessionService.setToken();
d.resolve();
}, 0);
return d.promise;
}
function check() {
var token = SessionService.getToken();
return !!token && !jwtHelper.isTokenExpired(token);
}
function user() {
return service.call('user', {cache: true});
}
}]);
The problem I am facing it's in the logout method. I have no server call to do, just clear the local storage and user is logged out, but I'd like to handle this with a promise so in the controller I can do the following:
function logout() {
AuthService.logout().then(function success() {
$state.go('login');
});
}
Is this a good way of achieving this ?
I think there is no need for a promise in your particular case, and I would design it in another way :
I would store an "authenticatedUser" inside the $rootScope, with some parameters that I might find usefull (user culture, roles, ...(or just a boolean if there is no other requirement)).
In a kind of "applicationController", I would have a $watch* looking for its value :
$rootScope.$watch('authenticatedUser', function(newVal, oldVal){
if (newVal == oldVal)
return;
if (newVal == null){ //User has been disconnected
//Remove everything from screen
//Display login form
}
});
So, inside your controller, I would just have :
function logout() {
AuthService.logout();
}
That way, if ever one day you decide to be able to logout from another controller (we never know what can happen ;-) ), you will just have to call your service, and everything will be done. There will be no need to duplicate code.
Also, there is something I don't understand in your code :
// inherit
var service = Object.create(ApiService);
In angular, every service is a singleton instanciated during angular bootstrap. Are you sure you want to override this default behaviour?
: pay attention to $watches, they cost lots of processing time during angular digest.

Angular Factory Variables Won't Update - Instance gets old values

I have a resource factory that builds objects for accessing our API. I use an environment variable to determine the base part of the URL - whether or not to include 'account/id' path segments when the admin user is 'engaging' a client account.
The sessionStorage item that holds the 'engagedAsId' doesn't get read, though for instances created after engaging an account. It requires a full reload of the app to pick up that change. Here is the factory code:
myapp.factory('ResourceSvcFactory',
['$rootScope', '$resource',
function ($rootScope, $resource) {
function ResourceSvcFactory (endpoint) {
// vvv problem is here vvv
var accountId = sessionStorage.getItem('engagedAsId');
var apiPath = (accountId != null)
? '/api/account/' + accountId + endpoint
: '/api' + endpoint;
var Resource = $resource(apiPath+':id/',{
// default params
id:''
},{
// custom actions
update: {method: 'PUT'}
});
return Resource;
}
return ResourceSvcFactory;
}]);
myapp.factory('AssetsResource', ['ResourceSvcFactory', function (ResourceSvcFactory) {
var endpoint = '/assets/';
var Resource = ResourceSvcFactory(endpoint);
return Resource;
}]);
I implement this in my Controller like this:
myapp.controller('AssetGroupListCtrl', [ 'AssetgroupsResource', function (AssetgroupsResource) {
var AssetGroups = AssetgroupsResource;
// ... rest of controller
}]);
When i run this it works fine. But, if i change the engaged status in the sessionStorage without a full reload, the instance in the controller does not pick up the new path.
Is there a way to 'refresh' the instance? ...automatically?
After hours of research, it appears that the fundamental flaw in what I'm trying to do in the question is this: I'm trying to use a 'singleton' as a 'class'. from the docs:
Note: All services in Angular are singletons. That means that the injector uses each recipe at most once to create the object. The injector then caches the reference for all future needs.
http://docs.angularjs.org/guide/providers
My work around was to create the $resource inside a method of a returned object. Here is an example:
MyApp.factory('AssetgroupsResource',
['$rootScope', '$resource',
function ($rootScope, $resource) {
return {
init: function () {
var accountId = sessionStorage.getItem('engagedAsId');
var apiPath = (accountId != null)
? '/api/account/' + accountId + endpoint
: '/api' + endpoint;
// default params
id:''
},{
// custom actions
});
return Resource;
}
}
}]);
This made it possible to build the object at the right time in the controller:
MyApp.controller('AssetGroupListCtrl', ['Assetgroups', function (Assetgroups) {
var Assetgroups = AssetgroupsResource.init();
// now I can use angular's $resource interface
}]);
Hope this helps someone. (or you'll tell me how this all could've been done in 3 lines!)
You can always call $scope.$apply(); to force an angular tick.
See a nice tutorial here: http://jimhoskins.com/2012/12/17/angularjs-and-apply.html
I think $resource uses promise which might be an issue depending on how you implement your factory in your controller.
$scope.$apply() can return an error if misused. A better way to make sure angular ticks is $rootScope.$$phase || $rootScope.$apply();.

angular $locationChangeStart not getting called properly, what do?

I'm working on a MEAN app that is based upon Brian Ford's angular-express-blog app on GitHub.
The problem I'm having is that I need to be able to call my UserService service on $locationChangeStart in order to check if there is a user logged. Most of the examples I see have you setting $rootScope.$on('$locationChangeStart'... in the module declaration. This doesn't allow me to access my custom service so my solution was to put it in a controller and call it in my main layout file.
I've set it up like so but the app does nothing. It doesn't even call an error. Can any of you spot the problem with this code?
Here is my github repo.
LayoutCtrl.js:
angular.module('myApp').
controller('LayoutCtrl', function($scope, $http, UserService) {
$scope.$on( "$locationChangeStart", function(event, next, current) {
if ( UserService.getUser() === null ) {
// no logged user, we should be going to #login
if ( next.templateUrl == "partials/login.html" ) {
// already going to #login, no redirect needed
} else {
// not going to #login, we should redirect now
$location.path( "/login" );
}
}
});
});
Layout.jade:
doctype html
html(ng-app="myApp", ng-controller='LayoutCtrl')
head
meta(charset='utf8')
base(href='/')
title Angular Express Seed App
link(rel='stylesheet', href='/css/app.css')
body
block body
And UserService.js:
angular.module('myApp').
service('UserService', function(){
var $scope = this;
var user = null;
$scope.user = {};
$scope.setUser = function(data){
user = data;
};
$scope.getUser = function(){
$scope.user = user;
};
return $scope;
});
I don't understand how your service is supposed to work, your getUser function returns nothing (undefined).
Use this instead:
angular.module('myApp').
service('UserService', function(){
var user;
this.setUser = function(data){
user = data;
};
this.getUser = function(){
return user;
};
});
so your problem is that undefiend !== null
and you are checking for this:
if ( UserService.getUser() === null )
if you want to check if it's undefined (or other falsy values) use this:
if ( ! UserService.getUser() )
also you should inject $location:
controller('LayoutCtrl', function($scope, UserService, $location) {
Debugging
use console.log to check the flow of your application
console.log(UserService.getUser()) # undefined
alternative solution with a run block :
angular.module('myApp').
run(function($rootScope, UserService, $location) {
$rootScope.$on( "$locationChangeStart", function(event, next, current) {
});
});

Categories