How to call $urlRouterProvider.otherwise() after loading JSON file? - angularjs

I am working on a project based on ionic and angular js. I am loading JSON file which contains some JSON data in key-value pair. What I want to achieve is I have to call $urlRouterProvider.otherwise() method after json file is loaded completely. Following is the code which I have tried but it does not work for me. I have tried putting console in 'defaultRoute' function it is getting executed but '$urlRouterProvider.otherwise('/tab/myjobs')' this line doesn't work.The following code is present in app.config function. Any help will be appreciated.
$.getJSON('js/constants/'+lang+'.json')
.then(function(response) {
$translateProvider.translations(window.localStorage['deviceLanguage'],response);
defaultRoute($urlRouterProvider);
}, function(response) {
//$translate.use('en');
});
function defaultRoute($urlRouterProvider){
if(window.localStorage['userData']) {
var access_token = JSON.parse(window.localStorage['userData']).access_token;
if(access_token){
$urlRouterProvider.otherwise('/tab/myjobs');
}else{
$urlRouterProvider.otherwise('/login');
}
}else{
console.log("in line 282");
$urlRouterProvider.otherwise('/login');
}
}

The problem is that you are running an async method in the config phase.
AngularJS lifecycle is splitted in 2 phases, config (where you can use providers, but not services because these are not yet registered), and run (where you cannot use providers, but you can use services and in general is equivalent to the main function).
The run phase starts when config phase has finished, and config phase do not wait for any async process, so what is happening is that when your JSON get promise is solved your config phase has already finished (so any provider config you try to do in your promise success callback do not really config anything).
So in short, you cannot use $urlRouterProvider.otherwise() passing the result of an async call, like your getJson method.
A couple alternatives to what you are trying to do (redirect user depending on auth) are:
angular ui-router login authentication and angularjs redirect to login page if not authenticated with exceptions.

Consider using $state.go('someState')
You would want your code to look like this:
if(access_token){
$state.go('myJobs'); // where myJobs is the state correlated with your url 'tabs/jobs'
}else{
$state.go('login'); // or whatever the state name is that you have for 'login'
}
}else{
console.log("in line 282");
$state.go('login');
}

If you are trying to send the user to different routes conditionally, use $state.go('route.path') instead of updating the .otherwise() configuration. For example:
var app = angular.module('myapp',['ionic']);
app.controller('$scope', '$state','$http', [function($scope, $state, $http){
$http.get('my/api/path')
.then(function(response){
if(response.authenticated){
$state.go('secret.route');
} else {
$state.go('public.route');
}
});
}]);

Related

Waiting until $resource finished to load in my controller before continuing

