Configuring UI Router states based on configuration - angularjs

Is there a way to configure a UI Router states based on Server Side JSON file. I wan't to avoid hard coding the States inside my module.config(..).
I firstly thought of having controller which has the state map data available which can just call $stateProvider. However, I believe, controllers cannot have providers injected into them.
The other option I have thought was having a Javascript file outside of angular which puts the state configuration data in some global variable to be referenced from Module config function.
But is there a better approach?

I would say, that there are in general two ways how to use SERVER data (JSON) to build up states.
Firstly, we can use $http to load the JSON:
AngularJS - UI-router - How to configure dynamic views
The point here is, that we will in .config() state store reference to $stateProvider and use it in .run() phase, once the JSON could be loaded via $http
// ref to provider, to be configured later
var $stateProviderRef;
// config phase
app.run(['$stateProvider',
function ($stateProvider)
{
$stateProviderRef = $stateProvider
}
// run phase
app.run(['$q', '$rootScope', '$state', '$http',
function ($q, $rootScope, $state, $http)
{
$http.get("myJson.json")
.success(function(data)
{
angular.forEach(data, function (value, key)
{
var state = {
"url": value.url,
...
};
...
// here we still configure provider, but in RUN
$stateProviderRef.state(value.name, state);
});
But there are some disadvantages. The main is, that direct url navigation (copy - paste) won't be working. The URL will not be rosolved soon enough...
Secondly, my preferred way - create JSON as variable on a server, load it as a script.
So server will somehow generate response via /api/stateData like:
var stateData = [{ stateName : "State1",
...
}];
And we will inject that into page as a resource
<script src="/api/stateData" ...
This could be directly used in .config() phase, and will solve issue with URL being configured soon enough.

Related

AngularJS: How to initialise components after service finished asynchronous request

We're not using AngularJs as a SPA but embedded module to manage some behavior and shared data, so we're not actually utilising something like angular router. How should I initialize components only after a shared data service finished an asynchronous request? AngularJS was used with Typescript
Angular Module
import ... from '...'
import ... from '...'
...
angular.module('app-1', [])
.service('data-service', DataService)
.component('zeroDateButton', new ZeroDateButtonComponent())
.component('zeroPanel', new ZeroPanelComponent())
.component('zeroChart', new ZeroChartComponent())
ASP.NET Page hosting Angular module
BI.aspx
<asp:Content ID="standardContent" ContentPlaceHolderID="MainContent" runat="server">
...
<zero-date-button></zero-date-button>
<zero-date-button></zero-date-button>
<zero-panel name="panel-1"></zero-panel>
<zero-panel name="panel-2"></zero-panel>
<zero-panel name="panel-3"></zero-panel>
<zero-chart></zero-chart>
...
<script src="Scripts/Components/component.app-1.js) "></script> //compiled angular module js file
</asp:Content>
Page URL: https://www.example.com/BI/Zero
DataService.ts
public tryGetData() {
return $http.get(url).then((res: any) => {
this.panels = res.panels;
});
}
ZeroPanelComponent.ts
...
public $onInit(): void {
this.panels = this.dataService.panels;
this._render();
...
Most of the logics for this module relies on the data from the three components, so I want to fetch and store them all together in the data service, from which each component access the data they need from this service, and let the service figure out the logics and tell each of them by broadcasting the events.
Upon the components initialization(in $onInit method), it should display things using data retrieved from data service. The problem is component initialization is not awaiting data service to finish data fetching, so the component can't get the data they need and render nothing.
Trial with $routeProvider
I've seen seen lot's of people advising $routeProvider with appModule.config(), however it was not working. I'm not sure if this solution will work considering the way we use Angular, but I'm still posting the code snippet.
angular.module('app-1', ['ngRoute'])
.config(($routeProvider) => {
$routeProvider
.when('/BI/Zero', {
template: '<zero-panel class="flex-basis-half marginBottom" panel-name="SalesSnapshot", container-id="sales-snapshot"></zero-panel>',
resolve: {
DataService: (DataService) => {
return DataService.tryGetData();
},
},
});
})
.service('zero-data-service', DataService)
...
and I added ng-view directive to one in BI.aspx
There's NO error in browser, <zero-panel> is not rendered and tryGetDate() is not called too. I found someone said the 'path' defined to when() is part of the URL after the # symbol. Could you verify if this is true?
In terms other solution, the most intuitive thing I can think of is broadcasting an event when data service has obtained the data, and components listen to event to fetch the data, instead of fetching during their initialization.
I appreciate if anyone can suggest if $routeProvider would work in my usecase, or suggest any other possible solution to achieve the goal.

communicate between 2 different controllers

I have an angular app where we have 4 different pages.Each page has its own controller. There is an home page which has a controller which routes to each page and its controller using
when('/a',{
templateUrl: './components/a.html',
controller:'aCtrl'
}).
when('/b',{
templateUrl: './components/b.html',
controller:'bCtrl'
}).
when('/c',{
templateUrl: './components/c.html',
controller:'cCtrl'
}).
when('/d',{
templateUrl: './components/d.html',
controller:'dCtrl'
}).
when('/home',{
templateUrl: './components/Home.html',
controller:homeCtrl'
}).
Now I want to share some data or some common functions between these controllers/pages. How can we do this? I googled it they say to use SERVICE. But I don't know in which controller I need to write the service. Can anybody give a good example for this.
A service in AngularJS is not written within a controller. It is bound to your app directly and can be used anywhere within your application. This is why Services are the recommended means of communication between controllers in AngularJS.
What you need to do is write a service like so:
angular.module('yourApp').service('serviceName', function () {....});
Within the service, you can:
Fetch data from an API end point (You can use the $http provider for this)
Define constant data (You can use Angular's constant provider for this)
Define some code that takes in some data and manipulates it and returns new data
Pretty much anything else you want to do with your data
Now, include the service in your controller as a dependency like so:
angular.module('yourApp').controller('yourController', function (serviceName) {
console.log(serviceName.getData());
// Do something with your data
});
Now within this controller, you have access to the data that the service has returned. Of course, the same service can be injected into multiple controllers, thereby making it possible to share data across controllers.
There are many ways you can share data.
event
services
$rootScope
Services provide an easy way for us to share data and functionality throughout our app. The services we create are singletons that can be injected into controllers and other services, making them the ideal place for writing reusable code.
var app = angular.module('app', []);
app.controller('leftCtrl', function ($scope,userService) {
left.btnClicked = function (object) {
userService.saveData(object);
}
});
app.controller('rightCtrl', function ($scope, userService) {
$scope.getData = userService.getData();
});
app.service('userService', function () {
var data = {};
this.saveData = function(object){
data = object;
}
this.getData = function(){
return data;
}
});
Dustin has the right approach. However there are times when you could use a different approach and that is to wrap the application in an AppController.
Everything that is in AppController can now be accessed. You could use this approach to put functions or constants that you want the child controllers of the application to have access to and don't have to inject services everywhere.
<body ng-controller="AppController">
<div ng-view></div>
</body>

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

AngularJS application architecture

I am relatively new to Angular but I am quite an experienced developer. So far I have made quite some progress in building my application to work with a CMS. I am a bit lost however on what the 'correct' approach would be to handle data in my model.
This is best described with an example:
Because I am hooking up my angular frontend with a CMS, the routing (pages) exist only in the CMS context. This means that the routing should be dynamic as well. I have managed to get the dynamic routes thing to work, but when I try to do things the right way (actually getting data from a server) I run into some issues...
app.config(function($provide, $routeProvider) {
$provide.factory("$routeProvider", function() {
return $routeProvider;
});
});
// Load the dynamic routes from the API...
app.run(function($routeProvider, $http, $scope, logger, siteRoutes) {
$routeProvider.when('/', { templateUrl: '__views/', controller: 'ContentPageController' });
$routeProvider.otherwise({redirectTo: '/'});
});
In other words, I inject a service into my app.run method (siteRoutes) and this one should connect to the API.
So my siteRoutes is a service:
cmsModule.service('siteRoutes', function siteRouteFactory(apiConnection, logger)
// SNIP
And in this service I inject my generic apiConnection service:
cmsModule.factory('apiConnection', ['$q', '$http', '$timeout', 'logger', function apiConnectionService($q, $http, $timeout, logger)
What I want is this:
I would like the siteRoutes service to load the data once and not execute the connection every time. I did this in the following way:
bla.service('example', function() {
var service = {
get: function(apiStuff) { // DO API CONNECT WITH .THEN HERE },
data: {}
}
service.get();
return service;
}
I would like one entry point towards the Api that handles all the $q stuff (my factory) I assumed I need to handle all the .then() stuff in my siteRoutes object, which is what I did.
Now, what happens in my app.run method is that I don't get the siteRoutes object with any data. So I recon I need to do a .then there as well?
But that made me question the entire design of putting all logic in a separate factory for the connection, because I basically like my app to just use the data and have my library deal with the async stuff (if you get what I am saying)...
Hope this is clear.
TL;DR -> How to make your services / factories handle async stuff without making your 'app' deal with it?
The templateUrl property can also be a function that takes the url parametes as input.
In the example below all routes will load a template with same name.
Eg. domain.com/#/blabla.html will load the view blabla.html from the server.
myApp.config(['$routeProvider', function ($routeProvider) {
$routeProvider
.when('/:templateName',
{
templateUrl: function (params) {
return params.templateName + ".html";
}
}
)
.otherwise({ redirectTo: '/main' });
}]);

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