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'
});
}
Related
Is it possible to get resolves working when using ng-controller? I prefer to use ng-controller as it allows me access to all 1.6+ life-cycle hooks such as $onDestroy, which I loose when defining the controller on state obj.
Plunker:
https://plnkr.co/edit/2FJ0dGtFQtBtcQ0uVbTi?p=preview
In the example below, the view loaded in 'main' makes the myData available to inject, however in main2 the controller is defined with ng-controller and myData is no longer available to inject.
$stateProvider.state('home', {
url: '/',
views: {
'main': {
controller: 'MainCtrl',
controllerAs: 'vm',
templateUrl: 'main.html'
},
'main2': {
templateUrl: 'main2.html'
}
},
resolve: {
myData: function() {
return ['My', 'resolve', 'is', 'working'];
}
}
});
Instantiated with ui-router state:
app.controller('MainCtrl', function(myData) {
console.log("MainCtrl")
console.log(myData);
console.log(this);
this.message = myData.join(' ');
});
Instatiated with ng-controller:
app.controller('MainCtrl2', function($scope) {
console.log("MainCtrl2");
console.log($scope);
this.message = $scope.$resolve.myData.join(' ');
});
The DEMO on PLNKR.
Ui router looks like:
<div ui-view="test1">
<div ui-view="test2">
My routing:
.state('testsPage', {
url: "/",
// templateUrl: "public/src/settings/settings.php",
views: {
'test1': {
templateUrl: 'public/src/test2.html',
controller: 'test2Controller',
controllerAs: 'vm',
resolve: {
getData: function(testService) {
return testService.getdata();
}
}
},
'test2': {
templateUrl: 'public/src/test1.html',
controller: 'test1ontroller',
controllerAs: 'vm',
resolve: {
getData: function(testService) {
return testService.getdata();
}
}
},
I want to call getData only once and not inject it the each controller
If I do that in each controller it means that I call to getData from server two times in loading.
I tried to use controller inheritance but it ask to inject that service
// Inherit from Base Controller
angular.extend(vm, $controller('baseController', {
getData: getData,
testService: testService,
$rootScope: $rootScope
}));
How can I make it like inheritance?
Do I need to move it to run the function? If yes do I have to use rootScope to get that data in the controller?
Try putting the resolve on the parent:
.state('testsPage', {
url: '/',
views: ...,
resolve: {
getData: function(testService) {
return testService.getdata();
}
}
...
}
You would then need to add getData as a dependancy on each views controller:
// Controller
function test1ontroller(getData) {
}
I've implemented my controller like this:
angular
.module(appSettings.appName)
.controller('someCtrl', function (objectFromResolve) {
var vm = this;
vm.serviceName = objectFromResolve;
})
And in another file I have the state implementations:
angular
.module(appSettings.appName)
.config(function ($stateProvider, $locationProvider) {
$urlRouterProvider.otherwise(appSettings.stateNames.menu);
$stateProvider.state(appSettings.stateNames.root, {
abstract: true,
resolve: {
objectFromResolve: function () {
return {};
}
},
templateUrl: './some_url.html'
}
);
$stateProvider
.state(appSettings.stateNames.menu, {
url: '/menu',
parent: appSettings.stateNames.root,
templateUrl: './some_url2.html',
controller: 'someCtrl(objectFromResolve)',
controllerAs: 'vm'
});
});
How can I pass the controller like 'someCtrl(objectFromResolve)' in order To invoke it not like a function, but a string and with args from the resolve object ???
I have a controller in AngluarJS defined like so:
'use strict';
var app = angular.module('app');
app.controller('AppCtrl', ['sth',
function (sth) {
this.inverse = false;
}
]);
here is routes deffinition:
$stateProvider.
state('app', {
abstract: true,
templateUrl: 'app/views/layout.html',
controller: 'AppCtrl',
controllerAs: 'app',
resolve: {}
}).
state('app.settings', {
abstract: true,
url: '/settings',
template: '<ui-view/>',
onEnter: function () {
}
});
How to access inverse variable from AppCtrl in app.settings route?
If you want to share data between 2 controllers, a service/factory is your best bet. Below is a an example of how you would do it. I have written it free-hand, so there may be syntax errors, but you get the idea.
app.controller('AppCtrl', ['sth', 'SharedData',
function (sth, SharedData) {
SharedData.inverse = false;
}
]);
app.factory('SharedDate', function() {
var data = {
inverse: true // or whatever default value you want to set
};
return data;
})
Now, you can inject SharedData factory in any controller, where you want to use that data.
Lets say I have two routes defined, served by the same view and controller, such as
/customers/:cutomerId/edit
/customers/add
in a controller I need to determine in which "mode" is view in. How do I map "edit" and "add" segments of the route so that it appears in $routeParams. Is there a way?
You may user route's resolves to solve it.
For example:
myApp.config(function($routeProvider) {
$routeProvider
.when('/customers/:cutomerId/edit', {
templateUrl: 'myView.html',
controller: 'MyController',
resolve: {
mode: function() {
return 'edit';
}
}
})
.when('/customers/add', {
templateUrl: 'myView.html',
controller: 'MyController',
resolve: {
mode: function() {
return 'add';
}
}
});
myApp.controller('MyController', function($scope, mode){
// Now controller knows it's mode.
});