angular $locationChangeStart not getting called properly, what do? - javascript

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) {
});
});

Related

Configuring which entities to alert about in JHipster

I've a jHipster project with the 4.5.1 version (Angular 1) and it does a really nice job. However, I'm already modifying the generated frontend code, which is CRUD focused for all entities, and want to unify many of them.
That said, I would like to be able to choose which entities to alert about using UI-Bootstrap. Now, when I save EntityA which manages EntityB in the same view, I get two alerts for each of the entities. I just want to get the message about the first one.
Is there any way to do it? Or just better, to disable the automatic entity messaging and doing it by hand in the controllers?
The interpectors for the angular http requests are kept in app/blocks/interceptor. There, there's a file called notification.interceptor.js and there's no filter for the entities being displayed, so we need to configure it in some way:
(function() {
'use strict';
angular
.module('myApp')
.factory('notificationInterceptor', notificationInterceptor);
notificationInterceptor.$inject = ['$q', 'AlertService'];
function notificationInterceptor ($q, AlertService) {
var service = {
response: response
};
return service;
function response (response) {
var headers = Object.keys(response.headers()).filter(function (header) {
return header.indexOf('app-alert', header.length - 'app-alert'.length) !== -1 || header.indexOf('app-params', header.length - 'app-params'.length) !== -1;
}).sort();
var alertKey = response.headers(headers[0]);
if (angular.isString(alertKey) && alertKey.indexOf('myEntityToBeDisplayed') !== -1) {
AlertService.success(alertKey, { param : response.headers(headers[1])});
}
return response;
}
}
})();
Then, if we also want to show alerts or log the error responses from the server, the errorhandler.interceptor.js intercepts each of the error responses happening. Tuning it a bit, there's the chance to show alerts for all of them:
(function() {
'use strict';
angular
.module('myApp')
.factory('errorHandlerInterceptor', errorHandlerInterceptor);
errorHandlerInterceptor.$inject = ['$q', '$rootScope'];
function errorHandlerInterceptor ($q, $rootScope) {
var service = {
responseError: responseError
};
return service;
function responseError (response) {
if (!(response.status === 401)) {
$rootScope.$emit('myApp.httpError', response);
}
return $q.reject(response);
}
}
})();
See also:
AngularJS: Catch all response status 500

manual bootstrap angular with http calls

I have a WebAPI service that returns dynamic configuration data. Before my angular app loads I would like to call that service and load the config data into angular. JSFiddle of my attempt at doing that. My question is, after seeing the string test in the console I am seeing nothing else written into the console. How do I get test 2 and wierd wierd to appear into the console
var app = angular.module('app', [])
app.provider("ConfigService", function () {
var self = this;
self.Settings = {};
self.config = function (data) {
console.log(data);
};
this.$get =
function($http) {
return self;
};
});
angular.element(document).ready(function($http) {
console.log('test')
angular.module('app').config([
'ConfigServiceProvider',
function(configService) {
console.log('test 2')
$http.get('http://www.google.com').then(function(result) {
console.log('wierd wierd')
configService.config(result);
angular.bootstrap(document, ['app']);
})
}
]);
});
EDIT
In response to the question, why I do not run this in app.run phase instead.
In the app.run phase the app is still initializing and sometimes it loads up prior to my configuration section being completed. I wanted 100% guarantee that my config section is loaded first before any of the app is.
You can use $http outside of your angular module with angular.injector. With $http you can request the config from your server and bootstrap your app when $http's promise resolves.
JS Fiddle
Create module
var app = angular.module("app", []);
app.provider("configService", function () {
var configService = {
config: {}
};
this.setConfig = function (config) { configService.config = config; };
this.$get = function() { return configService; };
});
Function that fetches config from server
function fetchConfig() {
var $http = angular.injector(["ng"]).get("$http");
return $http.get("http://www.google.com");
}
Function that bootstraps app
function bootstrap(config) {
app.config(["configServiceProvider", function (configServiceProvider) {
configServiceProvider.setConfig(config);
}]).run(["configService", function (configService) {
//Not necessary, just to confirm everything worked
console.log("YAY! You have a config:", configService.config);
}]);
angular.bootstrap(document, ["app"])
}
Put it all together!
fetchConfig().then(
/*sucess*/function (config) { angular.element(document).ready(function () { bootstrap(config); }); },
/*fail*/ function (err) { console.log("UH OH could not retrieve config!", err); });
EDIT: Please use #StevenWexler 's answer: https://stackoverflow.com/a/37599857/5670592. It is much more correct, uses a nifty angular feature ($inject), and will provide configuration before the beginning of the bootstrap cycle.
I have updated the application with your constraints regarding blocking execution until API call is complete.
Try this: https://jsfiddle.net/6svnemu8/3/
I moved the code to the module.run(...) block. This is where all providers are available and you can use $http and your ConfigService. I kept the bootstrap call in the document ready function, and I also added the $q service so you can block execution of the application until the API call is complete. You can verify this by looking at the order of the test outputs in the console:
angular.module('app').run([
'ConfigService', '$http', '$q',
function(configService, $http, $q) {
console.log('test 2');
var deferred = $q.defer();
$http.get('/6svnemu8/2/').then(function(result) {
deferred.resolve(result);
}, function(result){
deferred.reject(result);
});
console.log("test 3");
deferred.promise.then(function(result){
console.log('wierd wierd');
configService.config(result);
}, function(result){
console.log("call failed.");
});
}
]);
Option 1 -- if you have an MVC app
In your main razor view, use JSON.Net to serialize your Model (or a property on it) to JavaScript.
<script>
window.configuration = #(Html.Raw(JsonConvert.SerializeObject(Model)))
</script>
Then put it into an angular constant so you can inject it anywhere you need it, and it's guaranteed to be there. This is the most convenient way to do it.
angular.module('YourModule').constant('configuration', window.configuration);
Option 2 -- loading it asynchronously
This service will load the configuration and cache the promise.
angular.module('YourModule').factory('configuration', ['$http', function($http) {
var configurationLoaded;
var service = {
get: get
};
function get() {
if(configurationLoaded) return configurationLoaded;
configurationLoaded = $http.get( ... );
return configurationLoaded;
}
return service;
}]);
Then anywhere you need it, you'll have to pull out properties from it like this:
angular.module('YourModule').controller('SomeController', ['configuration', function(configuration) {
var vm = this;
configuration.get().then(function(config) {
vm.someSetting = config.someSetting;
});
}]);

