How do I add a reusable modal dialog in Angular? - angularjs

I'm new to Angular and attempting to implement this solution into my project.
It looks painfully easy, however, I'm trying to make this into a re-usable element so that I can call it from anywhere and just pass in the content to be shown (otherwise, what's the point?).
So, my specific question is: assuming I already have a controller that's bound to some DOM element and it has a feature that goes and fetches some factory driven $http call and upon the response I wish to notify the user via this dialog of something, how do I combine *this directive and *this controller with my existing one and how do I do it in a way that allows me to then use it again from a totally different controller?
Or is this perhaps a bad example for this use and should I be looking at a different one?

Compared to other options, below given the minimalist approach, using angular factory. See a sample snippet below.
Note: using Angular JS with UI Bootstrap - AngularUI.
Reusable modal view - ConfirmationBox.html
<div class="modal-header">
<h3 class="modal-title">{{title}}</h3>
</div>
<div class="modal-body">
{{message}}
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary btn-warn" data-ng-click="ok(); $event.stopPropagation()">OK</button>
<button type="button" class="btn btn-default" data-ng-click="cancel(); $event.stopPropagation()">Cancel</button>
</div>
Reusable module and shared factory, for handling the reusable modal dialog
angular.module('sharedmodule',['ui.bootstrap', 'ui.bootstrap.tpls'])
.factory("sharedService",["$q", "$modal", function ($q, $modal)
{
var _showConfirmDialog = function (title, message)
{
var defer = $q.defer();
var modalInstance = $modal.open({
animation: true,
size: "sm",
templateUrl: 'ConfirmationBox.html',
controller: function ($scope, $modalInstance)
{
$scope.title = title;
$scope.message = message;
$scope.ok = function ()
{
modalInstance.close();
defer.resolve();
};
$scope.cancel = function ()
{
$modalInstance.dismiss();
defer.reject();
};
}
});
return defer.promise;
}
return {
showConfirmDialog: _showConfirmDialog
};
}]);
Portion of your View, using the shared modal dialog
<a data-ng-click="showConfirm()">Go Back to previous page</a>
Controller of your view, opening your shared reusable modal dialog and handling notifications (Ok and Cancel)
var myModule = angular.module("mymodule", ['sharedmodule', 'ui.bootstrap', 'ui.bootstrap.tpls']);
myModule.controller('myController', ["$scope", "sharedService", "$window",
function ($scope, sharedService, $window)
{
$scope.showConfirm = function ()
{
sharedService.showConfirmDialog(
'Confirm!',
'Any unsaved edit will be discarded. Are you sure to navigate back?')
.then(function ()
{
$window.location = '#/';
},
function ()
{
});
};
}]);

Trying using 'ngDialog' library for popup and modal. Very good library. Later you can create a service which internally calls ngDialog functions. Later this service can be injected in your controllers for use. I have implemented this in one project.
The function in services can accept parameters for initialising the ngDialog modal.
Hope it helps :)

for making it better I would suggest you to modify the code to look something as below
Template:
<div class='ng-modal' ng-show='modalContent != null && modalContent != ""'>
<div class='ng-modal-overlay' ng-click='hideModal()'></div>
<div class='ng-modal-dialog' ng-style='dialogStyle'>
<div class='ng-modal-close' ng-click='hideModal()'>X</div>
<div class='ng-modal-dialog-content' ng-transclude></div>
<p>{{ modalContent }}</p>
</div>
</div>
Directive:
app.directive('modalDialog', function() {
return {
restrict: 'E',
scope: {
modalContent: '='
},
replace: true, // Replace with the template below
transclude: true, // we want to insert custom content inside the directive
link: function(scope, element, attrs) {
scope.dialogStyle = {};
if (attrs.width)
scope.dialogStyle.width = attrs.width;
if (attrs.height)
scope.dialogStyle.height = attrs.height;
scope.hideModal = function() {
scope.modalContent= null;
};
},
template: '...' // See below
};
});
and then use the code as below in template
<modal-dialog modal-content='modalMsg' width='750px' height='90%'></modal-dialog>
Once these changes are done you can write a function to set message in variable "modalMsg" and angular will take care of rest
Note: Code is pretty much the same as in the link, the only thing I have changed is the check to display the modal box

Related

Unable to call the controller in the angular ui bootstrap $modal

