Pass object through ui-router to controller - angularjs

I'm creating a user profile page using angular/rails. I've set up a StateProvider using ui-router, like so,
$stateProvider
.state('friendprofile', {
url: '/{name}',
views: {
"friendProfile": {
templateUrl: '../assets/angular-app/templates/_friendProfile.html',
controller: 'friendProfileCtrl',
}
},
})
This is the click action from the template,
%a.profile{"ui-sref" => "friendprofile(user)"}
name: {{ user.name }}
Note that I'm passing user here.
So when the link .profile is clicked I go to the url http://localhost:3000/#/Jan%20Jansen. So that's working fine.
In my friendProfileCtrl I have a function that calls a service,
friendProfileService.loadProfile().then(function(response) {
$scope.user_profile = response.data;
console.log ($scope.user_profile)
})
Which calls this service,
app.factory('friendProfileService', ['$http', function($http) {
return {
loadProfile: function() {
return $http.get('/users/4.json');
}
};
}])
Currently I have the return url users/4/.json hardcoded. Can I get the users id in there somehow? Or do I have to resolve it in the StateProvider?

You could just add id parameter of user inside state so that it can be easily readable from the $stateParams.
url: '/{id}/{name}', //assuming user object has `id` property which has unique id.
So while making an ajax you could directly get the user id from the user object before making an ajax.
Controller
friendProfileService.loadProfile($stateParams.id).then(function(response) {
$scope.user_profile = response.data;
console.log ($scope.user_profile)
})
Factory
app.factory('friendProfileService', ['$http', '$stateParams', function($http) {
return {
loadProfile: function(id) {
return $http.get('/users/'+ id +'json');
}
};
}])

You've said right - you should resolve it before controller loads:
$stateProvider
.state('friendprofile', {
url: '/{name}',
views: {
"friendProfile": {
templateUrl: '../assets/angular-app/templates/_friendProfile.html',
controller: 'friendProfileCtrl',
}
},
resolve: {
user: function($stateParams, friendProfileService) {
friendProfileService.loadProfile($stateParams.name);
}
}
})
And then in the controller you can inject user variable which won't be a promise but the resolved data already:
app.controller('friendProfileCtrl', [
'$scope',
'user',
...
function($scope, user, ...) {
$scope.user_profile = user;
console.log($scope.user_profile)
}
]);
Btw the value for ui-sref should be something like that: friendprofile({name: user})
And don't forget to add input parameter to loadProfile function. Probably you should better rename {name} parameter to {id}

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

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.

ui-router ignoring state params

I'm trying to implement a forgot password flow in my application that collects some information from the user and passes it through states.
After the user submits their employee ID and where they want a reset code sent to (email or phone), I send both of those to the token state:
$state.go("^.token", { employeeId: forgot.employeeid, resetType: forgot.resetType });
My router is pretty straightforward:
$stateProvider.state("authentication.forgot", {
url: "/login/forgot",
templateUrl: "partials/authentication/forgot.html",
controller: "ForgotController as forgot",
onEnter: function () { $(document).foundation(); }
});
$stateProvider.state("authentication.token", {
url: "/login/token",
templateUrl: "partials/authentication/token.html",
controller: "TokenController as token",
onEnter: function () { $(document).foundation(); }
});
And here is my token controller:
app.controller("TokenController", function ($state, $stateParams, $timeout, Authentication) {
console.log($stateParams);
});
In my token controller, $stateParams ends up being an empty object.
You are not specifying any parameter in the $stateProvider definition, put a url parameter or define the params parameter in the $stateProvider definition.
url: "/login/token",
params: { object: {} },
templateUrl: "partials/authentication/token.html",
Note than {} is the default value for object, in case no params are specified in the state transition, and you'll be able to access this via $sateParams.object
You have to declare the params that you are trying to access in the state declaration.
$stateProvider.state("authentication.token", {
url: "/login/token?employeeId:[a-zA-Z0-9]*&resetType:[A-Z]",
templateUrl: "partials/authentication/token.html",
controller: "TokenController as token",
onEnter: function () { $(document).foundation(); }
});
You would then be able to access these in your controller via $stateParams.

Angular UI router not resolving injected parameters