Reload a controller not managed by ngRoute on route change

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();
}]);

AngularJS - Factory not working/activating

I am trying to work with AngularJS and a Web API written in VB.NET (Doing this for my internship).
I have my angularApp defined, same for my controller and factory.
I'm also working with the routing from Angular and this works perfectly.
The issue I'm dealing with is that my factory isn't activated or isn't working.
Please take a look at the code below:
My AngularApp.js
var angularApp = angular.module('AngularApp', ['ngRoute']);
angularApp.config(['$routeProvider',
function ($routeProvider) {
$routeProvider
.when('/ExpenseOverview', {
controller: 'ExpenseController',
templateUrl: 'Views/ExpenseOverview.aspx'
})
.when('/AddExpense',
{
controller: 'ExpenseController',
templateUrl: 'Views/AddExpense.aspx'
})
.otherwise({ redirectTo: '/ExpenseOverview' });
}]);
My ExpenseController:
angular.module("AngularApp").controller("ExpenseController", ["$scope", "ExpenseFactory", function ($scope, ExpenseFactory) {
//variabelen
$scope.expenses = [];
$scope.id = 0;
$scope.date = "";
$scope.type = "";
$scope.title = "";
$scope.project = "";
$scope.status = "";
$scope.img = "";
var shown = false;
var dataURL = "";
ExpenseFactory.getList($scope);
}]);
So far my controller isn't doing much more other than retrieving a list of data from the database through the web API.
My ExpenseFactory.js
angular.module("AngularApp").factory("ExpenseFactory", ["$http", function ($http) {
alert("test factory");
var factory = {};
//lijst van expenses ophalen
factory.getList = function ($scope) {
$http.post("/api/Expense/List")
.success(function(data) {
if (data === undefined || data == "") {
data = [];
}
$scope.expenses = data;
$scope.id = $scope.expenses[$scope.expenses.length - 1].Id + 1;
})
.error(function() {
alert("Er is een fout opgetreden");
});
};
factory.saveList = function(expenseList) {
$http.post("/api/Expense/SaveList", { 'expenses': expenseList })
.success(function() {
alert("Expenses have been saved succesfully!");
})
.error(function() {
alert("Something went wrong while saving the expenses");
});
};
return factory;
}]);
As you can see, I have put an alert as the first line of code in the factory. This alert isn't even popping up, which means the factory isn't activating/working.
What is failing in this code?
EDIT
I updated my code to the current version with all the comments about things that might be interfering with the code. I can confirm that none of this was working, so the error is occuring somewhere else.
Another note: I'm working with Visual Studio 2012, if this might have something to do with it, please do elaborate how I can fix this shenannigans.
You missed to return $http promise from factory methods
factory.getList = function ($scope) {
return $http.post("/api/Expense/List")
.success(function(data) {
if (data === undefined || data == "") {
data = [];
}
$scope.expenses = data;
$scope.id = $scope.expenses[$scope.expenses.length - 1].Id + 1;
})
.error(function() {
alert("Er is een fout opgetreden");
});
};
factory.saveList = function(expenseList) {
return $http.post("/api/Expense/SaveList", { 'expenses': expenseList })
.success(function() {
alert("Expenses have been saved succesfully!");
})
.error(function() {
alert("Something went wrong while saving the expenses");
});
};
Rather than passing whole scope, you should create one model object like $scope.model = {} and that will hold the all value of ng-model (scope variables) don't pass whole scope to factory, Pass only relevant data to service method.
As the app, controller and factory are in different files, it's better to avoid referencing the module using angularApp.
Instead use the following syntax:
angular.module('AngularApp').factory("ExpenseFactory", ["$http", function ($http) {
Here,
angular.module('AngularApp')
Means you're getting the module.
If you're going to use var angularApp, you're actually polluting the global namespace with your variable name.
Also if by chance in some other library code, someone reassigns it to something else then your entire application will break.
for example:
in another file, `angularApp = alert;`
And one more thing,
In your controller:
angularApp.controller("ExpenseController", ["$scope", "ExpenseFactory", function ($scope, expenseFactory) {
You have a typo in expenseFactory as it must be ExpenseFactory
angularApp.controller("ExpenseController", ["$scope", "ExpenseFactory", function ($scope, ExpenseFactory) {
Thank you #mohamedrias for helping me find to solution.
It was indeed because of the URL Path that was invalid.
Because of the view that couldn't be loaded, the factory was crashing alongside it.
I don't know how this happened, but it's fixed now!
All the code was correct, except for a reference to an invalid URL path for my views.
Thanks for all the help and comments, I'll keep everything in mind for the upcoming parts of my project.
StackOverflow is awesome!

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.

Categories