How to deal with modal controller outside AngularJs module? - angularjs

My app uses Angular UI to deal with modals and I think I found a problem, as below:
I have a controller that calls the modal controller:
angular.module('MyApp').controller('MainCtrl', function ($scope, $modal) {
$scope.openModal = function () {
var myModal = $modal.open({
animation: true,
templateUrl: 'ModalContent.html',
controller: ModalCtrl
});
};
});
I also have the modal controller in another file, as a simple function:
function ModalCtrl ($scope, $modalInstance) {
$scope.ok = function () {
$modalInstance.close();
};
$scope.cancel = function () {
$modalInstance.dismiss('cancel');
};
};
It's working well, but I think the modal controller have to be inside AngularJs module. The questions are:
Can I do it and keep the modal controller in a separated file?
It's a good practice keep the modal controller outside AngularJs module?
What is the best practice to reuse a modal controller in many pages?
Thanks a lot!

That object you're passing into $modal.open() can be registered as a shared service and the modal controller can be embedded right inside it.
angular.module('myApp').factory('myModalConfig', function() {
return {
animation: true,
templateUrl: 'ModalContent.html',
controller: function($scope, $modalInstance) {
$scope.ok = function () {
$modalInstance.close();
};
$scope.cancel = function () {
$modalInstance.dismiss('cancel');
};
}
};
});
Then just inject that along with the $modal service and pass it.
angular.module('MyApp').controller('MainCtrl', function ($scope, $modal, myModalConfig) {
$scope.openModal = function () {
var myModal = $modal.open(myModalConfig);
};
});
I go further than this myself and wrap the whole thing into a custom modal service factory, so I can just inject one thing and open modals like this: profileModals.editProfile(). But that's beyond the scope of this answer.

Related

Mocked function not found in AngularUI modal controller unit test

