Setting AngularJS app configurations after a service has been called - angularjs

I am using $urlRouterProvider service in my app.config to define the default routes in my AngularJS application. I define it as follows:
app.config(function ($stateProvider, $urlRouterProvider) {
$urlRouterProvider
.when('/', '/history')
.otherwise('/history');
$stateProvider
.state('tabs', {
abstract: true,
url: '/',
template: '<my-tab></my-tab>',
onEnter: function(){console.log("enter");}
})
});
My problem is that I want to set these routes after a Boolean variable flag is fetched from database using a custom service (databaseService). If the flag is true, it should be:
$urlRouterProvider
.when('/', '/history')
.otherwise('/history');
and if flag is false, it should be:
$urlRouterProvider
.when('/', '/future')
.otherwise('/future');
How can I achieve this? I am not able to use a custom service in app.config function. Also I can't set configurations in my service because config gets set before a service is loaded. Please help!

Assuming you're going to change this once at the start of the application you could to the following (docs):
var routerProvider = null; // reference router for run phase
angular
.module('app')
.config(config)
.factory('databaseService', databaseService)
.run(run);
config.$inject = ['$urlRouterProvider'];
run.$inject = ['$urlRouter', 'databaseService'];
function config($urlRouterProvider) {
$urlRouterProvider.deferIntercept();
routerProvider = $urlRouterProvider;
}
function databaseService() {
return {
getDbFlag: function() {
// connect to db and fetch data
}
};
}
function run($urlRouter, databaseService) {
databaseService.getDbFlag()
.then(function (serviceData) {
// set the route with data from the DB
routerProvider.otherwise(serviceData.route);
$urlRouter.sync();
$urlRouter.listen();
});
}

Related

StateProvider somewhere else than app.js in AngularJS