I want to generate a modal pop up on click of a button. I am using ui bootstrap of angular with $modal service.
Now, when I click on button, I am writing the following code.
$scope.modalOpen = function () {
var modalInstance = $modal.open({
templateUrl: 'views/includes/someModal.html',
controller : //here I want to use the same controller where the current function is getting called
});
};
I am unable to call the same controller. Am I making any mistake? I tried Google but no success :( Please suggest. Thanks in advance :)
I am assuming the whole reason you want to do this is so you can share data (state) between the modal and the current controller.
You can achieve this without having to share the controller, using the resolve field on the modal configuration.
function CurrentController($scope, $modal) {
$scope.list = [];
$scope.modalOpen = function () {
var modalInstance = $modal.open({
templateUrl: 'views/includes/someModal.html',
controller : 'SomeOtherController',
resolve: {
list: function() { return $scope.list; }
}
});
};
}
This means that list will be dependency injected into SomeOtherController.
function SomeOtherController($scope, list) {
$scope.list = list;
}
resolve (Type: Object) - Members that will be resolved and passed
to the controller as locals; it is equivalent of the resolve
property in the router.
See the docs for $modal in angular-ui bootstrap.
If you open your modal from your controller, you can attempt something like this :
var myCtrl = this;
$scope.modalOpen = function () {
var modalInstance = $modal.open({
templateUrl: 'views/includes/someModal.html',
controller : myCtrl
});
};
But I don't think it's a good practice to reuse a controller instance.
You probably don't want to make it use the same controller. Since the scope isn't isolated, your modal can call functions from your current controller. You can also have an inline controller like the following:
The reason why you couldn't use functions from your current scope is you would have to give the modal your current scope to do that. Be careful when you do this. Your modal can mess up your current object. I went ahead and created a "save" and "cancel" button so that you can undo the changes.
Html
<div ng-controller="simpleController">
{{ item.title }}
<button ng-click="open();">Click Me</button>
</div>
modal template
<script type="text/ng-template" id="views/includes/someModal.html">
<div class="modal-header">
<h3 class="modal-title">Title!</h3>
</div>
<div class="modal-body">
title: {{ item.title }}<br/>
Click the buttons below to change the title throught the controller's function
<button ng-click="fn('test');">test</button>
<button ng-click="fn('test 1');">test 1</button>
<button ng-click="fn('test 2');">test 2</button>
</div>
<div class="modal-footer">
<button ng-click="close(true);">Save</button>
<button ng-click="close();">Close</button>
</div>
</script>
javascript
angular.module("myApp", ['ui.bootstrap']).controller("simpleController", ["$scope", "$uibModal", function($scope, $uibModal){
$scope.item = { title: "test", stuff: "other stuff"};
$scope.fn = function(title){
$scope.item.title = title;
};
$scope.open = function () {
var intialItem = angular.copy($scope.item);
var modalInstance = $uibModal.open({
templateUrl: 'views/includes/someModal.html',
controller: ["$scope", function(scope){//add other functionality here
scope.close = function(save){
if(!save){
$scope.item = intialItem;
}
modalInstance.close();
}
}],
scope: $scope
}).result.then(function(){
}, function(){
$scope.item = intialItem;//modal was dismissed
});
};
}]);

Integrating directive in Angular UI Modal

In this plunk I have the following:
A control object shared between the controller and the directive.
The directive declares a method in the control object.
The controller invokes the method to set a value.
The directive is included in an Angular UI Modal.
For some reason the control object is empty when the modal is opened (look at the console log). How to invoke the method from the controller to set the field value?
HTML
<div ng-app="app" ng-controller="myCtl">
<button ng-click="openModal()">Open modal</button>
<script type="text/ng-template" id="myModalContent.html">
<div class="modal-header">
<h4 class="modal-title">The Title</h4>
</div>
<ddl control="ddlControl"></ddl>
<div class="modal-footer">
<button type="submit">Submit</button>
</div>
</script>
</div>
Javascript
var app = angular.module('app', ['ui.bootstrap']);
app.controller('myCtl', function($scope,$uibModal) {
$scope.ddlControl = {};
$scope.openModal = function() {
$scope.modalInstance = $uibModal.open({
templateUrl: 'myModalContent.html',
scope: $scope
});
console.log($scope.ddlControl);
$scope.ddlControl.set();
};
})
.directive('ddl', function () {
var directive = {};
directive.restrict = 'EA';
directive.scope = {
control: '='
};
directive.template = '<div>someValue: {{someValue}}</div>';
directive.link = function (scope, element, attrs) {
scope.control = scope.control || {};
scope.control.set = function() {
scope.someValue = 1;
};
};
return directive;
});
There is a race condition between opening the modal and running a digest of the modal HTML.
When the button is clicked $scope.openModal() is executed. The modal opens and gets into the digest phase. But javascript is not waiting until the digesting has been completed, so it continues executing $scope.openModal() until the end.
You need to handle the promise of $uibModal.open().rendered(). The uibModal resolves the rendered promise when it's done.
$scope.openModal = function() {
$scope.modalInstance = $uibModal.open({
templateUrl: 'myModalContent.html',
scope: $scope
}).rendered.then(function() {
console.log($scope.ddlControl);
$scope.ddlControl.set();
});
};
The $uibModal.open() function returns the following:
Object {result: Promise, opened: Promise, rendered: Promise}
In the promise block of rendered, you can safely make use of the fields that has been changed by the directive.
Plunker: http://plnkr.co/edit/GnIThstxkuR06felh8Pe?p=preview