I'm building a rest api with fosrestbundle and I manage the frontend with angular and Twig template. One on my url address looks like this :
http://mywebsite/myroute/idContain
When I load the url in my browser, in a twig template (kind of html), I retrieve the parameter "idContain" (comming from a fosrestbundle controller) with ng-init of angularjs like this :
<div class="container-fluid" ng-init="getContainByID({{ idContain }})">
//...lot html div with angularjs directives
</div>
And immediately, ng-init will go to my angularJS app finds getContainByID(idContain) to run it.
This one looks like this :
angular.module("myApp", ["ngSanitize", 'angular.filter', 'ui.tinymce', ...])
.config(function($interpolateProvider, ...) {
$interpolateProvider.startSymbol('{[{').endSymbol('}]}');
})
.controller("myCtrl",function ($filter,..., myService)
{
// lot of code...
$scope.getContainByID = function(idContain)
{
$scope.currentContain = myService.getContains(idContain);
$scope.containRoot = $scope.currentContain.contain.containRoot;
...
}
// lot of code...
}
The fact is that, myService.getContains(idContain) come from my rest service looking like this :
angular.module("MyServiceRest", ['ngResource'])
.factory("myService", function ($rootScope, $resource) {
var apiData = $resource(
"/api", {},
{
...
"getContains": {method: 'GET', isArray: false, url: "mywebsite/api/myroute/:containid"}
...
});
return {
getContains: function (idContain) {
return apiData.getContains({containid: idContain});
}
}
});
Now the problem is, when I run my angularjs App, $scope.containRoot doesn't wait until myService.getContains(idContain) (coming from my asynchroeous $resource service) finished to load, and caused errors making my webapp crash.
How can I do, to force $scope.containRoot and the rest of my angular code to waiting until myService.getContains(idContain) (connected to the api resource) completly finished to load before continuing ?
$resource makes an asynchronous request but immediately reurns an empty object or array.
There are numerous ways you could handle your issue.
One would be not to worry about declaring $scope.containRoot and just using currentContain.contain.containRoot in the view. This property will get rendered after the request is received
Another is to use the $promise that is also returned by $resource and assign the scope property in promise callback
$scope.currentContain = myService.getContains(idContain);
$scope.currentContain.$promise.then(function(){
$scope.containRoot = $scope.currentContain.contain.containRoot;
});
Another is to use a routing resolve based on the same promise so the route( or state depending on router) is noot entered until the request is complete

Pass authentication information from MVC into angular

My project uses MVC to deliver the initial markup of my site
The MVC controller is super simple:
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
}
I have my ng-app tag, bundling, and #RenderBody in a layout view:
<!DOCTYPE html>
<html ng-app="myAppName">
<head>
#Styles.Render("~/Content/css")
</head>
<body>
<div class="container body-content">
#RenderBody()
</div>
#Scripts.Render("~/bundles/aBundle")
</body>
</html>
And my Index view is stripped down as simple as possible:
<ng-view></ng-view>
My angular app.ts file looks like this:
module app {
var main = angular.module("myAppName", ["ngRoute", "breeze.angular"]);
main.config(routeConfig);
routeConfig.$inject = ["$routeProvider"];
function routeConfig($routeProvider: ng.route.IRouteProvider): void {
$routeProvider
.when("/home",
{
templateUrl: "app/views/homeView.html",
controller: "HomeController as vm"
})
.when("/itemDetail/:itemId",
{
templateUrl: "app/views/itemDetailView.html",
controller: "ItemDetailController as vm"
})
.when("/addItem",
{
templateUrl: "app/views/addItemView.html",
controller: "AddItemController as vm"
})
.when("/login",
{
templateUrl: "app/views/loginView.html",
controller: "LoginController as vm"
})
.otherwise("/home");
}
}
I can inspect the Request sent by the user in the MVC controller, or in the Razor view using #Request.IsAuthenticated to see if the user is logged in, but what is the best way to pass this information to my angular app so that I can properly route the user to a login page when they first sign on, but skip the login page if they have an active session on the server?
The research I have done to try and figure this out has suggested to me that I probably need to create an angular service to store a boolean value regarding whether the user is authenticated or not. Then, I need to add some code to check this service for every route using $routeChangeStart, and redirecting to the login page only when necessary. I have looked at many examples, but can't quite put the pieces together in the context of my own application.
Could someone help me connect the dots, please?
I'm developing a project that I'm not using razor. Only html and angular in the views. So... What I did is:
I created a directive "appWrapper" that holds the layout. Inside of it there is a section that has the ng-view.
This directive uses a controller, the AuthCtrl, and this controller uses a service, the AuthService.
Because of this, I have access to everything that's inside this controller in the entire html. So I could say ng-click="vm.logout()" in an item in the sidebar, for example.
This "AuthService" has some methods that can be called by the controller. Login and logout being 2 of them.
When I execute login, I set some cookies with some information I get back from my controller.
When I logout, I remove these cookies.
On the app.js, where my routes are, I have I .run at the end that checks these cookies every time the location (url) is going to be changed. If they exist, it lets the user continue. If not, it redirects the user to the login.
Did it help? If needed, I can post the code for you to use as example.
No need to create an action inside a controller for checking this. It would require your application to go to the server, back to the browser, back to the server and back to the browser for every action the user takes. This is not good.
I can share what I do in my own apps, and although I am using ui-router, the same technique can easily be applied to the built in router.
Basic logical workflow:
Automatically add a resolve to every route that fetches the user's current auth status and caches it.
Check if route requires authentication, and reject if user is not logged in, otherwise allow to proceed normally.
On $routeChangeError (or something similar) check to see what action to perform.
The advantage of this is it allows you to have a great deal of flexibility with regard to providing a nice client side experience with security.
Adding Credentials to Every Route:
let originalStateFunction = $stateProvider.state;
$stateProvider.state = function (state, config) {
//
// The "allowAnonymous" is something we added manually
// This will become important later because we might have
// routes that don't require authentication, like say
// the login page
//
if (angular.isDefined(config) && !config.allowAnonymous) {
_.defaults(config.resolve || (config.resolve = {}), {
/*#ngInject*/
userSession: function userSessionSecurityCheck($q, sessionService) {
let def = $q.defer();
sessionService.getSession()
.then(session => {
if(!session.isAuthenticated){
def.reject({
error:'AUTHENTICATION_REQUIRED'
});
}
});
return def.promise;
//You could also do more complex handling here...
// like check permissions for specific routes
// and reject the promise if they fail.
}
});
}
//Now call the original state/when method with our
// newly augmented config object
return originalStateFunction.apply(this, arguments);
};
Inspecting The Route Error
.run($rootScope => {
"ngInject";
$rootScope.$on('$stateChangeError', (event, toState, toParams, fromState, fromParams, error) => {
event.preventDefault();
if(error.error === "AUTHENTICATION_REQUIRED"){
//Now you can redirect the user appropriately
}
});
});
You can create a simple endpoint to return the current user's status by inspecting the Principal, and so long as you are smart about caching that on the client, you will only incur the hit once per user.
The full sample code is too large for SO, but hopefully this gives you a good starting point.
I am thinking you would do it along the lines of below:
MVC controller:
[Authorize] // Make sure we're authorising the whole controller.
public class ProfileController : Controller
{
public ActionResult IsLoggedIn()
{
return Json(this.Request.IsAuthenticated);
// you may need allow get as the second parameter
}
}
Angular:
app.factory('ProfileService', function ($http) {
var service = {
isLoggedIn: isLoggedIn
};
return service;
function isLoggedIn() {
return $http.get('Profile/IsLoggedIn').then(function (response) {
return response.data; // this depends on what the response object looks like
});
}
});
app.run(function (ProfileService, $location) {
ProfileService.isLoggedIn().then(function (isLoggedIn) {
if (!isLoggedIn) {
$location.path('/unauthorised'); // just redirect them to the unauthorised route.
}
});
});
This means that everytime your app runs it will check to see whether you are logged in. You can also use this profile service to go grab other information about the user! And you can pull this service into any other module you wish to perform this kind stuff.
Remember that it is pointless trying to secure the javascript because it is run in a sandbox. But always make sure that you use [Authorize] attributes in your MVC code so that the server is always enforcing Authorisation and Authentication.
Before anyone says this isn't Typescript, any Javascript is also valid Typescript. I leave it to the user to put in the type defs.
-- More Info --
You can add a xmin cache expiry on the is logged in or store these details into local storage if you are constantly requesting the information:
app.factory('ProfileService', function ($http, $q) {
var service = {
isLoggedIn: isLoggedIn
};
var cacheExpiries = {
loggedIn: { value: null, expiry: null }
};
return service;
function isLoggedIn() {
var cacheObj = cacheExpiries['loggedIn'];
var useCache = false;
if (cacheObj.expiry) {
useCache = new Date() < cacheObj.expiry;
}
if (useCache) {
// because http returns a promise we need to
// short circuit function with a promise
return $q(function (res, rej) {
res(cacheObj.value);
});
}
// set the new expiry for the cache, this just adds 5 minutes to now
cacheObj.expiry = new Date(new Date().setMinutes(new Date().getMinutes() + 5));
return $http.get('Profile/IsLoggedIn').then(function (response) {
cacheObj.value = response.data;
return response.data; // this depends on what the response object looks like
});
}
});
You could easily encapsulate the cache into a factory with a proper key value storage api. This would extract some of the logic out of your thin service ( to keep it thin )
Local Storage and Cookie Storage
I have used this module for years to do local storage access, this can be slotted in place of the cache or you could still have your cache service to wrap this storage solution so that you can completely stay decoupled from your dependencies.

