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.
Related
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
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];
});
};
I've checked several solutions on the web but I don't quite understand how to stop the controllers from loading. So, I've created a plunkr to highlight what I want to do.
Basically: I want to load all data in a service and then pass around that data from that service to each controller. When the app first loads, because it's Async, the controllers are loaded first.
I could have just used the factory in each controller, but I want to hold that data in the "allProducts" property of the service. I don't see the need to call the factory function each time a view loads.
In the example, I've also tried with the $q service, but seems to me it has the same behaviour just like the http from the factory and still needs to call the http request on each view load...
So, could somebody help me with this example and implement an elegant solution?
app.factory('productsFactory', ['$http', '$q',
function($http, $q) {
var cachedData; //here we hold the data after the first api call
function getData(callback) {
if (cachedData) {
callback(cachedData);
} else {
$http.get('http://api.bestbuy.com/v1/products(longDescription=iPhone*|sku=7619002)?show=sku,name&pageSize=15&page=5&apiKey=bqs7a4gwmnuj9tq6bmyysndv&format=json')
.success(function(data) {
cachedData = data; //caching the data in a local variable
callback(data);
});
}
}
return {
getProds: getData
}
}
])
app.service('appService', ['productsFactory', '$q',
function(productsFactory, $q) {
var _this = this;
productsFactory.getProds(function(data) {
_this.allProducts = data; //wait for data to load before loading the controllers
})
}
])
app.controller('productsCtrl', ['$scope', 'appService',
function($scope, appService) {
$scope.myProducts = appService.allProducts;
}
]);
plunkr here: http://plnkr.co/edit/ZvtYwXHSasC3fCAZKkDF?p=preview
I didn't actually test it, but looks like you need to create and return a promise in order to have the data returned when it's available.
app.factory('productsFactory', ['$http', '$q',
function($http, $q) {
var cachedData; //here we hold the data after the first api call
function getData(callback) {
var d = $q.defer();
if (cachedData) {
d.resolve(callback(cachedData));
} else {
$http.get('http://api.bestbuy.com/v1/products(longDescription=iPhone*|sku=7619002)?show=sku,name&pageSize=15&page=5&apiKey=bqs7a4gwmnuj9tq6bmyysndv&format=json')
.success(function(data) {
cachedData = data; //caching the data in a local variable
d.resolve(callback(cachedData));
});
}
return d.promise;
}
return {
getProds: getData
}
}
])
app.service('appService', ['productsFactory', '$q',
function(productsFactory, $q) {
var _this = this;
productsFactory.getProds(function(data) {
_this.allProducts = data; //wait for data to load before loading the controllers
})
}
])
app.controller('productsCtrl', ['$scope', 'appService',
function($scope, appService) {
$scope.myProducts = appService.allProducts;
}
]);
Check this: http://plnkr.co/edit/ey0na3l2lyT0tUdbDyrf?p=preview
Changes:
- a main app controller that wraps all your app.
In this controller we prevent route change if the boot hasn't finished all its jobs. When boot is finished, we change location to the main/default route.
- in our run block we set the bootStatus var to false and wait until products are fetch.
Also, I've stored the result from service to $rootScope so you can use that data in all your controllers without injecting the service over and over again.
FINALLY!!! I Managed to do what I was looking for.
Note, this is not necessarily a best practice, but it was a problem that was bugging me and wanted to know how it's done.
The way I did it was to create a global variable, where I use a main resolve function to wait for the factory to do the http get and then pass it to the service. Then, I use resolve on every state where I need that data and reference that function.
UPDATE: Realized that I was calling the same factory function each time the state was changins, so I decided to go with a variable - a property in the appService which turns to true when the http get was called once: appService.retrieved and changed the main resolve function a bit.
var mainResolve = ['$q', 'appService', 'productsFactory', function($q, appService, productsFactory) {
var defer = $q.defer();
if(appService.retrieved) {
defer.resolve();
} else {
productsFactory.getProds(function(data) {
appService.allProducts = data;
defer.resolve();
})
appService.retrieved = true;
}
return defer.promise;
}]
And in the state
.state('home', {
url: "/home",
templateUrl: "home.html",
controller: 'homeCtrl',
resolve: {
waitingFor: mainResolve
}
})
You can find the plnkr with the working solution, here: http://plnkr.co/edit/ZvtYwXHSasC3fCAZKkDF?p=preview
Again, the factory could be refactored some more and eliminate some code like the caching data. This way, we only go once to the factory function.
I'm looking for some information on the best way to retrieve data from a local JSON file and handle the response. After browsing through Stack Overflow, I have some mixed thoughts as I've seen multiple ways of doing the same thing (although no explanation on why one may or may not be preferred).
Essentially, I have an Angular app that is utilising a factory to retrieve data from a JSON file; I'm then waiting for the response to resolve in my controller before using it in my html file, similar to the below:
Option 1
Factory:
comparison.factory('Info', ['$http', function($http) {
var retrievalFile = 'retrievalFile.json';
return {
retrieveInfo: function() {
return $http.get(retrievalFile);
}
}
}]);
Controller:
comparison.controller('comparisonController', ['$scope', 'Info', function($scope, Info) {
Info.retrieveInfo().then(function(response) {
$scope.info = response.data;
});
}]);
My main point of contention is figuring out when it's best to wait for the response to resolve, or if it even matters. I'm toying with the idea of having the factory return the fulfilled promise, and wait for the controller to retrieve the data also. In my view, it's best to abstract all data retrieval out of the controller and into the factory, but I'm not sure if this extends to waiting for the actual data to be returned within the factory itself. With this in mind, I'm confused about whether to opt for option 1 or option 2 and would really appreciate some feedback from more experienced/qualified developers!
Option 2
Factory:
comparison.factory('Info', ['$http', function($http) {
var retrievalFile = 'retrievalFile.json';
return {
retrieveInfo: function() {
return $http.get(retrievalFile).then(function(response) {
return response.data;
});
}
}
}]);
Controller:
comparison.controller('comparisonController', ['$scope', 'Info', function($scope, Info) {
Info.retrieveInfo().then(function(response) {
$scope.info = response;
});
}]);
Thank you for any input/suggestions in advance!
It depends on what your controller is expecting and how you set up your application. Generally, I always go with the second option. Its because I usually have global error or success handlers in all api requests and I have a shared api service. Something like below.
var app = angular.module('app', []);
app.service('ApiService', ['$http', function($http) {
var get = function(url, params) {
$http.get(url, { params: params })
.then(handleSuccess, handleError);
};
// handle your global errors here
// implementation will vary based upon how you handle error
var handleError = function(response) {
return $q.reject(response);
};
// handle your success here
// you can return response.data or response based upon what you want
var handleSuccess = function(response) {
return response.data;
};
}]);
app.service('InfoService', ['ApiService', function(ApiService) {
var retrieveInfo = function() {
return ApiService.get(retrievalFile);
/**
// or return custom object that your controller is expecting
return ApiService.get.then(function(data) {
return new Person(data);
});
**//
};
// I prefer returning public functions this way
// as I can just scroll down to the bottom of service
// to see all public functions at one place rather than
// to scroll through the large file
return { retrieveInfo: retrieveInfo };
}]);
app.controller('InfoController', ['InfoService', function(InfoService) {
InfoService.retrieveInfo().then(function(info) {
$scope.info = info;
});
}])
Or if you are using router you can resolve the data into the controller. Both ngRouter and uiRouter support resolves:
$stateProvider.state({
name: 'info',
url: '/info',
controller: 'InfoController',
template: 'some template',
resolve: {
// this injects a variable called info in your controller
// with a resolved promise that you return here
info: ['InfoService', function(InfoService) {
return InfoService.retrieveInfo();
}]
}
});
// and your controller will be like
// much cleaner right
app.controller('InfoController', ['info', function(info) {
$scope.info = info;
}]);
It's really just preference. I like to think of it in terms of API. What is the API you want to expose? Do you want your controller to receive the entire response or do you want your controller to just have the data the response wraps? If you're only ever going to use response.data then option 2 works great as you never have to deal with anything but the data you're interested in.
A good example is the app we just wrote where I work. We have two apps: a back-end API and our front-end Angular application. We created an API wrapper service in the front-end application. In the service itself we place a .catch for any of the API endpoints that have documented error codes (we used Swagger to document and define our API). In that .catch we handle those error codes and return a proper error. When our controllers/directives consume the service they get back a much stricter set of data. If an error occurs then the UI is usually safe to just display the error message sent from the wrapper service and won't have to worry about looking at error codes.
Likewise for successful responses we do much of what you're doing in option 2. In many cases we refine the data down to what is minimally useful in the actual app. In this way we keep a lot of the data churning and formatting in the service and the rest of the app has a lot less to do. For instance, if we need to create an object based on that data we'll just do that in return the object to the promise chain so that controllers aren't doing that all over the place.
I would choose option two, as it your options are really mostly the same. But let see when we add a model structure like a Person suppose.
comparison.factory('Info', ['$http', function($http) {
var retrievalFile = 'retrievalFile.json';
return {
retrieveInfo: function() {
return $http.get(retrievalFile).then(function(response) {
//we will return a Person...
var data = response.data;
return new Person(data.name, data.age, data.gender);
});
}
}
}]);
This is really simple, but if you have to map more complex data into object models (you retrieve a list of people with their own items... etc), that's when things get more complicated, you will probably want to add a service to handle the mapping between data and models. Well you have another service DataMapper(example), if you choose your first option you will have to inject DataMapper into your controller and you will have to make your request through your factory, and map the response with the injected service. And then you probably say, Should I have all this code here? ... Well probably no.
That is an hypothetical case, something that count a lot is how you feel structuring your code, won't architecture it in a way you won't understand. And at the end take a look at this: https://en.wikipedia.org/wiki/SOLID_(object-oriented_design) and research more information about this principles but focused to javascript.
Good question. A couple of points:
Controllers should be view centric versus data centric therefore you
want remove data logic from the controller and rather have it focus
on business logic.
Models (M in MVC) are a data representation of your application and
will house the data logic. In Angular case this would be a service
or factory class as you rightfully pointed out. Why is that well for
example:
2.1 AccountsController (might have multiple data models injected)
2.1.1 UserModel
2.1.2 AuthModel
2.1.3 SubscriptionModel
2.1.4 SettingsModel
There are numerous ways to approach the data model approach, but I would say your service class should be the data REST model i.e. getting, storing, caching, validating, etc. I've included a basic example, but suggest you investigate JavaScript OOP as that will help point you in the right direction as to how to build data models, collections, etc.
Below is an example of service class to manage your data.Note I have not tested this code but it should give you a start.
EXAMPLE:
(function () {
'use strict';
ArticleController.$inject = ['$scope', 'Article'];
function ArticleController($scope, Article) {
var vm = this,
getArticles = function () {
return Article.getArticles()
.then(function (result) {
if (result) {
return vm.articles = result;
}
});
};
vm.getArticles = getArticles;
vm.articles = {};
// OR replace vm.articles with $scope if you prefer e.g.
$scope.articles = {};
$scope.userNgClickToInit = function () {
vm.getArticles();
};
// OR an init on document ready
// BUT to honest I would put all init logic in service class so all in calling is init in ctrl and model does the rest
function initArticles() {
vm.getArticles();
// OR chain
vm.getArticles()
.then(getCategories); // doesn't here, just an example
}
initArticles();
}
ArticleModel.$inject = ['$scope', '$http', '$q'];
function ArticleModel($scope, $http, $q) {
var model = this,
URLS = {
FETCH: 'data/articles.json'
},
articles;
function extract(result) {
return result.data;
}
function cacheArticles(result) {
articles = extract(result);
return articles;
}
function findArticle(id) {
return _.find(articles, function (article) {
return article.id === parseInt(id, 10);
})
}
model.getArticles = function () {
return (articles) ? $q.when(articles) : $http.get(URLS.FETCH).then(cacheArticles);
};
model.getArticleById = function (id) {
var deferred = $q.defer();
if (articles) {
deferred.resolve(findArticle(id))
} else {
model.getBookmarks().then(function () {
deferred.resolve(findArticle(id))
})
}
return deferred.promise;
};
model.createArticle = function (article) {
article.id = articles.length;
articles.push(article);
};
model.updateArticle = function (bookmark) {
var index = _.findIndex(articles, function (a) {
return a.id == article.id
});
articles[index] = article;
};
model.deleteArticle = function (article) {
_.remove(articles, function (a) {
return a.id == article.id;
});
};
}
angular.module('app.article.model', [])
.controller('ArticleController', ArticleController)
.service('Article', ArticleModel);
})()
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) {
});
});