I'd like to know if I can put a stateprovider somewhere else than my app.js, because it causes me some problems in other pages.
app.js
var app = angular.module('myApp', 'ui.router']);
app.config(function ($stateProvider, $urlRouterProvider) {
$stateProvider
// route to show our basic form (/form)
.state('form', {
url: '/form',
templateUrl: 'templates/step_Form/form.html?v=' + new Date().getDay()
})
// url will be nested (/form/profile)
.state('form.upload', {
url: '/upload',
templateUrl: 'templates/step_Form/form-upload.html?v=' + new Date().getDay()
})
// url will be /form/interests
.state('form.content', {
url: '/content',
templateUrl: 'templates/step_Form/form-content.html?v=' + new Date().getDay()
})
$urlRouterProvider.otherwise('/form/upload');
})
My controller
angular.module('myApp').controller('ImportGeneralCtrl', function ($scope, $stateProvider, $urlRouterProvider) {
//myfunctions
}
I integrated a multi-step form in my HTML, using the state provider.
How could i get out the state provider from app.js to only apply it on my controller?
You cant use $stateProvider or $urlRouterProvider (providers) in a controller because those providers are just made for configuration injection. It can be uses in any angular.module('myApp').config() you want but you can't user providers in a controller. In controllers you could only inject $state (modules, services, factories, e.g.):
angular.module('myApp').controller('myController', function ($scope, $state) {}
This little code snippet shows you how to create a Service, a Factory and a Provider.

Angularjs custom headers with interceptors

I am trying to add a custom header using interceptors to every request I make on the app and I get the following error
Uncaught Error: [$injector:unpr] Unknown provider:
httpRequestInterceptorProvider <- httpRequestInterceptor <- $http <-
$templateFactory <- $view <- $state
// Ionic Starter App
(function () {
'use strict';
var app = angular
.module('app', ['ionic', 'auth0.lock', 'angular-jwt'])
.config(config)
.factory(factory)
factory.$inject = ['httpRequestInterceptor'];
config.$inject = ['$stateProvider', '$urlRouterProvider', 'lockProvider', 'jwtOptionsProvider', '$httpProvider'];
function factory(httpRequestInterceptor) {
return {
request: function (config) {
config.headers['X-switch-using'] = isApple;
return config;
}
}
}
function config($stateProvider, $urlRouterProvider, lockProvider, jwtOptionsProvider, $httpProvider) {
$stateProvider
// setup an abstract state for the tabs directive
.state('app', {
url: '/app',
abstract: true,
templateUrl: 'components/menu/menu.html',
})
.state('app.home', {
url: '/home',
views: {
'menuContent': {
templateUrl: 'components/home/home.html'
}
}
})
.state('app.dashboard', {
url: '/dashboard',
views: {
'menuContent': {
templateUrl: 'components/template/template.html'
}
}
})
.state('app.signin', {
url: '/login',
views: {
'menuContent': {
templateUrl: 'components/login/login.html'
}
}
});
// if none of the above states are matched, use this as the fallback
$urlRouterProvider.otherwise('/app/home');
$httpProvider.interceptors.push('httpRequestInterceptor');
lockProvider.init({
clientID: AUTH0_CLIENT_ID,
domain: AUTH0_DOMAIN,
options: {
auth: {
redirect: false,
params: {
scope: 'openid',
device: 'Mobile device'
}
}
}
});
// Configuration for angular-jwt
jwtOptionsProvider.config({
tokenGetter: function () {
return localStorage.getItem('id_token');
},
whiteListedDomains: ['localhost'],
unauthenticatedRedirectPath: '/login'
});
}
})();
When I try to $httpProvider.interceptors.push('httpRequestInterceptor');
Any ideas? Cheers!
The problem is you are inject interceptor here factory.$inject = ['httpRequestInterceptor']; but what exactly the httpRequestInterceptor is ? you have not create anything with that name.
What you need to do is change below functions name to httpRequestInterceptor from factory:
function factory(httpRequestInterceptor)
and make it function httpRequestInterceptor()
Then replace .factory(factory) with .factory(httpRequestInterceptor) and you can remove factory.$inject if you dont need to inject anything else.
Problem 1
The first problem is that there is no dependency in your app like httpRequestInterceptor.
Problem 2
The 2nd major problem is that you can not inject a simple factory or service in the config phase of Angular.
From the docs
Only providers and constants can be injected into configuration
blocks. This is to prevent accidental instantiation of services before
they have been fully configured.
So consider changing your code like this:
// Ionic Starter App
(function () {
'use strict';
var app = angular
.module('app', ['ionic', 'auth0.lock', 'angular-jwt'])
.config(config)
//.factory(factory) // Removed factory
config.$inject = ['$stateProvider', '$urlRouterProvider', 'lockProvider', 'jwtOptionsProvider', '$httpProvider'];
function factory() {
return {
request: function (config) {
config.headers['X-switch-using'] = isApple;
return config;
}
}
}
function config($stateProvider, $urlRouterProvider, lockProvider, jwtOptionsProvider, $httpProvider) {
/** your state configuration here **/
$httpProvider.interceptors.push(factory);
/** your lockprovider and jwtOptionsProvider here **/
}
})();
I don't see a factory with httpRequestInterceptor which you are injecting in your factory named factory. If at all the factory httpRequestInterceptor is another module, you have inject that module as a dependency in your app module.
var app = angular
.module('app', ['ionic', 'auth0.lock', 'angular-jwt','inject
the module which has the httpRequestInterceptor factory'])
.config(config)
.factory(factory)

Angular ui-router shows old stale data after transition back to state

We have an ASP.NET MVC 5 application which uses ui-router AngularJS module.
When we go back to a state that has already been loaded, it always shows the old data until we do a full page refresh.
For example:
User clicks 'View Profile', we show the "ViewProfile" state, a page displaying the profile
User clicks "Edit Profile", we show the "EditProfile" state, a page with fields to edit the profile
User makes changes and clicks 'Save', they are then moved back to the "ViewProfile" state
When "ViewProfile" state loads, it still shows the old data before the edits
How can we force ui-route to pull fresh data any time it loads any state?
Angular config
var app = angular.module("MyApp", ['ngIdle', 'ui.router'])
.config(['$stateProvider', '$urlRouterProvider', '$locationProvider',
function ($stateProvider, $urlRouterProvider, $locationProvider) {
// Configure client-side routing
$locationProvider.hashPrefix("!").html5Mode(true);
$urlRouterProvider.otherwise("/");
$stateProvider
.state('Home', {
url: '/',
views: {
"mainContainer": {
templateUrl: function (params) { return 'Home/Index'; }
}
}
})
.state('ProfileManagement', {
url: '/ProfileManagement-{action}',
views: {
"mainContainer": {
templateUrl: function (params) { return 'ProfileManagement/' + params.action; }
}
}
})
}]);
How we are doing the transition
$state.go(stateName, { action: actionName }, { reload: true, inherit: false, notify: true });
EDIT w/ Solution
Since all the functionality is written using jQuery, we cannot use Angular controllers to control the data. Our solution was to completely disable Angular template caching with the following code:
app.run(function ($rootScope, $templateCache) {
$rootScope.$on('$viewContentLoaded', function () {
$templateCache.removeAll();
});
});
By default ui-router state transition will not re-instantiate toState's controller.
To force instantiation of the controller, you can do this.
//assuming you are using $state.go to go back to previous state
$state.go('to.state', params, {
reload: true
});
You need to use resolves, like so:
var app = angular.module("MyApp", ['ngIdle', 'ui.router'])
.config(['$stateProvider', '$urlRouterProvider', '$locationProvider',
function ($stateProvider, $urlRouterProvider, $locationProvider) {
// Configure client-side routing
$locationProvider.hashPrefix("!").html5Mode(true);
$urlRouterProvider.otherwise("/");
$stateProvider
.state('Home', {
url: '/',
views: {
"mainContainer": {
templateUrl: function (params) { return 'Home/Index'; }
}
},
resolve: {
loadedData: (injectable) => injectable.getData()
}
})
.state('ProfileManagement', {
url: '/ProfileManagement-{action}',
views: {
"mainContainer": {
templateUrl: function (params) { return 'ProfileManagement/' + params.action; }
}
},
resolve: {
loadedData: (injectable) => injectable.getData()
}
})
}]);
In each state, there is a resolve, calling the method 'getData()' in the injectable service (I'm saying a 'service' because that's where we would have the data loaded from).
Then for each state's controller, it will have access to the loadedData resolve; the data will already be loaded at that point and be usable by that controller. So for your case scenario, going back to the state you've come from, the newly updated data will be loaded again and available in that state again.
Keep your state transition the same, with the reload: true, as that is what you want as well.
Maybe this work for your problem.
In your angular main page you should have something like this:
app.run(['$rootScope', function ($rootScope){
$rootScope.$on('$stateChangeStart', function(event, toState){
$rootScope.UrlActive=toState.name;
});
}]);
In your angular controller you should have something like this:
app.controller('AnalisisCtrl', ['$scope','$rootScope', function ($scope,$rootScope) {
$scope.$watch('UrlActive', function(newValue) {
if(newValue="ProfileManagement"){
doSomething();
};
});
}]);

$http request in angular config - Is it possible?

I am trying to make an $http call inside my publicApp.config in order to retrieve an array of available routes to match with the $urlMatcherFactoryProvider.
Right now, I am hard-coding them to be pageUrls = ['about','contact','another-page'];
But I have an url in my express API which returns an array of available URLS. The api URL is "/page-urls"
Would it be possible to make an $http.get('/page-urls') request inside the config? I know $http is available inside run(), but I need the list of available URLs BEFORE routing via the $stateProvider.
(function() {
'use strict'
var pageUrls = [];
var publicApp = angular.module('publicApp', ['ui.router'])
publicApp.config(['$stateProvider', '$urlRouterProvider', '$urlMatcherFactoryProvider', function($stateProvider, $urlRouterProvider, $urlMatcherFactoryProvider) {
pageUrls = ['about','contact','another-page'];
var urls = pageUrls.join('|');
var urlMatcher = $urlMatcherFactoryProvider.compile("/{source:(?:" + urls + ")}");
$stateProvider
.state('/', {
url: '/',
templateUrl: "views/home/home.view.html",
controller: "homeCtrl"
})
.state('urls', {
url: urlMatcher,
templateUrl: "views/pages/page.view.html",
controller: "pageCtrl"
});
$urlRouterProvider.otherwise('/');
}]);
})();
Create a provider which gets $stateProvider as an injectable. The provider will create a service that does the http request then registers the routes. Inject the service in a run block and initiate route registration.
Something like this:
var publicApp = angular.module('publicApp', ['ui.router'])
publicApp.provider('routes', function($stateProvider, $urlRouterProvider, $urlMatcherFactoryProvider){
function registerRoutes(listOfUrls){
// register routes with $stateProvider
// angular.forEach(listOfUrls, function(url){
// $stateProvider.state...
// });
}
this.$get = function($http){
return {
initialize: function(){
return $http.get('/page-urls').then(function(response){
registerRoutes(response.data);
});
}
};
};
});
publicApp.run(function(routes){
routes.initialize();
});

How can I add ui-router stateProvider configuration to my application with Typescript?

Before when not using Typescript I was adding my ui-router state information like this:
app.config([
'$httpProvider',
'$locationProvider',
'$sceProvider',
'$stateProvider',
appConfig
]);
function appConfig(
$httpProvider,
$locationProvider,
$sceProvider,
$stateProvider
) {
$locationProvider.html5Mode(true);
$sceProvider.enabled(false);
var admin = {
name: 'admin',
url: '/admin',
views: {
...
}
};
Now I am using Typescript I assume I should code this into a class. But how can I add the class to my app? I already have this code where I added a controller:
var app = angular.module('app',
[
])
.controller('appController', AppController);
I tried to add the state information as a .config but it appears this only accepts a function as the argument.
Can someone tell me how I can add my state information for ui-router to my app?
This is the way I am defining the ui-router states .. in typescript (as other parts are in TS...):
module MyModule
{
export class MyConfig
{
constructor(private $stateProvider: ng.ui.IStateProvider
, private $urlRouterProvider: ng.ui.IUrlRouterProvider
... // other dependencies)
{
this.init();
}
private init(): void
{
this.$stateProvider.state("App", <ng.ui.IState>
{
abstract: true,
.... // more settings
});
// more states
}
}
}
angular.module('MyModule')
.config(
["$stateProvider", "$urlRouterProvider", // more dependencies
($stateProvider, $urlRouterProvider) =>
{
return new MyModule.MyConfig($stateProvider, $urlRouterProvider);
}
]);
I agree there is no advantage by using a class. However you can use TypeScript for the config and have type definitions (intellisense + compile time checking), no need to use javascript directly either.
I have the app configuration and the ui-router configuration in the same file app.config.ts
((): void => {
'use scripts';
angular
.module('app')
.config(config);
config.$inject = [
'$locationProvider',
'$stateProvider',
'$urlRouterProvider'
];
function config($locationProvider: ng.ILocationProvider,
$stateProvider: angular.ui.IStateProvider,
$urlRouterProvider: angular.ui.IUrlRouterProvider) {
//html5 removes the need for # in URL
$locationProvider.html5Mode({
enabled: true,
requireBase: false
});
$urlRouterProvider.otherwise('/');
//angular-ui-router for multiple views
$stateProvider
.state('index', <ng.ui.IState>{
url: "/",
views: {
"viewA": {
templateUrl: "app/home/homenav.html"
},
"viewB": {
templateUrl: "app/home/home.html"
}
}
});
//more states here.
}
})();
You wouldn't gain an advantage from using a class here because you don't need to use this or add properties to it in the config function. Just use a function like its JavaScript (TypeScript won't complain).

Resources