Is it possible to use resolves outside of states? - angularjs

I have site navigation that doesn't exist in any particular state. It's always available along the top of the page, regardless of which state the application is in.
I need to hide/show certain menu options depending on who the user is. I'm using windows authentication so a trip to the server is a necessity. The problem is since the nav bar doesn't belong to any particular state I don't know where to put the resolve.
Is there something like a global state which would be resolved first before any other states where I could put the resolve?
Something like:
.state('$global', {
url: '/',
templateUrl: 'partials/navigation/navbar.html',
controller: 'NavCtrl',
resolve: {
navData: ['$http', 'SettingsFactory', 'ViewMatrixService', function($http, SettingsFactory, ViewMatrixService) {
return $http.get(SettingsFactory.APIUrl + 'api/nav', { withCredentials: true }).then(function (response) {
ViewMatrixService.GenerateHomeViewMatrix(response.data.CurrentUser);
return response.data;
});
}]
}
})
I considered using $broadcast but then I'd need to make sure every possible point of entry to the application gets the information the nav bar needs from the server and broadcasts it which contaminates all my other controllers with nav bar responsibilities.

I was unable to find a way to use resolves outside of states, but I did find a solution.
I created an abstract parent state called app. I have a resolve in app that gets the user's profile. Then it can be injected into any child state controllers.
Since the nav bar is stateless, in the same resolve I pass the currentUser object to a service which is used across the app to store visibility and disabled flags for all controls.
It looks like this:
$stateProvider
.state('app', {
abstract: true,
template: '<ui-view/>',
resolve: {
currentUser: ['$http', 'SettingsFactory', 'ViewMatrixService', function($http, SettingsFactory, ViewMatrixService) {
return $http.get(SettingsFactory.APIUrl + 'api/users/current', { withCredentials: true }).then(function (response) {
ViewMatrixService.GenerateHomeViewMatrix(response.data);
return response.data;
});
}
]}
})
.state('app.home', {
url: '/',
templateUrl: 'partials/home.html',
controller: 'HomepageCtrl',
resolve: {
homeData: ['$http', 'SettingsFactory', function($http, SettingsFactory) {
return $http.get(SettingsFactory.APIUrl + 'api/home', { withCredentials: true }).then(function (response) {
return response.data;
});
}]
}
})
.state('app.userProfiles', {
url: '/admin/users',
templateUrl: 'partials/admin/user-profiles.html',
controller: 'UserProfilesCtrl'
})...

Related

Undefined resolve data in Controller, UI-Router

NOTE: This question is similar to UI-Router and resolve, unknown provider in controller but differs in that it deals specifically with AngularJS 1.5+ and Component-based apps which changes how things are configured for a state resolve.
So I am trying to resolve some data in a child state. I had done this before for a previous resolve but am running into an issue for the 2nd one.
Here is my setup:
App State
I have a parent state "app" and a child state "home". When a User logs in they go through the "app" state which did the resolving and then they get redirected to the "home" state.
angular
.module('common')
.component('app', {
templateUrl: './app.html',
controller: 'AppController',
bindings: {
member: '=',
}
})
.config(function ($stateProvider) {
$stateProvider
.state('app', {
redirectTo: 'home',
url: '/app',
data: {
requiredAuth: true
},
resolve: {
member: ['AuthService',
function (AuthService) {
return AuthService.identifyMember()
.then(function (res) {
AuthService.setAuthentication(true);
return res.data;
})
.catch(function () {
return null;
});
}
],
organization: ['AuthService',
function (AuthService) {
return AuthService.identifyOrganization()
.then(function (res) {
return res.data;
})
.catch(function () {
return null;
});
}
],
authenticated: function ($state, member) {
if (!member)
$state.go('auth.login');
}
},
component: 'app',
});
});
Home State
angular
.module('components')
.component('home', {
templateUrl: './home.html',
controller: 'HomeController',
bindings: {
member: '=',
}
})
.config(function ($stateProvider) {
$stateProvider
.state('home', {
parent: 'app',
url: '/home',
data: {
requiredAuth: true
},
component: 'home',
resolve: {
'title' : ['$rootScope',
function ($rootScope) {
$rootScope.title = "Home";
}
],
}
});
});
And in my controller when I try to console.log the output of what should be there:
function HomeController(AuthService, $state) {
let ctrl = this;
console.log(ctrl.organization);
}
But, I am getting undefined.
My methods in AuthService are getting called the same way for the member resolve so I am not sure what the problem is.
So it turns out that I was simply missing the binding for organization in both the App State and Home State:
bindings: {
member: '=',
organization: '=',
}
NOTE: Because I used bindings, I did not have to inject the data into the Controller itself as is shown in the UI-Router docs at the following link:
https://github.com/angular-ui/ui-router/wiki/Nested-States-&-Nested-Views#inherited-resolved-dependencies
I am not sure why using the bindings allows that but for the purposes of inheriting data from a parent state, it seems to achieve the same result.
EDIT: After rewording my search queries, I was able to find the section in the UI-Router docs that actually shows the same thing that I did:
Instead of injecting resolve data into the controller, use a one-way component input binding, i.e., <.
https://ui-router.github.io/guide/ng1/route-to-component#create-a-component
This seems to connect the data to the specific Controller like how injecting the data into the Controller connects it as well. Although I am still unsure if any under-the-hood differences between binding and injecting exist.
Given that UI-Router shows the same logic that I had used, this seems to be the proper way to allow a Controller access to resolved data for a particular state.
The only other thing I would say is to pay attention to what type of binding you need to use. You can find the different types and their descriptions here under Component-based application architecture and then under Components have a well-defined public API - Inputs and Outputs:
https://docs.angularjs.org/guide/component