UI Bootstrap Modal within Directive

I'm using Angular 1.4.1 and UI Bootstrap 0.13.
I have a directive from which I'm opening a modal. The modal opens fine, but the buttons seemingly don't get bound to their handlers - they don't do anything. I've used this same code in another project just fine, except not from within a directive.
My directive:
(function () {
var app = angular.module('myApp');
app.directive('someDirective', function () {
return {
restrict: 'E',
templateUrl: 'SomeDirective.html',
scope: {
list1: '=list1',
list2: '=list2',
save: '&'
},
controller: ['$scope','$modal','myService', function ($scope,$modal,myService) {
$scope.openModal = function () {
var modalInstance = $modal.open({
templateUrl: 'ModalTemplate.html',
controller: 'modalController',
backdrop: 'static',
size: 'sm',
resolve: {
saveData: function () {
//do save action
}
}
});
modalInstance.result.then(
function (itemToSave) {
//save item
},
function () {
//Cancel
}
);
};
}]
}
});
}());
The modal's controller:
(function() {
var app = angular.module('myApp');
app.controller('modalController', [
'$scope', '$modalInstance', 'saveData',
function($scope, $modalInstance, saveData) {
$scope.saveData = saveData;
$scope.save = function() {
$modalInstance.close($scope.saveData);
};
$scope.cancel = function() {
$modalInstance.dismiss('cancel');
};
}
]);
}());
And the template for the modal content:
<div class="modal-header bg-info">
<h3 class="modal-title">Add New Record</h3>
</div>
<div class="modal-body">
<form class="form-horizontal" role="form"></form>
</div>
<div class="modal-footer bg-info">
<button class="btn btn-primary" ng-click="save()">Save</button>
<button class="btn btn-warning" ng-click="cancel()">Cancel</button>
</div>
My thought is that I'm having issues with scope, but I can't track it down. I have put break points on modalController. The app.controller() call happens when the app loads, I've seen that. But breakpoints within save() and cancel() never get hit.
Can someone help me figure out why the modal's buttons don't do anything?
This turned out to be a stupid mistake. At some point I apparently overwrote the name of one of the other controllers in my project with the same name of the controller I was using for the modal. The other controller did not have save() or cancel() methods so nothing was happening. As soon as I fixed my error and all controllers once again had their proper names this started working again.

Angular UI Bootstrap modal inside ngRepeat

I am making an app, where I have a lot of input fields. Those input fields are generated from JSON object array field with AngularJS ngRepeat directive and have a button next to them which open an Angular UI Bootstrap modal to edit this value in a bigger textarea. I cannot figure out how to reference this model property to Angular UI Bootstrap so that I can save the changes made in modal. Since this functionality is needed in multiple views, I turned it into a service.
I have made a plunker to illustrate my problem.
http://plnkr.co/edit/ZrydC5UExqEPvVg7PXSq?p=preview
Currently in plunker example modal contains textarea, but I will actually need to use Text-Angular directive, because those fields contain some HTML markup and I would be easier for users to edit values with this nice addon.
TextAngular
EDIT: Please, if you are taking time to answer, you might aswell take a little more time to edit my plunker example to show exactly how your solution would look like, because seems that everyone who tries to help me, think they know the solution, but in reality it doesn't work :( Thanks!
I personally like to decorate my $scope with the services (i.e. $scope.modalService = ModalService;), so I understand the source of the logic. In the ng-repeat you then pass the value item into the method call:
<div class="input-group">
<input class="form-control" ng-model="value.value">
<span class="input-group-addon" ng-click="modalService.openTextEditModal(value)">
<span class="glyphicon glyphicon-align-justify"></span>
</span>
</div>
The modal service and modal template would then reference the object, in this case a clone of the object to help with state management, not the text:
app.factory('ModalService', function($modal) {
return {
openTextEditModal: function(item) {
var modalInstance = $modal.open({
templateUrl: 'modal.html',
backdrop: 'static',
controller: function($scope, $modalInstance, $sce, item) {
var clone = {};
angular.copy(item, clone);
$scope.clone = clone;
$scope.close = function() {
$modalInstance.dismiss('cancel');
};
$scope.save = function() {
angular.extend(item, clone);
$modalInstance.close();
};
},
size: 'lg',
resolve: {
item: function() {
return item;
}
}
});
}
};
});
With the corresponding modal template changes:
<div class="modal-header">
<h3 class="modal-title">Edit text</h3>
</div>
<div class="modal-body">
<textarea class="form-control" ng-model="clone.value"></textarea>
</div>
<div class="modal-body">
<button class="btn btn-warning" ng-click="close()">Close</button>
<button class="btn btn-success pull-right" ng-click="save()">Save</button>
</div>
It might be easier to make a controller for your modal and pass in the objects that you need from your scope. Those will be passed by reference so changes to them will update the scope of your parent scope. Something like this in your MainCtrl :
var modalInstance = ModalService.open({
templateUrl: 'modal.html',
controller: 'YourModalController',
size: 'lg',
resolve: {
text: function () {
return $scope.editText;
}
}
});
modalInstance.result.then(function () {
});
And then in your modal controller:
app.controller('YourModalController', ['$scope', '$modalInstance', 'text', function YourModalController($scope, $modalInstance, text) {
$scope.text = text;
$scope.close = function() {
$modalInstance.dismiss('cancel');
};
$scope.save = function() {
$modalInstance.close($scope.text);
};
}]);
And if you want it to be reusable so you do not have to duplicate the modal instance code in the parent controller you could make that a directive.
You can return the promise and then handle the success callback in the controller.
In the openTextEditModal function, return modalInstance.result;.
Then, in the controller you can do this:
$scope.editText = function(text){
ModalService.openTextEditModal(text).then(function(newText){
console.log(newText);
$scope.text = newText; // do something with the new text
});
};

