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