ng-model two way binding not working with ui bootstrap modal - angularjs

I have following code for working with ui bootstrap modal. I am having a input field whose value has to be captured on the controller. But the input field value is not getting reflected on the controller after an value is entered on the modal.
angular.module('myApp')
.controller('mainController', ['$scope', '$modal', function($scope, $modal) {
$scope.openModal = function() {
$modal.open({
templateUrl: 'modal.html',
controller: BoardController
});
};
}])
.directive('modalDialog', function() {
return {
restrict: 'E',
replace: true,
transclude: true,
template:
'<div class="modal-content">' +
'<div class="modal-header">' +
'<h4 ng-bind="dialogTitle"></h4>' +
'</div>' +
'<div class="modal-body" ng-transclude></div>' +
'<div class="modal-footer">' +
'<button type="button" class="btn btn-default" ' +
'ng-click="cancel()">Close</button>' +
'<button type="button" class="btn btn-primary" ' +
'ng-click="ok()">Save</button>' +
'</div>' +
'</div>'
};
});
var BoardController = function ($scope, $modalInstance) {
$scope.dialogTitle = 'Create new item';
$scope.placeholder = 'Enter item name';
$scope.inputname = '';
$scope.ok = function () {
console.log($scope.inputname);
$modalInstance.dismiss('cancel');
};
$scope.cancel = function () {
$modalInstance.dismiss('cancel');
};
};
In 'modal.html' i have the following code:
<modal-dialog>
<input type="text" class="form-control" id="enter-name"
ng-model="inputname" placeholder={{placeholder}}>
{{ inputname }}
</modal-dialog>
So, after entering some text into the inputfield when i click the save the following line under $scope.ok() prints blank.
console.log($scope.inputname);
I guess this has something to do with scopes or may be transclusion. But i am not able to figure out whats causing this. I couldnt find the updated value in developer console also.