I'm using Angular-Bootstrap modals and have a basic controller like so:
.controller('DashboardHelpController', ['$scope', '$uibModal', function ($scope, $uibModal) {
var dhc = this;
dhc.open = function (size, resource) {
var modalInstance = $uibModal.open({
templateUrl: 'resourcePlayModal.html',
controller: 'ModalInstanceCtrl as mic',
size: size,
resolve: {
resource: function () {
return resource;
}
}
});
};
}])
It calls a standard modal instance controller:
.controller('ModalInstanceCtrl', ['$uibModalInstance', 'resource', function ($uibModalInstance, resource) {
this.resource = resource;
this.cancel = function () {
$uibModalInstance.dismiss();
};
}])
And here's my unit test, modeled after another SO post:
describe('Modal controller', function () {
var modalCtrl, scope, modalInstance;
beforeEach(module('MyApp'));
// initialize the controller and a mock scope
beforeEach(inject(function ($controller, $rootScope) {
scope = $rootScope.$new();
modalInstance = {
// create a mock object using spies
close: jasmine.createSpy('modalInstance.close'),
dismiss: jasmine.createSpy('modalInstance.dismiss'),
result: {
then: jasmine.createSpy('modalInstance.result.then')
}
};
modalCtrl = $controller('DashboardHelpController', {
$scope: scope,
$uibModalInstance: modalInstance
});
}));
it('should instantiate the mock controller', function () {
expect(modalCtrl).not.toBeUndefined();
});
it('should have called the modal dismiss function', function () {
scope.cancel;
expect(modalInstance.dismiss).toHaveBeenCalled();
});
});
The problem is that the cancel function isn't found on the scope:
Expected spy modalInstance.dismiss to have been called with [ 'cancel'
] but it was never called.
UPDATE: I had originally attempted to call cancel as a function:
it('should have called the modal dismiss function', function () {
scope.cancel();
expect(modalInstance.dismiss).toHaveBeenCalled();
});
That didn't work. My code above is an attempt to solve the original problem:
TypeError: scope.cancel is not a function
Things are complicated a bit my my use of the Controller as syntax, but this should work. Thanks for any help.
scope.cancel is a function, but you aren't invoking it as such.
it('should have called the modal dismiss function', function () {
scope.cancel();
expect(modalInstance.dismiss).toHaveBeenCalledWith();
});
In addition, scope.cancel() is never defined on DashboardHelpController, even though that is the scope that you create for your tests. You need to create a method on your dashboard controller that calls through to the modal instance close method after the modalInstance has been created.
var modalInstance = $uibModal.open({
...
dhc.cancel = function () {
modalInstance.dismiss();
}

Modal Instance not resolving input

I am trying to use AngularUI modal's and I cannot seem to figure out why it is not resolving my variables.
Function that opens the modal and the modal instance
$scope.openModal = function (size, cert) {
var modalInstance = $modal.open({
template: 'ModalContent.html',
controller: 'ModalInstanceCtrl',
size: size,
resolve: {
certs: function () {
return $scope.certification;
}
}
});
modalInstance.result.then(function () {});
};
Modal Controller some stuff in here is leftover from debugging
angular.module('myApp').controller('ModalInstanceCtrl', ['$scope', '$filter', '$modalInstance', function ($scope, $filter, $modalInstance, certs) {
console.log(certs);
var results = $filter('filter')(certs, {id: id})[0];
$scope.cert = results;
$scope.ok = function () {
$modalInstance.close();
};
}]);
The main issue is that when it gets into the controller I am getting undefined for certs even though it should be resolved by the openModal function. I was following the official angular UI tutorial on how to do them here: Angular UI Bootstrap Modals
In your injection of 'certs' into your controller, you need to add it to the name declaration as well as the function.
angular.module('myApp').controller('ModalInstanceCtrl', ['$scope', '$filter', '$modalInstance', 'certs', function ($scope, $filter, $modalInstance, certs) {

Working with $emit and $on from child modal to parent angularjs

I have this situation
two files, both in the same app
var app = angular.module('myapp');
file one is the parent and I have:
app.controller("ControllerOne", ['$scope', '$http', '$modal',
function ($scope, $http, $modal) {
$scope.$on('refreshList', function (event, data) {
console.log(data);
});
$scope.openModal = function () {
var modalInstance = $modal.open({
templateUrl: '/SomeFolder/FileWithControllerTwo',
controller: 'ControllerTwo',
size: 'lg',
resolve: {
someParam: function () {
return "param"
}
}
});
}
}]);
file two is the child and I have:
app.controller("ControllerTwo", ['$scope', '$http', 'someParam',
function ($scope, $http, someParam) {
$scope.SaveSomething = function () {
$http.post(url, obj)
.success(function (data) {
$scope.$emit('refreshList', [1,2,3]);
}).error(function () {
});
};
}]);
Assuming that i can open the modal and I can "SaveSomething".
What I need to do to send some data from ControllerTwo to ControllerOne?
I already checked this post Working with $scope.$emit and .$on
but I cant't solve the problem yet.
Obs:
FileOne.js -> I have the ControllerOne (parrent) -> $on
FileTwo.js -> I have the ControllerTwo (child) -> $emit
Yes, I can hit the code inside $http.post.success condition
Assuming you are using angular-ui bootstrap (which has a $model), then the $scope in the model is a childscope of $rootScope.
According to $model documentation you can supply the ControllerOne $scope by using the scope option which will make the modal's $scope a child of whatever you supply. Thus:
var modalInstance = $modal.open({
templateUrl: '/SomeFolder/FileWithControllerTwo',
controller: 'ControllerTwo',
size: 'lg',
scope: $scope,
resolve: {
someParam: function () {
return "param"
}
}
});
Then you could emit to that using $scope.$parent.$emit(...). Strictly speaking, this creates a coupling in that it assumes that the user of the modal listens to the events.
If you don't want to inject your scope, they you could inject $rootScope and emit on that. But that would also send the event to every scope in the application.
This is assuming that you actually want to leave the modal open and send a message back to the parent controller. Otherwise, just use close() or dismiss() methods.

Calling another controller within AngularJS UI Bootstrap Modal

I have a completely working version of the ui.bootstrap.modal as per the example here http://angular-ui.github.io/bootstrap/#/modal but I want to take a stage further and add it to my controller configuration.
Perhaps I'm taking it too far (or doing it wrong) but I'm not a fan of just
var ModalInstanceCtrl = function ($scope...
My modal open controller:
var controllers = angular.module('myapp.controllers', []);
controllers.controller('ModalDemoCtrl', ['$scope', '$modal', '$log',
function($scope, $modal, $log) {
$scope.client = {};
$scope.open = function(size) {
var modalInstance = $modal.open({
templateUrl: 'templates/modals/create.html',
controller: ModalInstanceCtrl,
backdrop: 'static',
size: size,
resolve: {
client: function () {
return $scope.client;
}
}
});
modalInstance.result.then(function (selectedItem) {
$log.info('Save changes at: ' + new Date());
}, function () {
$log.info('Closed at: ' + new Date());
});
};
}
]);
Modal instance controller:
var ModalInstanceCtrl = function ($scope, $modalInstance, client) {
$scope.client = client;
$scope.save = function () {
$modalInstance.close(client);
};
$scope.cancel = function () {
$modalInstance.dismiss('cancel');
};
};
However I would like to change this last controller to:
controllers.controller('ModalInstanceCtrl', ['$scope', '$modalInstance', 'client',
function ($scope, $modalInstance, client) {
$scope.client = client;
$scope.save = function () {
$modalInstance.close(client);
};
$scope.cancel = function () {
$modalInstance.dismiss('cancel');
};
}
]);
If I also update the controller reference within $scope.open for ModalDemoCtrl controller to
controller: controllers.ModalInstanceCtrl
then there are no errors but the save and cancel buttons within the modal window no longer work.
Could someone point out where I am going wrong - possibly a fundamental lack of understanding of the way controllers work within the AngularJS?!
Controller specified in $scope.open needed single quotes around it.
controller: 'ModalInstanceCtrl',
You are referencing your module to variable controllers.
All controllers in angular systems has unique names.
The controller is still "ModalInstanceCtrl" not "controllers.ModalInstanceCtrl".

AngularUI Bootstrap Modal Open event

I'm invoking a bootstrap modal dialog through a link.
I want to start a timer in the angular controller when the dialog pops up. How do I detect the dialog open event in the angular controller to start the timer?
If I start timer in the scope like this,
app.controller('myctrl',
['$scope', '$window', '$timeout', 'svc',
function ($scope, $window, $timeout, svc) {
$scope.countdown = 10;
$scope.runCounter = function () {
$scope.countdown -= 1;
if ($scope.countdown > 0)
$timeout($scope.runCounter, 60000);
}
$scope.runCounter();
}]);
the timer starts when the application starts. I want the timer to start only when the dialog opens.
Thanks.
Check this out.
var modalInstance = $modal.open({...});
modalInstance.opened.then(function() {
alert("OPENED!");
});
The $modal.open() returns an object that, among other properties contains the opened promise, to be used as above.
I assume that you are using modals from http://angular-ui.github.io/bootstrap/.
If you look closely you will see that the component expose a promise that will be resolved when the dialog is opened. Which is what you will need to use. You can do something like that in the controller where the modal is created:
$scope.runCounter = function () {
$scope.countdown -= 1;
if ($scope.countdown > 0)
$timeout($scope.runCounter, 60000);
}
//Creating the dialog
var modalInstance = $modal.open({
templateUrl: 'myModalContent.html',
controller: ModalInstanceCtrl
}
});
//Add a function for when the dialog is opened
modalInstance.opened.then(function () {
$scope.runCounter
});
See working plunker here
var modalInstance = $modal.open({
templateUrl: '../augustine_app/templates/program_purchase_popup.html',
backdrop: 'static',
controller: function ($scope, $modalInstance) {
$scope.cancel = function () {
$modalInstance.dismiss('cancel');
};
}
});
if (navigator.userAgent.match(/iPhone|iPad|iPod/i)) {
modalInstance.opened.then(function () {
var modal;
var getModalInterval = function () {
modal = document.getElementsByClassName('modal')[0];
if (modal) {
clearInterval(getModal);
modal.style.marginTop = window.screenTop + 'px';
modal.style.height = 'auto';
modal.style.position = 'absolute';
modal.style.overflow = 'visible';
}
};
modal = document.getElementsByClassName('modal')[0];
if (!modal) {
var getModal = setInterval(getModalInterval, 2000);
}
});
}
};
Unfortunatly the open.then(func) runs before the freaking modal is actually in the DOM. Hence the setInterval.
here is some non jQuery angular code.
For my case, I need to be able to detect when modal is opened inside the modal controller itself.
At first opened promise was resolved even though the modal hasn't been loaded in the DOM yet. By wrapping the call inside a $timeout, opened promise is now resolved after the modal is loaded to the DOM.
$modal.open({
templateUrl: 'modalTemplate.html',
controller: 'modalCtrl'
});
// inside modalCtrl
angular.controller('modalCtrl', ['$modalInstance', '$timeout', function($modalInstance, $timeout) {
$timeout(function() {
$modalInstance.opened.then(function() {
//do work
});
});
}]);

Resources