ui-router: $stateParams is empty in resolves - angularjs

Besides ui-router, I am using ui-bootstrap's $modal service.
I use resolves (actually passed inside a modal) on the onEnter property of the state (with url parameters) to activate modals (as mentioned in the docs|FAQ of ui-router).
I tried to access the $stateParams, however it seems to be an empty object when the resolves fire.
function onEnter($modal, $state) {
// simple handler
function transitionToOverlay() {
return $state.transitionTo('parent');
}
// actual modal service
$modal
.open({
size: 'sm',
resolve: { getY: getY },
controller: 'ChildCtrl as child',
template: template
})
.result
.then(transitionToOverlay)
.catch(transitionToOverlay);
}
// resolve
function getY($state, $stateParams) {
console.log('State resolve getY...');
console.log($stateParams); // returns {} empty object
return 'y'; // just a dummy resolve
}
Here's a plnkr for demonstration purposes.

UI-Router doesn't have any control over your $modal call. Resolves should go on state definitions if you would like UI-Router to inject them.
var state = {
url: '{id}',
name: 'parent.child',
resolve: { getY: getYfn }, // moved from $modal call
onEnter: function(getY) { // injected into onEnter
$modal.open({
resolve: { getY: function () { return getY; } }, // passed through to $modal.open
controller: 'ChildCtrl as child', // ChildCtrl injects getY
});
}
}

Just posting this in case someone has the same problem...
I had the same problem as in the original question but the selected answer didn't help me too much since I couldn't get to access the resolve defined directly in the state inside my modal controller.
However, I noticed $stateParams is still accessible in the onEnter function so it is possible to create a variable here and then use this variable inside the $modal.open() function.
.state('parent.child', {
url: 'edit/:id',
// If defining the resolve directly in the state
/*resolve: { // Here $stateParams.id is defined but I can't access it in the modal controller
user: function($stateParams) {
console.log('In state(): ' + $stateParams.id);
return 'user ' + $stateParams.id;
}
},*/
onEnter: function($modal, $stateParams, $state) {
var id = $stateParams.id; // Here $stateParams.id is defined so we can create a variable
$modal.open({
templateUrl: 'modal.html',
// Defining the resolve in the $modal.open()
resolve: {
user: function($stateParams) {
console.log('In $modal.open(): ' + $stateParams.id); // Here $stateParams.id is undefined
console.log(id); // But id is now defined
return 'user ' + id;
}
},
controller: ChildCtrl,
controllerAs: 'ctrl'
})
.result
.then(function(result) {
return $state.go('^');
}, function(reason) {
return $state.go('^');
});
}
})
Here is an example plnkr : http://plnkr.co/edit/wMMXDSsXLABFr0P5q2On
Also, if needing to define the resolve function outside the configuration object, we can do it like this:
var id = $stateParams.id;
$modal.open({
resolve: {
user: myResolveFunction(id)
},
...
});
And:
function myResolveFunction(id) {
return ['MyService', function(MyService) {
console.log('id: ' + id);
return MyService.get({userId: id});
}];
}

Related

Is it possible to mix injected dependencies with custom arguments in AngularJS?