So consider the following fragment from my angularUI routing setup. I am navigating to the route /category/manage/4/details (for example). I expect 'category' to be resolved before the relevant controller loads, and indeed it is to the extent that I can put a breakpoint inside the resolve function that returns the category from the category service and see that the category has been returned. Now putting another breakpoint inside the controller itself I can see that 'category' is always undefined. It is not injected by UI router.
Can anyone see the problem? It may be somewhere other than in the code I've provided but as I have no errors when I run the code, it's impossible to tell where the source of the issue might lie. Typical js silent failures!
.state('category.manage', {
url: '/manage',
templateUrl: '/main/category/tree',
controller: 'CategoryCtrl'
})
.state('category.manage.view', {
abstract: true,
url: '/{categoryId:[0-9]*}',
resolve: {
category: ['CategoryService', '$stateParams', function (CategoryService, $stateParams) {
return CategoryService.getCategory($stateParams.categoryId).then(returnData); //this line runs before the controller is instantiated
}]
},
views: {
'category-content': {
templateUrl: '/main/category/ribbon',
controller: ['$scope', 'category', function ($scope, category) {
$scope.category = category; //category is always undefined, i.e., UI router is not injecting it
}]
}
},
})
.state('category.manage.view.details', {
url: '/details',
data: { mode: 'view' },
templateUrl: '/main/category/details',
controller: 'CategoryDetailsCtrl as details'
})
The concept is working. I created working plunker here. The changes is here
instead of this
resolve: {
category: ['CategoryService', '$stateParams', function (CategoryService, $stateParams) {
//this line runs before the controller is instantiated
return CategoryService.getCategory($stateParams.categoryId).then(returnData);
}]
},
I just returned the result of the getCategory...
resolve: {
category: ['CategoryService', '$stateParams', function (CategoryService, $stateParams) {
return CategoryService.getCategory($stateParams.categoryId); // not then
}]
},
with naive service implementation:
.factory('CategoryService', function() {return {
getCategory : function(id){
return { category : 'SuperClass', categoryId: id };
}
}});
even if that would be a promise... resolve will wait until it is processed...
.factory('CategoryService', function($timeout) {return {
getCategory : function(id){
return $timeout(function() {
return { category : 'SuperClass', categoryId: id };
}, 500);
}
}});

reuse controllers, views and services in angular app

I want my app to fetch data from a server API, lets say I have the following API /orders , /users. Basically I just want to display the json I get from the server in a table. I am using ng-table directive for that purpose. So, in terms of components I have :
Services - both services do the same thing - go to an API and fetch JSON
Views - same view for both of the APIs, just display different data
Controllers - both fetch data from the service and display it in the table view.
So the way I see it, they all do the same thing with very minor adjustments. What I would like to do is
angular.module('admin').config(function ($routeProvider, $locationProvider) {
// same template and controller for both
$routeProvider.
when('/users', {
templateUrl: '/partials/table.html',
controllers: '/js/controllers/table.js
}).
when('/orders', {
templateUrl: '/partials/table.html',
controllers: '/js/controllers/table.js'
});
});
And in my service
factory('AdminService', ['$resource', function($resource) {
// somehow I want to inject the right endpoint, depending on the route
return $resource( '/:endpoint',
{ }, {} );
}]);
And in my table controller as well, I want to be able to know what to pass to the service
I could of course use separate controllers and services for each API endpoint it just seems like a wasteful duplication of code that does 99% the same thing
Is this possible ?
How do I wire everything together ?
If you want separate routes, but the same controller, but with some options, you can use the resolve option in the route definition to pass some options:
$routeProvider.
when('/users', {
templateUrl: '/partials/table.html',
controller: 'TableController',
resolve: {
'option1': function() {
return 'val1'
},
'option2': function() {
return 'val2'
}
}
}).
when('/orders', {
templateUrl: '/partials/table.html',
controller: 'TableController',
resolve: {
'option1': function() {
return 'val3'
},
'option2': function() {
return 'val4'
}
}
});
Then the controller in both cases will be injected with "option1" and "option2", which can be used to customise its behaviour:
app.controller('TableController', function($scope, option1, option2) {
// Do something with option1 or option1
});
From the resolve object functions, you could return a $resource object, or even return a promise that will be resolved with some data before the route is displayed. You can see the docs for $routeProvider for details.
Edit: For the resource, you could write a configurable factory like:
app.factory('MyResource', function($resource) {
return function(endpoint) {
return $resource('/' + endpoint);
}
});
And then use it in the controller:
app.controller('TableController', function($scope, MyResource, endpoint) {
var currentResource = MyResource(endpoint);
currentResource.query(); // Whatever you want to do with the $resource;
}
assuming that "endpoint" was was one of the options added in the resolve, so something like
when('/orders', {
templateUrl: '/partials/table.html',
controller: 'TableController',
resolve: {
'endpoint': function() {
return '/orders'
}

Resources