angular ui-router- ajax call on load

We are developing an single page application using angular JS and I am using state provider for configuring routes. Basically there is a global navigation view and a dashboard view. I have to pass few params from navigation to make a service call and then display the dashboard accordingly.I have split the states as two, one for navigation and other for dashboard. THe thing which i am not able to figure out is that where should i make ajax call to fetch dashboard data. Should i make it in navigation itself and pass it through resolve. or should i just pass the data to dashboard controller and make ajax call from there. Below is my state
$stateProvider
.state('home', {
url: '/',
templateUrl: 'templates/home.htm',
controller: 'homeController',
})
.state('dashboard', {
url: 'contact',
templateUrl: 'templates/dashboard.htm',
controller: 'dashboardController'
})
.state('state3', {
url: '/articles',
templateUrl: 'templates/state3.htm',
controller: 'state3Controller'
});
$urlRouterProvider.otherwise('/home');
This entirely depends on how you want the user experience to play out.
If you want to do all the data fetching before transitioning to the dashboard state, use a resolve state configuration
.state('dashboard', {
url: '/contact',
templateUrl: 'templates/dashboard.htm',
controller: 'dashboardController',
resolve: {
someData: function($http) {
return $http.get('something').then(res => res.data);
}
}
}
then your controller can be injected with someData, eg
.controller('dashboardController', function($scope, someData) { ... })
This will cause the state transition to wait until the someName promise has been resolved meaning the data is available right away in the controller.
If however you want to immediately transition to the dashboard state (and maybe show a loading message, spinner, etc), you would move the data fetching to the controller
.controller('dashboardController', function($scope, $http) {
$scope.loading = true; // just an example
$http.get('something').then(res => {
$scope.loading = false;
$scope.data = res.data;
});
})

Changing a request url based on url when using ui.route

I am using $http in angular for ajax calls and using ui.router for routing.
Routes
.state("/dashboard.inactive", {
url: "/inactive",
templateUrl: "angular/templates/dashboard/inactive.html",
controller: "dashboardCtrl"
})
.state("/dashboard.drafts", {
url: "/drafts",
templateUrl: "angular/templates/dashboard/drafts.html",
controller: "dashboardCtrl"
});
So the below code works if it is for a single URL.
Controller
app.controller('dashboardCtrl', function ($scope, DashboardFactory){
DashboardFactory.listings(function(DashboardFactory) {
$scope.results = DashboardFactory;
});
});
Below factory is fetching only from drafts.json resource. So when the URL changes to inactive I want it to fetch from inactive.json and active.json respectively.
Factory
app.factory('DashboardFactory', function($http){
return {
listings: function(callback){
$http.get('drafts.json').success(callback);
}
};
});
In short I need to send requests to any one of the below 3 URLs based on the URL
1) '/drafts.json'
2) '/inactive.json'
3) '/active.json'
I can create a different controllers for each active, inactive and drafts and make it fetch as expected. But is there any better way to do this??
You could use the $state service of ui route in order to tell which state your are in.
Just inject $state to your service and then use $state.current in order to access the current state config.
app.factory('DashboardFactory',
function($http, $state){
return {
listings: function(callback){
var currentView = $state.current.url.replace('/', '');
$http.get(currentView + '.json').success(callback);
}
};
});
A better solution would be to either use the params property of the state config or add some custom property like:
.state("/dashboard.inactive", {
url: "/inactive",
templateUrl: "angular/templates/dashboard/inactive.html",
controller: "dashboardCtrl",
params: {
json: 'inactive.json'
}
})
.state("/dashboard.drafts", {
url: "/drafts",
templateUrl: "angular/templates/dashboard/drafts.html",
controller: "dashboardCtrl",
params: {
json: 'drafts.json'
}
});
It is described in the documentation.

Best practices for AngularJS CRUDS