angular directive scope missunderstanding

Well, I want to create a "summernote" directive (wysiwyg editor).
This is the template:
<summernote active="false">
<button class="edit" ng-click="edit()">Edit</button>
<button class="save" ng-click="saveData()">Save</button>
<button class="cancel" ng-click="cancel()">Cancel</button>
<div class="summernote"></div> // here will be loaded the summernote script
</summernote>
Directive code:
...
.directive('summernote', function($compile) {
return {
restrict: 'E',
replace: true, // Not sure about what this code does,
scope: {
active: '='
},
link: function($scope, elem, attrs) {
var $summernote = elem.find('.summernote'),
$edit = elem.find('.edit'),
$save = elem.find('.save'),
$cancel = elem.find('.cancel');
$scope.active = false;
$scope.$watch('active', function(active) {
// switch summernote's & buttons' state
// code ...
});
// here I have the buttons' click event defined
// QUESTION 1: Is there a better way?
// I'm doing this because the code below is not working.
$edit.on('click', function() {...});
$cancel.on('click', function() {...});
$save.on('click', function() {...});
// THIS IS NOT WORKING...
$scope.edit = function() {
alert('edit');
};
$scope.cancel = function() {
alert('cancel');
};
}
}
});
When I click save button, I want to send some data, so I have declared saveData on the mainController, but I have to send the div.summernote data and I don't know how to do
<button class="save" ng-click="saveData(getSummernoteDataFromDirectiveScope?)">Save</button>
MainController:
.controller('MainController', function($scope, myDataFactory) {
$scope.saveSummernoteData(data) {
myDataFactory.updateData('field_name', data);
}
}
The main question is, how to works with different? scopes. The thing is that I want to separate the directive logic (edit, cancel, div.summernote behaviour), and the "save" button, which its logic is declared in the MainController (or main $scope, here is my mess).
Are the $scope of the link function and the MainController $scope the same??
I think I have a little mess with all of this, so any help (documentation) would be appreciated.
Documentation can be found here directives and here compile.
Replace: true, would replace the element you have attached the directive to with your template, because in your case your template seems to be inline with the code you don't need that (also it will be removed in the next major release of angular).
Question 1: You shouldn't need to bind $on events ng-click should just work.
If you want to define your save function inside your controller you can pass your function as an attribute and call it inside your save routine defined in your directive:
In your html:
<summernote active="false" on-save="saveData()">
<button class="edit" ng-click="edit()">Edit</button>
<button class="save" ng-click="save()">Save</button>
<button class="cancel" ng-click="cancel()">Cancel</button>
<div class="summernote"></div> // here will be loaded the summernote script
</summernote>
Inside your directive:
scope: {
active: '=',
invokeOnSave: '&onSave'
},
link: function($scope, elem, attrs) {
...
$scope.save = function() {
var data = "some data"; //Whatever mechanism you use to extract the data from your div
$scope.invokeOnSave(data);
};
...
}

Resources