The problem here is transclusion. ngTransclude directive creates one more scope, but it is a sibling scope. Using transclusion makes it very difficult to access your scope. In your case you could retrieve model value like this:
$scope.ok = function () {
console.log($scope.$$childHead.$$nextSibling.inputname);
$modalInstance.dismiss('cancel');
};
But of course this is terrible. Fortunately, you can control what scope transclusion will use for rendered template if you make transclusion manually. For this you need to use link function with the fifth argument which is transclude function.
Your directive will become (note, that you don't use ng-tranclude directive in template anymore):
.directive('modalDialog', function() {
return {
restrict: 'E',
replace: true,
transclude: true,
template:
'<div class="modal-content">' +
'<div class="modal-header">' +
'<h4 ng-bind="dialogTitle"></h4>' +
'</div>' +
'<div class="modal-body"></div>' +
'<div class="modal-footer">' +
'<button type="button" class="btn btn-default" ng-click="cancel()">Close</button>' +
'<button type="button" class="btn btn-primary" ng-click="ok()">Save</button>' +
'</div>' +
'</div>',
link: function(scope, element, attrs, controller, transclude) {
transclude(scope, function(clone) {
var modalBody = element[0].querySelector('.modal-body');
angular.element(modalBody).append(clone);
});
}
};
});
Demo: http://plnkr.co/edit/I7baOyjx4pKUJHNkxkDh?p=preview

Related

Angular Directive: Button Spinner

I have created a directive, its behave like when you click on button it will show spinner or loader, its show user there is something in progress like API call or moving to another page, I am facing following issues:
If more than one directive used on page, its show spinner for both button instead clicked button, i want it should show only for clicked button
In some scenarios, clicked event bind three times, as there are three span used inside button to show spinner, there is some issue with event propagation
Here is directive code:
(function () {
'use strict';
angular
.module('app.base')
.directive('buttonSpinner', directiveFunction)
.controller('btnController', ControllerFunction);
// ----- directiveFunction -----
directiveFunction.$inject = [];
/* #ngInject */
function directiveFunction() {
var directive = {
restrict: 'E',
scope: {
label: "#",
available: "="
},
controller: 'btnController',
controllerAs: 'vm',
replace: true,
transclude: true,
template:
'<button ng-disabled="vm.isDisabled">'
+ '<span ng-hide="vm.isClicked">{{label}}</span>'
+ '<div class="spinner" ng-show="vm.isClicked">'
+ '<span class="bounce1"></span>'
+ '<span class="bounce2"></span>'
+ '<span class="bounce3"></span>'
+ '</div>' +
'</button>'
};
return directive;
}
// ----- ControllerFunction -----
ControllerFunction.$inject = [ '$scope' ];
/* #ngInject */
function ControllerFunction( $scope ) {
var vm = this;
vm.isClicked = false;
$scope.$on('APICALLED', function(event, data){
vm.isClicked = data.done;
if( data.elem ) {
angular.element(document.getElementById(data.elem))[0].disabled = true;
} else {
vm.isDisabled = data.disable;
}
});
}
})();
How to use:
In View:
<button-spinner class="btn btn-primary btn-block btn-lg" type="submit" ng-click="vm.notifyMe($event)" label="Notify Me" available="vm.notify.is" id="notifyMeBtn"></button-spinner>
In Controller:
to show spinner:
$rootScope.$broadcast('APICALLED', {'done': true, 'disable': true});
to hide spinner:
$rootScope.$broadcast('APICALLED', {'done': false, 'disable': false});
Your problem is related to your $broadcast's so its a pattern problem. $rootScope.$broadcast() isnt a good solution at all. e.g.) You need to destroy $rootScope.$broadcast.$on() bindings manually. Just parse a unique scope var into the directive like loading. This scope param could be handled by the controller itself for each loading procedure:
/* #ngInject */
function directiveFunction() {
var directive = {
restrict: 'E',
scope: {
label: "#",
available: "=",
loading: "="
},
controller: 'btnController',
controllerAs: 'vm',
replace: true,
transclude: true,
template:
'<button ng-disabled="vm.isDisabled">'
+ '<span ng-hide="vm.isClicked">{{label}}</span>'
+ '<div class="spinner" ng-show="vm.loading">'
+ '<span class="bounce1"></span>'
+ '<span class="bounce2"></span>'
+ '<span class="bounce3"></span>'
+ '</div>' +
'</button>'
};
return directive;
}
View
<button-spinner class="btn btn-primary btn-block btn-lg"
type="submit"
ng-click="vm.notifyMe($event)"
loading="vm.isLoading"
label="Notify Me"
available="vm.notify.is"
id="notifyMeBtn"></button-spinner>

AngularJS - scope conflict because of multiple directives

I have a conflict between my directive and the uib-datepicker-popup.
I´m getting a
Error: [$compile:multidir] Multiple directives [input (module: myModule), uibDatepickerPopup (module: ui.bootstrap.datepicker)] asking for new/isolated scope
. Here´s my Code:
module.run(function($templateCache){
if (!$templateCache.get('input.html')){
$templateCache.put('input.html',
'<div>' +
'<span>' +
'<button type="button" ng-click="open = true">' +
'<span class="glyphicon glyphicon-calendar"></span>' +
'</button>' +
'</span>' +
'<input type="text" class="form-control" placeholder="TT.MM.JJJJ" ng-model-options="{allowInvalid: true}" is-open="open" />' +
'</div>');
}
});
and
module.directive('input', function ($interpolate, $templateCache) {
return {
restrict: 'E',
scope: true,
controller: 'someController',
compile: function (tElem, tAttr) {
var content = angular.element($interpolate($templateCache.get('/input.html'))());
var input = content.find('input');
_.each(tAttr.$attr, function (attributeName, property) {
input.attr(attributeName, tAttr[property]);
});
tElem.replaceWith(content);
}
};
});
In the view, it´s:
<input ng-model="myModel" uib-datepicker-popup></input>
The uib-datepicker-popup is causing the error. I guess it is creating its own scope, that is in conflict with the scope of the dierective. But I need the directives scope to be true. So what can I do?

How to reload the page in ui-router

i have a delete button, for the confirmation from the user, i am using directive for modal.
this is the directive code
app.directive('modal', function() {
return {
template: '<div class="modal fade">' + '<div class="modal-dialog">' + '<div class="modal-content">' + '<div class="modal-header">' + '<button type="button" class="close" data-dismiss="modal" aria-hidden="true" ng-click="close()">×</button>' + '<h4 class="modal-title">{{ title }}</h4>' + '</div>' + '<div class="modal-body" ng-transclude></div>' + '</div>' + '</div>' + '</div>',
restrict: 'E',
transclude: true,
replace: true,
scope: true,
link: function postLink(scope, element, attrs) {
scope.title = attrs.title;
scope.$watch(attrs.visible, function(value) {
if (value == true)
$(element).modal('show');
else
$(element).modal('hide');
});
$(element).on('shown.bs.modal', function() {
scope.$apply(function() {
scope.$parent[attrs.visible] = true;
});
});
$(element).on('hidden.bs.modal', function() {
scope.$apply(function() {
scope.$parent[attrs.visible] = false;
});
});
}
};
});
this is the controller code
$scope.deletePlace = function(place) {
if (place._id) {
var url = '/api/places/' + place._id;
$http.delete(url, {})
.then(function(response) {
$scope.showModal = false;
$state.transitionTo('dashboard.places.list', null, { reload: true, inherit: true, notify: true });
}, function(response) { // fail
$scope.errorMessage = true;
});
}
}
after clicking ok button on delete modal, the modal will hide, but the black screen remains same and the buttons on page or not clickable, untill i refresh the page manually. Is there any way to remove the black screen after clicking ok button on confirmation modal. If i click the refresh manually, it will work. i don't want to refresh it manually. i want it to be refreshed automatically or from other way to hide the black screen.
Try $state.reload() It should work in most cases if you're using latest version of ui-router.
If that doesn't work try routing to the same page using $state.go() or $state.transitionTo() with additional parameter as {reload: true}.
If that also doesn't work create a auto executing method to initialize your controller and call that method again.
activate();
function activate(){
}

How to call directive template on click?

I am trying to make a reusable modal dialog and I would like to load directive template on click directive itself..
function modalDialog() {
var directive = {
restrict: 'A',
link: linkFunc,
template: '<div class="modalBox--blur">' +
'<div class="modalBox">' +
'<h3>' {{title}} '</h3>' +
'<h4>' {{text}} '</h4>' +
'<button ng-click="answer(true)">Cancel</button>' +
'<button ng-click="answer(false)">Ok</button>' +
'</div>' +
'</div>',
scope: {
title: '=dialogTitle',
text: '=dialogTxt'
},
transclude: true
};
return directive;
function linkFunc($scope, element, attrs) {
element.on('click', function () {
$scope.newEl = element.parent();
$scope.newEl.append(...template Here...???);
});
}
}
This is how directive is set in the view:
<button
modal-dialog
dialog-title="modalBox.title"
dialog-txt="modalBox.subText"
type="button"
ng-click="deleteSth()"
class="button">
</button>
I can't figure out how to load template on element click :
element.on('click', function () {
$scope.newEl = element.parent();
$scope.newEl.append(template????);
});
Any tips?
Thank you in advance!
You can get the template with $templateCache
Like $templateCache.get('templateId.html')
Solution was compiling the template:
scope.modal = $compile(' <div class="modalBox--blur">' +
'<div class="modalBox">' +
'<h3>{{title}}</h3>' +
'<h4>{{text}}</h4>' +
'<button ng-click="dialogAnswer(true)">Annuleren</button>' +
'<button ng-click="dialogAnswer(false)">Ok</button>' +
'</div>' +
'</div>')(scope);
element.on('click', function () {
scope.newEl = element.parent();
scope.newEl.append(scope.modal);

Angular directive check element?

I'm hooking up a $modal service for confirmation boxes in my app and made a directive that only works for ng-click. Well I also need it to work for ng-change so I did it like the following:
.directive('ngConfirmClick', ['$modal',
function($modal) {
var ModalInstanceCtrl = function($scope, $modalInstance) {
$scope.ok = function() {
$modalInstance.close();
};
$scope.cancel = function() {
$modalInstance.dismiss('cancel');
};
};
return {
restrict: 'A',
scope:{
ngConfirmClick:"&",
item:"="
},
link: function(scope, element, attrs) {
element.bind('click', function() {
var message = attrs.ngConfirmMessage || "Are you sure ?";
if(element == 'select'){
var modalHtml = '<div class="modal-body">' + message + '</div>';
modalHtml += '<div class="modal-footer"><button class="btn btn-success" ng-model="" ng-change="ok()">OK</button><button class="btn btn-warning" ng-change="cancel()">Cancel</button></div>';
} else {
var modalHtml = '<div class="modal-body">' + message + '</div>';
modalHtml += '<div class="modal-footer"><button class="btn btn-success" ng-click="ok()">OK</button><button class="btn btn-warning" ng-click="cancel()">Cancel</button></div>';
}
var modalInstance = $modal.open({
template: modalHtml,
controller: ModalInstanceCtrl
});
modalInstance.result.then(function() {
scope.ngConfirmClick({item:scope.item});
}, function() {
});
});
}
}
}
]);
You can see I'm trying to check if the element is a 'select' element but I'm not sure how angular's link method/function reads the element. Can I check it with a string like how I did it? (It doesn't work when I try this btw).
How can I check if the element I'm attaching my directive to is a select?
Angular's jqLite is a subset of jQuery and that is the element parameter passed into the link function (unless you load the full jQuery library, then it will be a jQuery object). As described in this post using element.prop('tagName') will return the element type which is a method included in the jqLite library.
So I got confused and the if statement should of been at the element.bind not at the var modalHtml...
Here's the updated code for me to get this to work with both ng-change and ng-click. I just added bind on click and bind on change with an if statement to check the element.context.tagName was select or not
directive('ngConfirmClick', ['$modal',
function($modal) {
var ModalInstanceCtrl = function($scope, $modalInstance) {
$scope.ok = function() {
$modalInstance.close();
};
$scope.cancel = function() {
$modalInstance.dismiss('cancel');
};
};
return {
restrict: 'A',
scope:{
ngConfirmClick:"&",
item:"="
},
link: function(scope, element, attrs) {
console.log(element.context.tagName);
if(element.context.tagName == 'SELECT'){
element.bind('change', function() {
var message = attrs.ngConfirmMessage || "Are you sure ?";
var modalHtml = '<div class="modal-header"><h4 id="title-color" class="modal-title"><i class="fa fa-exclamation"></i> Please Confirm</h4></div><div class="modal-body">' + message + '</div>';
modalHtml += '<div class="modal-footer"><button class="btn btn-primary" ng-click="ok()">OK</button><button class="btn btn-warning" ng-click="cancel()">Cancel</button></div>';
var modalInstance = $modal.open({
template: modalHtml,
controller: ModalInstanceCtrl
});
modalInstance.result.then(function() {
scope.ngConfirmClick({item:scope.item});
}, function() {
});
});
} else {
element.bind('click', function() {
var message = attrs.ngConfirmMessage || "Are you sure ?";
var modalHtml = '<div class="modal-header"><h4 id="title-color" class="modal-title"><i class="fa fa-exclamation"></i> Please Confirm</h4></div><div class="modal-body">' + message + '</div>';
modalHtml += '<div class="modal-footer"><button class="btn btn-primary" ng-click="ok()">OK</button><button class="btn btn-warning" ng-click="cancel()">Cancel</button></div>';
var modalInstance = $modal.open({
template: modalHtml,
controller: ModalInstanceCtrl
});
modalInstance.result.then(function() {
scope.ngConfirmClick({item:scope.item});
}, function() {
});
});
}
}
}
}
]);

Resources