I'm trying to figger out the best way to create CRUDS (Create/Read/Update/Delete) in AngularJS by calling API requests back and forth.
I've read some articles about code structure but still I'm not sure how to maintain a clean structure by creating those CRUDS.
In this project I'm using ui-router for state management.
.state('user', {
url: "/user",
templateUrl: "app/views/user/index.html",
controller: "UserController as user",
resolve: {
data: ['UserService', function (UserService) {
return UserService.all();
}]
}
})
.state('createUser', {
url: "/user/create",
templateUrl: "app/views/user/create.html",
controller: "UserController as user"
})
What is the best way to make this work? Because I'm using the data variable, which is being resolved in my user state in the UserController, I'm not able to use the same UserController for my createUser state, because the injected variable cant be resolved.
Is there maybe any way I can check on this so I can just use the UserController for all my user-specific actions?
You can certainly use the same controller if you want, eventhough it is usually a better to have a new controller for create action. Usually people either write a special controller for each CRUD action or sometimes they use the same controller for Create and Update.
In your specific case, if you want to use the same controller, just return an empty array of users as the data variable. Like this:
.state('user', {
url: "/user",
templateUrl: "app/views/user/index.html",
controller: "UserController as user",
resolve: {
data: ['UserService', function (UserService) {
return UserService.all();
}]
}
})
.state('createUser', {
url: "/user/create",
templateUrl: "app/views/user/create.html",
controller: "UserController as user",
resolve: {
data: [function () {
return [];
}]
}
})

Angular ui-router to accomplish a conditional view

I am asking a similar question to this question: UI Router conditional ui views?, but my situation is a little more complex and I cannot seem to get the provided answer to work.
Basically, I have a url that can be rendered two very different ways, depending on the type of entity that the url points to.
Here is what I am currently trying
$stateProvider
.state('home', {
url : '/{id}',
resolve: {
entity: function($stateParams, RestService) {
return RestService.getEntity($stateParams.id);
}
},
template: 'Home Template <ui-view></ui-view>',
onEnter: function($state, entity) {
if (entity.Type == 'first') {
$state.transitionTo('home.first');
} else {
$state.transitionTo('home.second');
}
}
})
.state('home.first', {
url: '',
templateUrl: 'first.html',
controller: 'FirstController'
})
.state('home.second', {
url: '',
templateUrl: 'second.html',
controller: 'SecondController'
});
I set up a Resolve to fetch the actual entity from a restful service.
Every thing seems to be working until I actually get to the transitionTo based on the type.
The transition seems to work, except the resolve re-fires and the getEntity fails because the id is null.
I've tried to send the id to the transitionTo calls, but then it still tries to do a second resolve, meaning the entity is fetched from the rest service twice.
What seems to be happening is that in the onEnter handler, the state hasn't actually changed yet, so when the transition happens, it thinks it is transitioning to a whole new state rather than to a child state. This is further evidenced because when I remove the entity. from the state name in the transitionTo, it believes the current state is root, rather than home. This also prevents me from using 'go' instead of transitionTo.
Any ideas?
The templateUrl can be a function as well so you check the type and return a different view and define the controller in the view rather than as part of the state configuration. You cannot inject parameters to templateUrl so you might have to use templateProvider.
$stateProvider.state('home', {
templateProvider: ['$stateParams', 'restService' , function ($stateParams, restService) {
restService.getEntity($stateParams.id).then(function(entity) {
if (entity.Type == 'first') {
return '<div ng-include="first.html"></div>;
} else {
return '<div ng-include="second.html"></div>';
}
});
}]
})
You can also do the following :
$stateProvider
.state('home', {
url : '/{id}',
resolve: {
entity: function($stateParams, RestService) {
return RestService.getEntity($stateParams.id);
}
},
template: 'Home Template <ui-view></ui-view>',
onEnter: function($state, entity) {
if (entity.Type == 'first') {
$timeout(function() {
$state.go('home.first');
}, 0);
} else {
$timeout(function() {
$state.go('home.second');
}, 0);
}
}
})
.state('home.first', {
url: '',
templateUrl: 'first.html',
controller: 'FirstController'
})
.state('home.second', {
url: '',
templateUrl: 'second.html',
controller: 'SecondController'
});
I ended up making the home controller a sibling of first and second, rather than a parent, and then had the controller of home do a $state.go to first or second depending on the results of the resolve.
Use verified code for conditional view in ui-route
$stateProvider.state('dashboard.home', {
url: '/dashboard',
controller: 'MainCtrl',
// templateUrl: $rootScope.active_admin_template,
templateProvider: ['$stateParams', '$templateRequest','$rootScope', function ($stateParams, templateRequest,$rootScope) {
var templateUrl ='';
if ($rootScope.current_user.role == 'MANAGER'){
templateUrl ='views/manager_portal/dashboard.html';
}else{
templateUrl ='views/dashboard/home.html';
}
return templateRequest(templateUrl);
}]
});

Resources