How to redirect using ajax in angularjs

I want to redirect a user on specific page using ajax call in angularjs. I am able to redirect using below mentioned code but when i again want to redirect user to root page i am unable to do so as the value of $window.location.href+ 'getTechnicianWorkOrder/'+woId is persisting as is:
$scope.getTechnicianWorkOrderFormURL = function(woId){
return $window.location.href + 'getTechnicianWorkOrder/'+woId;
};
Another place where i want to redirect to root page:
$scope.getAssignedListURL = function(){
return $window.location.href;
};
Note: i want to make this redirection work even in offline mode of HTML5 cache-manifest.
Using the $location service.
$location.path('/');
The answer by #prashant-palikhe is the right one, $location.path('/'); is the route to your root path. just use the dependency of $location to your controller like this:
yourapp.controller('YourController', ['$location', function($location) {
...
}
I always use the ui.router for my routes and in there you can add $urlRouterProvider.otherwise('/') for fallback in any unknown state or route.
In some cases you can add something like this in your states:
resolve : {
dataObj : ['$http', function($http) {
return $http({method : 'GET', url : '/your/ajax/endpoint'})
;}],
},
onEnter : ['dataObj', '$state', function(dataObj, $state) {
// dataObj is your ajax response object. Based on this you can redirect to a certain state of needed
$state.go('default');
}]
resolve is data that preloads data onEnter is called before entering the state. This can be used as some sort of middleware.

Need to resolve a $http request before the execution of the resolve property inside $stateProvider

I’m building an angular application that is going to run on several domains. Since there are different configurations on each domain I'll need to fetch all the variables by doing a call to the server. The call will return a JSON object that contains different rest urls.
My problem is that I need to do this call before the 'resolve' step inside the $stateProvider, since I already have a task that is dependent on the configuration object from the server.
What should work here is a really great feature $urlRouterProvider.deferIntercept(); documented here:
$urlRouterProvider
The deferIntercept(defer)
Disables (or enables) deferring location change interception.
If you wish to customize the behavior of syncing the URL (for example, if you wish to defer a transition but maintain the current URL), call this method at configuration time. Then, at run time, call $urlRouter.listen() after you have configured your own $locationChangeSuccess event handler.
The code snippet from the API documentation:
var app = angular.module('app', ['ui.router.router']);
app.config(function($urlRouterProvider) {
// Prevent $urlRouter from automatically intercepting URL changes;
// this allows you to configure custom behavior in between
// location changes and route synchronization:
$urlRouterProvider.deferIntercept();
}).run(function($rootScope, $urlRouter, UserService) {
$rootScope.$on('$locationChangeSuccess', function(e) {
// UserService is an example service for managing user state
if (UserService.isLoggedIn()) return;
// Prevent $urlRouter's default handler from firing
e.preventDefault();
UserService.handleLogin().then(function() {
// Once the user has logged in, sync the current URL
// to the router:
$urlRouter.sync();
});
});
// Configures $urlRouter's listener *after* your custom listener
$urlRouter.listen();
});
And also, related to this question:
AngularJS - UI-router - How to configure dynamic views
There is working example - plunker
To make it clear, suitable for this use case, let's observe the code of the plunker.
So, firstly we can see the .config() phase. It does have access to providers but NOT to their services (e.g. $http). Not yet, services themselves will be available later...
app.config(function ($locationProvider, $urlRouterProvider, $stateProvider)
{
// this will put UI-Router into hibernation
// waiting for explicit resurrection later
// it will give us time to do anything we want... even in .run() phase
$urlRouterProvider.deferIntercept();
$urlRouterProvider.otherwise('/other');
$locationProvider.html5Mode({enabled: false});
$stateProviderRef = $stateProvider;
});
What we did, is set a reference to provider (configurable object), to be used later: $stateProviderRef.
And the most crucial thing is we STOPPED the UI-Router, and forced him to wait for us with $urlRouterProvider.deferIntercept(); (see the doc and cites above)
There is an extract of the .run() phase:
app.run(['$q', '$rootScope','$http', '$urlRouter',
function ($q, $rootScope, $http, $urlRouter)
{
// RUN phase can use services (conigured in config phase)
// e.g. $http to load some data
$http
.get("myJson.json")
.success(function(data)
{
// here we can use the loaded stuff to enhance our states
angular.forEach(data, function (value, key)
{
var state = { ... }
...
$stateProviderRef.state(value.name, state);
});
// Configures $urlRouter's listener *after* your custom listener
// here comes resurrection of the UI-Router
// these two important calls, will return the execution to the
// routing provider
// and let the application to use just loaded stuff
$urlRouter.sync();
$urlRouter.listen();
});
}]);
Most important is, that this .run() was executed just ONCE. Only once. As we require.
We can also use another technique: resolve inside of one super root state, which is parent of all state hierarchy roots. Check all the details here:
Nested states or views for layout with leftbar in ui-router?
There is another way how to solve the:
How to resolve $http request before the execution of the resolve property inside $stateProvider?
In case, that we just need to get some $http result inside of the resolve, we can do it just like this:
resolve: {
myResolve1:
function($http, $stateParams) {
return $http.get("/api/foos/"+stateParams.fooID);
}
}
This is a snippet from documenation of the [$stateProvider][1], section resolve. We can see, that we return the promise of the $http service: return $http.get()
So, to extend that, to asnwer:
But how can I make the ui-router wait until the promise is resolved?
we can just use return $http.get() and then .then(). And inside of it, we have access to returned result - which we can adjust:
myResolve1:
function($http, $stateParams) {
// we still return the promise of the $http.get))
return $http
.get("/api/foos/"+stateParams.fooID)
.then(function(response) {
// but now, the content of resolved property
// will be the first item of the loaded array
return response.data[0];
};
}
}
There is also enahnced solution - in case we need to make this to happen before every state. We just introduce some "root" state as a super parent. It will contain such resolve and all child states will wait until this one is resolved, but just resolved just once. See more here: angular ui-router resolve for parent state

How to retrieve constants for AngularJS from backend

i have some application settings that i want to retrieve from backend, so that they would be available to all of my application controllers via injection. What is the most angular-way to do that?
1) If i only needed settings for one or few controllers i could retrieve them via routing resolve method, but what about global application scope?
2) I could use the .run() method, but since call will be async i have no guaranties that my config will be loaded before i access controllers.
Currently my settings are returned as a json object, and my templates/html files are simply served by web server. So i cannot embed anything into script tags, parse html on the server side or any similar technique.
I would create a service for your settings. Then using the .run() method, called a service that returns your app settings data:
angular
.module('YourApp',[])
.service('Settings',function(){
this.data = {}
})
.run(function(Settings,$http){
$http
.get('/ajax/app/settings')
.success(function(data){
Settings.data = data
})
})
function Controller($scope,Settings){
// use your Settings.data
}
http://docs.angularjs.org/api/angular.Module#methods_run
There is a neat plugin to do the job. https://github.com/philippd/angular-deferred-bootstrap
You need to change bootstrap method
deferredBootstrapper.bootstrap({
element: document.body,
module: 'MyApp',
resolve: {
APP_CONFIG: function ($http) {
return $http.get('/api/demo-config');
}
}
});
angular.module('MyApp', [])
.config(function (APP_CONFIG) {
console.log(APP_CONFIG);
});

Resources