I am using ngAside to display some data. The data depends on a variable chosen by the user so I need to pass an Id to ngAside's controller.
Right now my controller looks like this:
$aside.open({
templateUrl: 'details.html',
placement: position,
size: 'lg',
backdrop: true,
locals: {
mode: "create"
},
controller: function Ctrl($scope, $uibModalInstance, mode) {
$scope.test = mode;
$scope.ok = function (e) {
$uibModalInstance.close();
e.stopPropagation();
};
$scope.cancel = function (e) {
$uibModalInstance.dismiss();
e.stopPropagation();
};
}
})
I have based this on what I've seen written on ngMaterial here.
It is not working as expected though and Im getting
Unknown provider: modeProvider <- mode
How can I fix this and be able to pass variables to be displayed on my ngAside template?
Since you want to inject "mode" variable in a controller, my guess would be using the standard "resolve" parameter.
$aside.open({
templateUrl: 'details.html',
placement: position,
size: 'lg',
backdrop: true,
resolve: {
mode: function() {
return "create";
}
},
controller: function Ctrl($scope, $uibModalInstance, mode) {
[...]
}
});
Basically, for each controller parameter that is not registered in the Dependency Injection list, you need to provide a function which will return the value you want to be injected in the variable.
This function :
mode: function() {
return "create";
}
Will be executed and its return value will populate the "mode" controller argument.
Related
I'm developing a simple modal window with ui.bootstrap. This modal is showed when we click in a certain button binding to a controller and fires up, but the modal and its content is binding to another controller so when we click is necessary to know where the controller is which it'll be in another folder of the project.
For example, imaging the structure as follows:
component1
..... template1.html
..... controller1.js
component2
..... template2.html
..... controller2.js
The controller1.js is in charge of load the modal view which renders and is binding with template2.html and controller2.js respectively. So, in controller1.js we have this:
$scope.open = function (size) {
var modalInstance = $uibModal.open({
templateUrl: 'components/component2/template2.html',
controller: 'components/component2/controller2.js',
size: size,
resolve: {
items: function () {
return $scope.items;
}
}
});
modalInstance.result.then(function (selectedItem) {
$scope.selected = selectedItem;
$log.debug(selectedItem);
}, function () {
$log.info('Modal dismissed at: ' + new Date());
});
};
Which obviously does not work for controller2.js. As we do we templateUrl, there is any way to load a controller passing its path as parameter in the $uibModal.open?
I have not tested it, but do something like:
var modalInstance = $uibModal.open({
templateUrl: 'components/component2/template2.html',
controller: 'ModalController',
size: size,
resolve: {
items: function () {
return $scope.items;
}
}
});
app.controller('ModalController', function ($scope, $modalInstance) {
// do some things
});
I have a directive defined as follows:
app.directive('newTask', function () {
return {
restrict: 'AE',
scope: {
parentCase: "=",
options: "="
},
templateUrl: '/app/views/task/newTask.html',
controller: 'newTaskController'
};
});
This works great when I'm using it in the HTML on a page where I want to show it by default using:
<new-task parent-case="case" options="options" />
However, I'm working on having a modal pop-up and display the page similar to how the other page does it.
To do so, inside of the button click, I have
var modalInstance = $modal.open({
templateUrl: '/app/views/task/newTask.html',
backdrop: 'static',
controller: 'newTaskController',
resolve: {
parentCase: function () {
return {};
},
options: function () {
return { InitialTask: true };
}
}
})
This isn't passing 'parentCase' and 'options' through over $scope. It looks like instead it wants me add parameters to the newTaskController to allow these to come through. Is there a way to get these to resolve through the new $scope on newTaskController similar to how I do it through the HTML?
Do I need to have a separate "newTaskModal.html" that gets opened instead and just have
<new-task parent-case="case" options="options" />
on it in order to get the functionality I'm looking for?
"I think you can pass a scope parameter to your $model.open function. Try adding scope: $scope to your object" -- Komo
I'm using UI-Router and angular bootstrap-ui. I have a state setup to create modal 'onEnter'. I'm having problems now when I'm trying to close the modal 'onExit'. Here is the basic state. It will open a modal when 'items.detail' is entered and it will transitions to 'items' when that modal is closed or dismissed.
.state('items.detail', {
url: '/{id}',
onEnter: function ($stateParams, $state, $modal, $resource) {
var modalInstance = $modal.open({
templateUrl: 'views/modal/item-detail.html',
controller: 'itemDetailCtrl'
})
.result.then(function () {
$state.transitionTo('items');
}, function () {
$state.transitionTo('items');
});
}
})
I've tried using the onExit handler like so. But haven't been able to access the modalInstance or the scope that the modal is in from that handler. Everything I try to inject comes up undefined.
.state('items.detail', {
url: '/{id}',
onEnter: function ($stateParams, $state, $modal, $resource) {
var modalInstance = $modal.open({
templateUrl: 'views/modal/item-detail.html',
controller: 'itemDetailCtrl'
})
.result.then(function () {
$state.transitionTo('items');
}, function () {
$state.transitionTo('items');
});
},
onExit: function ($scope) {
controller: function ($scope, $modalInstance, $modal) {
$modalInstance.dismiss();
};
}
})
from within my modal's controller I've tried listening for state changes.
$scope.$on('$stateChangeStart', function() {
$modalInstance.dismiss();
});
I've tried this with both $scope.$on and $rootScope.$on and both of these work but they end up being called every time I transition between any states. This only happens however after I've opened the modal.
In case this last bit is unclear... When I refresh my angular app I can transition between all my other states with out this listener event being called but after I open that modal all of my state changes get called by that listener, even after the modal is closed.
Although the question is quite old, as I ran into the same situation recently, and came up with a better solution, I decided to share my results here anyway. The key point is to move the $modal.open service into resolve part of the state, which is kind of pre-load data and $promise services, and then inject the resolved modelInstance into onEnter, onExit etc. Codes might looks like as follow:
.state('items.detail', {
url: '/{id}',
resolve: {
modalInstance: function(){
return $modal.open({
templateUrl: 'views/modal/item-detail.html',
controller: 'itemDetailCtrl'
})
},
},
onEnter: function ($stateParams, $state, modalInstance, $resource) {
modalInstance
.result.then(function () {
$state.transitionTo('items');
}, function () {
$state.transitionTo('items');
});
},
onExit: function (modalInstance) {
if (modalInstance) {
modalInstance.close();
}
}
})
I think you can better organize your modal opening behavior.I would not use onEnter and onExit handlers inside state definition. Instead it's better to define controller which should handle modal:
.state('items.detail', {
url: '/{id}',
controller:'ItemDetailsController',
template: '<div ui-view></div>'
})
Then define your controller:
.controller('ItemDetailsController', [
function($stateParams, $state, $modal, $resource){
var modalInstance = $modal.open({
templateUrl: 'views/modal/item-detail.html',
controller: 'ModalInstanceCtrl',
size: size,
resolve: {
itemId: function () {
return $stateParams.id;
}
modalInstance.result.then(function () {
$state.go('items');
}, function () {
$state.go('items');
});
}
});
}
])
Then define your ModalInstanceCtrl:
.controller('ModalInstanceCtrl', [
function ($scope, $modalInstance, itemId) {
//Find your item from somewhere using itemId and some services
//and do your stuff
$scope.ok = function () {
$modalInstance.close('ok');
};
$scope.cancel = function () {
$modalInstance.dismiss('cancel');
};
};
]);
In this way you modal will be closed within ModalInstanceCtrl, and you will not worry about state's onExit handler.
About listener added on $scope. Seems when you add the listener and never remove it whenever your state changes, this is causing memory leak and handler function gets executed every time your app changes his state! So it's better to get rid of that event listener as you don't need it actually.
You can prevent leaving the state by listening for the $stateChangeStart event
$scope.$on('$stateChangeStart', function(event, toState, toParams, fromState, fromParams){
console.log('$stateChangeStart', toState);
if (toState.name != 'items.list'){
event.preventDefault();
var top = $modalStack.getTop();
if (top) {
$modalStack.dismiss(top.key);
}
$state.go('items.list');
}
});
I'm trying to write a unit test that asserts the correct variable is being sent to the resolve property of ui.bootstrap.modal from the Angular-UI Bootstrap components. Here is what I have so far:
// Controller
angular.module('app')
.controller('WorkflowListCtrl', function ($scope, $modal) {
// Setup the edit callback to open a modal
$scope.edit = function(name) {
var modalInstance = $modal.open({
templateUrl: 'partials/editWorkflowModal.html',
controller: 'WorkflowEditCtrl',
scope: $scope,
resolve: {
name: function() { return name; }
}
});
};
});
It's worth noting that the resolve.name property must be a function for the Angular-UI component to work correctly - previously I had tried resolve: { name: name } but this didn't work.
// Unit Test
describe('Controller: WorkflowListCtrl', function () {
// load the controller's module
beforeEach(module('app'));
var workflowListCtrl,
scope,
modal;
// Initialize the controller and a mock scope
beforeEach(inject(function ($controller, $rootScope) {
scope = $rootScope.$new();
modal = {
open: jasmine.createSpy()
};
workflowListCtrl = $controller('WorkflowListCtrl', {
$scope: scope,
$modal: modal
});
it('should allow a workflow to be edited', function() {
// Edit workflow happens in a modal.
scope.edit('Barney Rubble');
expect(modal.open).toHaveBeenCalledWith({
templateUrl: 'partials/editWorkflowModal.html',
controller: 'WorkflowEditCtrl',
scope: scope,
resolve: {
name: jasmine.any(Function)
}
});
});
}));
});
At the moment, this is just checking that the resolve.name property is a function, but what I'd really like to do is assert the resolve.name function returns Barney Rubble. This syntax obviously doesn't work:
expect(modal.open).toHaveBeenCalledWith({
templateUrl: 'partials/editWorkflowModal.html',
controller: 'WorkflowEditCtrl',
scope: scope,
resolve: {
name: function() { return 'Barney Rubble'; }
}
});
It seems like I somehow want to spy on the resolve.name function to check it was called with Barney Rubble but I can't figure out a way to do that. Any ideas?
So I have figured out a way to do this.
Define a 'private' function on $scope:
$scope._resolve = function(item) {
return function() {
return item;
};
};
Modify the original $scope function to call this 'private' method:
$scope.edit = function(name) {
var modalInstance = $modal.open({
templateUrl: 'partials/modal.html',
controller: 'ModalCtrl',
scope: $scope,
resolve: {
name: $scope._resolve(name)
}
});
};
Update your tests to mock this function and return the original value, then you can test it was passed in correctly.
it('should allow a workflow to be edited', function() {
// Mock out the resolve fn and return our item
spyOn($scope, '_resolve').and.callFake(function(item) {
return item;
});
// Edit workflow happens in a modal.
scope.edit('Barney Rubble');
expect(modal.open).toHaveBeenCalledWith({
templateUrl: 'partials/modal.html',
controller: 'ModalCtrl',
scope: scope,
resolve: {
name: 'Barney Rubble'
}
});
});
I'm trying to include an angular-ui modal in my web application but am having issues with getting everything set up.
The documentation indicate that you can use $modalInstance to inject the child controller into the parent controller but I don't quite understand how to go about doing so.
Here is the current code (it is straight from the modal demo from the documentation):
angular.module('myApp.controllers', []).
controller('addContent', function ($scope, $http, $modal, $log){
//modaltest
$scope.items = ['item1', 'item2', 'item3'];
$scope.addTerm = function () {
var newTerm = $modal.open({
templateUrl: 'newTermModal.jade',
controller: newTerms,
resolve: {
items: function () {
return $scope.items;
}
}
});
newTerm.result.then(function (selectedItem) {
$scope.selected = selectedItem;
}, function () {
$log.info('Modal dismissed at: ' + new Date());
});
};
}).
controller("newTerms",function($scope, $modalInstance, items){
$scope.items = items;
$scope.selected = {
item: $scope.items[0]
};
$scope.ok = function () {
$modalInstance.close($scope.selected.item);
};
$scope.cancel = function () {
$modalInstance.dismiss('cancel');
};
});
When I run the app like it is now and click the button to open the modal (addTerm function) the app crashes with the error "ReferenceError: newTerms is not defined."
As I mentioned above, the angular-ui site indicates you can inject a controller with $modalInstance but I have not been able to figure out how.
a
Any help would be greatly appreciated!
EDIT
After adding the quotation marks on the pathname as suggested by Chandermani, it seems the modal is loading in the current page rather than the specified template.
I've changed the path to the following: templateUrl:
$scope.addTerm = function () {
var newTerm = $modal.open({
templateUrl: './views/partials/newTermModal.jade',
controller: 'newTerms',
resolve: {
items: function () {
return $scope.items;
}
}
});
A screenshot of the issue follows:
Any idea what could be causing this?
Well you can pass the controller as a string value. I took the default demo sample for modal and changed it to pass controller name instead of controller itself.
See my plunker http://plnkr.co/edit/jpJX4WvHw0SSYm3pAAzq?p=preview
So something like this
controller: 'newTerms',
should work.
I got the same problem, the modal loads main HTML file but not template.
My previous configuration was:
opens dialogs but dialog content is main HTML (like on your pic)
$scope.opts = {
backdrop: true,
backdropClick: true,
dialogFade: false,
keyboard: true,
templateUrl : 'app/reports/modalContent.html',
controller : 'ModalInstanceCtrl',
resolve: {}
};
works as expected
$scope.opts = {
backdrop: true,
backdropClick: true,
dialogFade: false,
keyboard: true,
templateUrl : '/app/reports/modalContent.html',
controller : 'ModalInstanceCtrl',
resolve: {}
};
Sounds like if you put wrong templateUrl, it by default uses main page HTML.
Be sure that you have right path for templateUrl
Hope it will help,
Have you tried to declare a dependency of 'ui.bootstrap' module? Like this:
angular.module('myApp.controllers', ['ui.bootstrap'])
Happened to me today, too. The templateUrl in the controller must match the id for the modal in the html file.
You need to define the newTerms controller before your other controller. Or you can change the code and just create a function inside your main controller with the name newTerms and remove the quotation marks for the name of your controller in your modal.
$scope.addTerm = function () {
var newTerm = $modal.open({
templateUrl: './views/partials/newTermModal.jade',
controller: newTerms,
resolve: {
items: function () {
return $scope.items;
}
}
});
var newTerms = function($scope, $modalInstance, items){
$scope.items = items;
$scope.selected = {
item: $scope.items[0]
};
$scope.ok = function () {
$modalInstance.close($scope.selected.item);
};
$scope.cancel = function () {
$modalInstance.dismiss('cancel');
};
}