Undefined resolve data in Controller, UI-Router - angularjs

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

Related

Is it possible to use resolves outside of states?

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'
})...

Accessing ui-router state controller's data in data section

I have the state configuration defined in as follows:
angular.module('app', ['ui.router')
.config(function ($stateProvider, $urlRouterProvider) {
.state('element', {
url: '/element',
templateUrl: 'element.html',
controller: 'ElementCtrl',
controllerAs:'ec',
data: {
title: 'Element {{ec.name}}',
}
})
.state('element.detail', {
url: '/detail',
templateUrl: 'detail.html',
controller: 'HomeCtrl',
controllerAs:'hc',
data: {
title: 'Detail {{hc.age}}',
}
})
.controller('ElementCtrl', function ($scope, $stateParams) {
this.name = "myName";
})
.controller('HomeCtrl', function ($scope, $stateParams) {
this.age = "myName22";
});
Here, I want to dynamically determine the data.title field based on the value assigned to the variable in the controller.
Is there a way to do this?(It is better if I do this before the state is activated).
It is not possible. See more here:
Accessing parameters in custom data
there is NO way how to set these data dynamically. E.g. based on the $stateParams. These settings are expected to be defined in the .config() phase, not evaluated in .run()(in compariosn with others like resolve, templateUrl...)
The data : {} is intended as a static setting, defined in a config phase.
The way to go is to use resolve : {}:
Resolve
You can use resolve to provide your controller with content or data that is custom to the state. resolve is an optional map of dependencies which should be injected into the controller.

Not able to send data using $state.go

I have this code in my angular app. So as the code says, there are multiple views being loaded and I want to pass one parameter.
.state('root.moduleListing', {
params: ['registerData'],
views: {
"main_container": {
controller: 'ModuleListingController as model',
templateUrl: 'misc/module-listing/module-listing.tpl.html',
resolve: {
registerData: ['$stateParams', function($stateParams) {
return $stateParams.registerData;
}]
}
},
"components_header_header": {
controller: 'HeaderController as model',
templateUrl: 'components/header/post-login.tpl.html',
},
"components_footer_footer": {
controller: 'FooterController as model',
templateUrl: 'components/footer/footer.tpl.html',
}
}
}
& I am calling it using
$state.go('root.moduleListing', {'registerdata': response});
& in controller, I am just trying to log it to the console.
module.controller('ModuleListingController', function ($scope, $state, registerData) {
var model = this;
console.log("registerData : " + registerData);
}
Now the app doesn't even start i.e. I get an error in the console saying:
failed to instantiate module amcatUI due to:
TypeError: id.match is not a function
at getArrayMode
Type of defined parameters and toParamas of $state.go should be same array or object on both sides of state transition. Just add your parameters with a default value on your state definition.
You need to update your code from
params: ['registerData'],
to
params: {'registerData' : null},
You can refer to "https://stackoverflow.com/a/26204095/1132354" for detailed answer.
Edit
Also, you will need to update
$state.go('root.moduleListing', {'registerdata': response});
to
$state.go('root.moduleListing', {'registerData': response});
There is a small typo error.

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