I am developing an application in Angular that makes many calls to a couple of webservices. I want to develop an offline component to the app such that it will cache certain webservice results to LocalStorage and use them when the connection is offline.
That part is fairly simple, and the part I am having the most trouble with is how to branch logically when the app is offline.
Here is my current flow:
User loads the page
Webservice calls happen as usual
$http interceptor looks for a 404 error and marks a $rootScope.isOnline boolean flag to indicate we are offline, otherwise if no 404 then we mark as online
I want my code to branch depending on this flag, in a way that is maintainable. As such, I was looking at using dependency injection to inject either an 'online' service which makes calls to the webservice, or an 'offline' service, which interfaces with the LocalStorage results if they exist.
Can I base my dependency injection based off the online/offline flag to inject the correct service like so?
.factory('AuthService', ['$rootScope', '$injector', function($rootScope, $injector) {
if($rootScope.isOnline) {
return $injector.get('OnlineAuthService');
}
else {
return $injector.get('OfflineAuthService');
}
}])
.service('OnlineAuthService', ['$rootScope', '$http', '$location', 'serviceEndpoint', 'securityEndpoint', 'organisationId', function ($rootScope, $http, $location, serviceEndpoint, securityEndpoint, organisationId) {
this.ensureSession = function (data) {
// Do some connection to the webservice
};
}])
.service('OfflineAuthService', ['$rootScope', function ($rootScope) {
this.ensureSession = function (data) {
// Do some LocalStorage stuff
};
}])
AuthService.ensureSession(data);
The issue I am having is that $rootScope.isOnline is not marked as offline before the first call to my webservice, so even when the connection is offline, the dependency injection looks at $rootScope.isOnline and injects the OnlineAuthService.
Is this the correct way of developing an Online/Offline app in Angular or is there a better way?
There are many ways to do this. Take a look at this simplified plunker:
http://plnkr.co/edit/CpiJaF480Mai050b63RC?p=preview
The idea there is that you have the singleton service object returned by the service, but then you copy over different function methods as online/offline changes.
There are many other ways to do this, such as adding levels of indirection (creating a service from which you can request the current active service for example).
You can not simply do it with angular injection, because both factory and service calls will get called once and only once and will simply register a single instance for the service name (they will not get called every time you inject).
Related
I have read other postings that discuss ways that other technologies clean up the browser cookies when the browser is closed down, but none of them show how to get AngularJS to do this. How do I trigger a cookie removal method in AngularJS to run when the browser is closed?
In AngularJS 1.4.8, cookies can be removed with the syntax $cookies.remove('keyname'). I can write a method to remove all the cookies by name. But how do I make sure that the cookie removal method is called whenever the browser is closed? Is the syntax any different if I want the method to be called when a browser tab is closed?
ONGOING EFFORTS:
As per #User2341963's suggestion, I added cookie removal code to the run method in the main module of the app. The same exact code runs correctly when I put it in a logout() method elsewhere in the app, but when I put breakpoints in the firefox debugger and close the browser, I see that the cookie removal code is never run when the browser is closed. What specific changes do I make to the code to get the cookies to all be removed by AngularJS when the browser is closed?
Here is the code for the main module of the app, including the run() method:
angular
.module('hello', [ 'ngRoute', 'auth', 'home', 'secure', 'public1', 'navigation' ])
.config(
function($routeProvider, $httpProvider, $locationProvider) {
$locationProvider.html5Mode(true);
$routeProvider
.when('/', {
templateUrl : 'js/home/home.html',
controller : 'home'
})//plus other routes
.otherwise('/');
$httpProvider.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
}
).run(['$cookies', '$window', '$log', function($cookies, auth, $window, $log) {
//other stuff
$window.onbeforeunload = function() {
// Clearing all cookies now!
$cookies.remove("AUTH1");
$cookies.remove("AUTH2");
$cookies.remove('procStep');
$cookies.remove('usrname');
$cookies.remove('exists');
$cookies.remove('wid');
};
}]);
If you need only cookies, because you need it to be sent to backend server, you can use the main controller's $destroy event.
Using $on.$destroy
This event called when the controller about to be destroyed.
Other option is to use angularjs sessionStorage
Session Storage removed when the window is closed automatically
I have an application set up with the Mean.js yeoman generator. It uses PassportJS to setup local-authentication. This all works great and I understand how to check if a user is logged in from the ExpressJS routes file.
The problem is that most of my page routing is done in the angular routes. I know how to check authentication in the controller with the the following code.
// Projects controller
angular.module('projects').controller('ProjectsController', ['$scope', '$stateParams', '$location', 'Authentication', 'Projects',
function($scope, $stateParams, $location, Authentication, Projects) {
$scope.authentication = Authentication;
But how do I check authentication in the routes. For example in this routes files how would I only allow authenticated users to access the tools html file and redirect users that arent logged in back to the home page:
'use strict';
//Setting up route
angular.module('analysis').config(['$stateProvider',
function($stateProvider) {
// Projects state routing
$stateProvider.
state('imageAnalysis', {
url: '/analysis',
templateUrl: 'modules/analysis/views/tools.client.view.html'
});
}
]);
I know there are similar posts out there but I had trouble understanding many of them. Thanks, I really appreciate any help. I am new to the stackoverflow community and still learning community standards.
At a high level, there are two approaches:
Use your view routing layer (e.g. UI Router) to catch “unauthenticated” state
Use HTTP interceptors to look for requests that have a 401 Unauthorized status, indicating that the user must login (or that their current session has expired)
In practice you’ll probably use a combination of both.
Speaking to the UI Router, there a two of doing this: resolves or events.
Resolves: The UI Router provides a nice feature called resolves, which allows you to provide a promise that must be resolved before the view is rendered. You could create a promise which resolves your user state.
Events: The UI Router provides a $stateChangeStart event, you can observe this event and prevent it if the user is not logged in. You would then send the user to login page. This is a bit more stateful (you have to remember where the user wanted to go in the first place, so that you can redirect after login).
I chose the event approach for my work on the Stormpath Angular SDK because it gives me the flexibility to define authorization on top of authentication.
You may be looking for HTTP Interceptors. Check auth on the requests.
From OneHungryMind:
HTTP interceptors are a great way to define behavior in a single place
for how a request or response is handled for ALL calls using the $http
service. This is a game changer when you want to set an auth token on
all outgoing calls or respond to a particular HTTP status error at the
system level. You can pair interceptors with the incredibly useful
Angular Storage module to cache an authenticated user and retrieve it
the next run time.
There are some good tutorials(1) out there(2)!
Can you run an Angular service (or a function on that service) before anything else? Ideally, as soon as ng-app gets parsed.
Here's my use case: I'm writing an app that gets AJAX data from a server and then parses the data a hundred different ways. I would like to make the initial AJAX call before all the controllers get called? That way I just have all the data parsed and loaded in the service without me worrying about updating any controllers or whatever.
I would like to make the initial AJAX call before all the controllers get called
In Angular method run is fired before any controller is called
var app = angular.module('myApp',[]);
app.run(function($rootScope){
// ajax call and other stuff
}
In run method you can do any job like login to Facebook, token validation and so on
Reference
Configuration blocks (aka app.config) - get executed during the provider registrations and configuration phase. Only providers and constants can be injected into configuration blocks. This is to prevent accidental instantiation of services before they have been fully configured.
Run blocks (aka app.run) - get executed after the injector is created and are used to kickstart the application. Only instances and constants can be injected into run blocks. This is to prevent further system configuration during application run time.
docs.angularjs.org/guide/module
plnkr = http://plnkr.co/edit/WTNuWKSgj0bMR1dtUkto?p=preview
The best way to configure how your services behave is to use providers. so, assuming you already have a mydata from your ajax call, the plnkr above shows a running example...
myapp.config(['sayHelloProvider',function(sayHelloProvider){
// assuming your ajax retrievies mydata
var mydata = angular.fromJson( angular.element(document.getElementById('mydata')).html() );
// configure service
sayHelloProvider.SetMessage("Olah! rate is=" + mydata.rate);
}]);
Situation
I scaffolded the application using a yeoman generator angularjs, thus providing me a clean directory structure and framework structure. I'm just learning angularjs and using yeoman is a cleaner way to do things, and soc is pretty much achieved.
Now I am having a problem on where to put things, like services and factories, I am currently dealing with user authentication right now.
Basically
I have two routes as of the moment, one is / and one is /secured. I set this up so that I can really get started playing with authentication.
I found this article about authenticating a user. And I found it really interesting, and I thought I could somehow understand it, and really I understood it, but I think I fail to understand some of the basics of how should factories and services should be used.
What I currently have
First, I do not have a backend service, because the app fails right on and writing a service won't be necessary right now, I can just write a simple php script that returns 401 status or 200, but that wont be necessary right now, I want to work on the client side first.
In my app.coffee file
angular.module('myApp', [
'ngCookies',
'ngResource',
'ngSanitize',
'ngRoute'
])
.config ($routeProvider) ->
$routeProvider
.when '/',
templateUrl: 'views/home.html'
controller: 'HomeCtrl'
.when '/secured',
templateUrl: 'views/secured.html'
controller: 'SecuredCtrl'
resolve:
loggedin: checkLoggedin // this is the wrong one
.otherwise
redirectTo: '/'
.config ($httpProvider) ->
$httpProvider.interceptors.push('httpInterceptor')
In the article, it says create a function that checks if the user is loggedin or not, and in his codebase he made that function inside the config method. And btw, he ain't using yeoman so that pretty much complicate things to me.
I thought that checking loggedin status is a factory or a service's job, so I thought of writing a factor for that like so
angular.module('myApp')
.factory 'checkLoggedin', ($q, $timeout, $http, $location, $rootScope) ->
deferred = $q.defer()
$http.get('api/loggedin').success (user) ->
if user isnt '0'
$timeout(deferred.resolve, 0)
else
$rootScope.message = 'You need to be logged in.'
$timeout(
() -> deferred.reject(),
0
)
$location.url '/login'
What is the proper way of doing this?
You can use the awesome angular-http-auth module - https://github.com/witoldsz/angular-http-auth
All you need to do is listen for the event 'auth-loginRequired' at the app controller level and then show the login screen.
What i have done (not the best way) is listen for this event in my app controller and redirect to login route -
$scope.$on('event:auth-loginRequired', function() {
$location.path('/login');
});
I am using AngularJs for a new application. I feel I have solved a problem but i'm not sure i have done it in the best way possible so would like to check before gunning ahead.
Let's for examples sake say i have the two controllers AccountsCtrl and ContactsCtrl, every time each is called a request to a REST server for all the accounts or contacts is made for each controller respectively. When within the controller any data changes are kept in sync in the angular models (and the server backend) to reflect this and hence the UI. Every time the user switches between each controller they have to make call to the server to fetch the data which it already had (which was up to date) last time it was loaded.
Currently this causes a very small lag. I would like to make it persistant i.e. not loaded each time the controller is loaded to save on the lag and the server requests. I have tried saving the data into the $rootScope and this works great but i'm not sure it's the right thing to do?
The question is how best solve this problem? Is the $rootScope the best way to tackle this?
I would store the data and code that interacts with the web server in one or two Angular services. Each time a controller is created (e.g., when you go back to the page a second time), the (appropriate) service should decide whether to return the cached data or make an Ajax request to your REST server.
Your service(s) would be injected into your controllers.
See also https://stackoverflow.com/a/12009408/215945 and https://groups.google.com/d/topic/angular/eegk_lB6kVs/discussion
First of all, you should do your REST call via a service:
app.factory('RestService', ['$http', function($http) {
return {
getSomething: function(url) {
var result = {};
$http.jsonp(url).success(function(data) {
result.list = data;
});
return result;
}
};
...
}];
You don't need $rootScope. Just define a parent controller which scopes both controllers, e.g. RootController, and store the data there. This is because child scopes (e.g. from AccountsCtrl) inherit what is defined in parent scopes.
<div ng-controller="RootController">
<div ng-controller="AccountsCtrl">
</div>
<div ng-controller="ContactsCtrl">
</div>
</div>
In practice this is almost the same as using $rootScope, but you don't need to inject it and you keep your controller logic similar as other controllers.