I'm using UI Bootstrap's $uibModal to create a modal. I'm also using UI Router 0.2.15, so what I want is a state opening in a new modal.
This is what I have in my config function:
$stateProvider
.state("mystate.substate1", {
url: '...',
template: '<div ui-view></div>',
onEnter: showFirstCustomModal
})
.state("mystate.substate2", {
url: '...',
onEnter: showSecondCustomModal
});
// End of calling code
function showFirstCustomModal($uibModal) {
var options = {
backdrop: 'static',
templateUrl: '...',
controller: 'Controller1',
controllerAs: 'controller'
};
$uibModal.open(options);
}
function showSecondCustomModal($uibModal) {
var options = {
backdrop: 'static',
templateUrl: '...',
controller: 'Controller2',
};
$uibModal.open(options);
}
The two modal methods above are very similar. I would like to replace them with a generic method:
$stateProvider
.state("mystate.substate1", {
url: '...',
onEnter: showGenericModal('some_template','SomeController1', 'alias1')
})
.state("mystate.substate2", {
url: '...',
onEnter: showGenericModal('some_other_template', 'SomeController2')
});
// End of calling code
function showGenericModal(templateUrl, controller, controllerAlias, $uibModal) {
var options = {
backdrop: 'static',
templateUrl: templateUrl,
controller: controller
};
if(!!controllerAlias) {
options.controllerAs: controllerAlias;
}
$uibModal.open(options);
}
I put the $uibModal as the last argument to avoid it getting reassigned. But I can't get this to work. The error I get is
Cannot read property 'open' of undefined
Also, I've been reading this and I know that you'll have to use the $injector in order to allow your service to be injected. But I supposed that's already handled by UI-Bootstrap.
Since $stateProvider is defined in config block, $uibModal can't be passed from there as a reference.
It is not possible to mix dependencies and normal arguments in Angular DI. For onEnter it should be a function that accepts the list of dependencies.
The code above translates to:
onEnter: showGenericModal('some_other_template', 'SomeController2')
...
function showGenericModal(templateUrl, controller, controllerAlias) {
return ['$uibModal', function ($uibModal) {
...
$uibModal.open(options);
}];
}
Or a better approach:
onEnter: function (genericModal) {
genericModal.show('some_other_template', 'SomeController2');
}
...
app.service('genericModal', function ($uibModal) {
this.show = function (templateUrl, controller, controllerAlias) {
...
$uibModal.open(options);
}
});
#estus answer correct, I don't know how I didn't saw the state: "For onEnter it should be a function that accepts the list of dependencies.".
However, I will let my answer here to provide another perspective. You can define a service to wrap up and organize correctly your code, in order to call a customized modal on onEnter state event:
angular.module('app').service('AppModals', AppModals);
// or use /** #ngInject */ aswell
AppModals.$inject = ['$uibModal'];
function AppModals($uibModal) {
this.open = function _generateModal(options) {
var defaultOptions = {
backdrop: 'static'
// Any other default option
};
return $uibModal.open(angular.extend({}, defaultOptions, options);
};
}
On the state definition:
$stateProvider
.state('app.state', {
url: '/state-modal',
template: '<ui-view></ui-view>',
controller: 'DummyCtrl',
controllerAs: 'dummy',
onEnter: appState_onEnter
});
// or use /** #ngInject */ aswell
appState_onEnter.$inject = ['$uibModal'];
function appState_onEnter(AppModals) {
AppModals.open({
templateUrl: 'modals/state-modal.html',
controller: 'DummyCtrl',
controllerAs: 'dummy'
});
}

scope access when promise resolved in Angular UI-Router

I have a small question regarding scopes and promises.
I declared wiz_ids outside a promise call and I would like to access it again when the promise is resolved.
I tried to use bind() but without luck.
This is my state:
state: 'wizard',
config: {
url: '/wizard',
templateUrl: 'app/partners/wizard/wizard.html',
controller: 'WizardController',
controllerAs: 'vm',
redirectTo: 'wizard.step1',
resolve: {
/* #ngInject */
promiseData: ['$window', '$location', 'WizardDataService', function ($window, $location, WizardDataService) {
var wiz_ids = {
'wiz_install_id': $location.search().install_id,
'wiz_instance_id': $location.search().instance_id
};
return WizardDataService.getWizardData(wiz_ids)
.then(function (response) {
// How do I access wiz_ids from here? //
return response.data;
});
}]
},
}
You could return a more complex object inside then().
Something like:
return WizardDataService.getWizardData(wiz_ids)
.then(function(response) {
var data = {
wiz_ids: wiz_ids,
wiz_data: response.data
}
return data;
});
Then in controller access the individual properties accordingly

Angular Component Bindings Undefined

I'm trying to put together my first angular component with ngRoute and so far I'm unable to get data to resolve.
config:
.when('/myfirstcomponent', {
template: '<myfirstcomponent claimKeys="$resolve.claimKeys"></myfirstcomponent>',
resolve: {
claimKeys: ['$http', function($http) {
$http.get('server/claimkeys.json').then((response) => {
var claimKeys = response.data.DATASET.TABLE;
return claimKeys;
});
}]
}
})
Component:
.component('myfirstcomponent', {
bindings: {
'claimKeys': '#'
},
templateUrl: 'components/component.html',
controller: [function() {
this.$onInit = function() {
var vm = this;
console.log(vm.claimKeys);
};
}]
The html for the component simply has a p element with some random text that's all.
I can see when debugging that I am retrieving data but I cannot access it on the component controller...
EDIT: Thanks to the accepted answer below I have fixed my issue. It didn't have anything to do with an issue with asynchronous calls but with how I had defined my route and the component. See below code for fix. Thanks again.
some issues:
as you said claimKeys within directive should be claim-keys
its binding should be '<' (one way binding) or '=' (two way binding), but not '#' which just passes to directive a string found between quotes
in your directive's controller var vm = this; should be above
$onInit function and not inside it (the scopes are different)
resolve.claimkeys should return $http's promise and not just call
it
claimKeys should be received by router's controller as injection and passed to its template
controllerAs: '$resolve' should be used by router
app.component('myfirstcomponent', {
bindings: {
'claimKeys': '='
},
template: 'components/component.html',
controller: function() {
var vm = this;
this.$onInit = function() {
console.log(vm.claimKeys);
};
}
});
app.config(function ($stateProvider) {
$stateProvider.state('myfirstcomponent', {
url: '/myfirstcomponent',
template: '<myfirstcomponent claim-keys="$resolve.claimKeys"></myfirstcomponent>',
resolve: {
claimKeys: ['$http', function($http) {
return $http.get('claimkeys.json').then((response) => {
return response.data.DATASET.TABLE;
});
}]
},
controller: function (claimKeys) {
this.claimKeys = claimKeys;
},
controllerAs: '$resolve'
})
});
plunker: http://plnkr.co/edit/Nam4D9zGpHvdWaTCYHSL?p=preview, I used here .state and not .when for routing.

AngularJS: $modal, passing $scope variable to child controller

JS Code,
var mod= $modal.open({
animation: true,
templateUrl: $scope.url,
controller: function($scope, $modalInstance,custObj) {
alert(custObj); /*************Line 1**************************/
$scope.save = function() {
$modalInstance.close();
};
$scope.cancel = function(){
$modalInstance.dismiss('cancel');
};
},
resolve : {
/************Resolving current scope value to retrieve it in Line 1*******/
custObj: $scope.customer;
}
});
Though I sent $scope.customer object via resolve property, the modal controller is not getting the value. Am I missing anything?
The value of custObj in the resolve-object should be a function returning what you want to inject:
resolve: {
custObj: function(){
return $scope.customer;
}
}
check documentation for $routeProvider resolve
use
locals : {
custObj: $scope.customer;
}
instead of resolve.

stateParams vs $stateParams with ui-router?

I am confused. For a long time now I have been using stateParams as a means of find out the stateParams inside a templateUrl.
Now I tried to do the same in a resolve and it does not work. In fact nothing happens when I use stateParams.
However by chance I found that I can use $stateParams in the resolve and it works.
Can someone tell me what is the difference and why do I need to use stateParams in the templateUrl and $stateParams in the resolve?
var auth = {
name: 'auth',
url: '/Auth/:content',
templateUrl: function (stateParams) {
var page = 'app/auth/partials/' + stateParams.content + '.html';
return page;
},
controller: function ($scope, authService) {
$scope.aus = authService;
},
resolve:
{
init: function ($stateParams) {
var x = 99;
return true;
}
}
};
I've created working example here, showing that $statePrams are accessible in the resolve
// States
$stateProvider
.state('auth', {
url: "/auth/:content",
templateUrl: 'tpl.html',
controller: 'AuthCtrl',
resolve : {
init : ['$stateParams' , function($stateParams){
return { resolved: true, content: $stateParams.content };
}]
}
})
Controller
.controller('AuthCtrl', ['$scope', 'init', function ($scope, init) {
$scope.init = init;
}])
and this could be the calls
auth/8
auth/xyz
Check it here

Resources