How to handle click events from inside of the directive? - angularjs

I have a modal component that takes an object with binding (ng-model). Something like:
<modal ng-model="modals.createContact"></modal>
I'm checking for $ctrl.ngModel.show to show/hide the modal:
<div class="modal" ng-show="$ctrl.ngModel.show" ng-transclude></div>
I show/hide modal by setting modals.createContact.show using ng-click directive:
<button ng-click="modals.createContact.show = true"></button>
But this solution is hard to maintain.
I need a directive something like this to toggle modal's show property:
<button modal="modals.createContact">Toggle modal</button>
Directive should listen the click event of the element (button) then toggle the $ctrl.modal.show property.
What I mean with toggling is:
$ctrl.modal.show = !$ctrl.modal.show;
How can achieve this scenario using directives?

To handle click events inside a directive be sure to use $apply:
app.directive("myDirective", function() {
return {
link: postLink
}
function postLink(scope, elem, attrs) {
elem.on("click", function(ev) {
scope.$apply(function() {
//code here
});
});
}
})
AngularJS modifies the normal JavaScript flow by providing its own event processing loop. This splits the JavaScript into classical and AngularJS execution context. Only operations which are applied in the AngularJS execution context will benefit from AngularJS data-binding, exception handling, property watching, etc... You can also use $apply() to enter the AngularJS execution context from JavaScript.
For more information, see
AngularJS Developer Guide - Integration with the browser event loop

Related

How can I convert custom jquery event listener into angularjs listener?

I have created angularjs component. I wrote a custom event listener in jquery, event is triggered by non-angularjs library.
$( "#myDiv" ).on( "CornerstoneImageRendered", function(e) {
// buisness logic
});
myDiv is a div which is part of angularjs component.
I want to write this listener into angularjs component. How can I do it?
PS: Event link https://github.com/cornerstonejs/cornerstone/wiki/CornerstoneImageRendered-Event
Create a custom directive:
app.directive("myEventListener", function() {
return {
link: postLink
};
function postLink(scope, elem, attrs) {
elem.on( "CornerstoneImageRendered", function(event) {
// business logic
scope.$eval(attrs.myEventListener, {$event: event});
scope.$apply();
});
}
});
Usage
<div id="myDiv" my-event-listener="onMyEvent($event)">
</div>
JS
$scope.onMyEvent = function(event) {
console.log(event);
};
For more information, see AngularJS Developer Guide - Creating Custom Directives
If you're going to listen on events from an external, non angularjs, library you need to notify angularjs when a change has happend.
One way to do this is by wrapping your logic, which is triggered by the external event, in the $scope.$apply method. The $apply method will
notify angularjs that a change has happend and triggered the digest loop which will sync change from the scope to the view.
Add the event inside the controller and try it like this
$("#myDiv").on("CornerstoneImageRendered", function(e) {
$scope.$apply(function () {
// buisness logic on the scope
});
});
Here's an great artile on the subject
To archive what you want first you need use
angular.element and then you can add event listener

AngularJS parent div directive wait for children div stacked directives to complete

I have a portion of html that is like this
<div my-custom-security-directive>
<button ng-if={{some constraints}} ng-click={{logic}}>cancel</button>
<button ng-disabled={{some logic}} ng-click={{some logic}}>submit<button>
</div>
My custom security directive does dom manipulation to the buttons to show/hide them via css. The issue I am having is with timing and perhaps directive precedence? When all the code is finished executing I only see the submit button and not the cancel button. I believe this is because of the ng-if and I attempted to set the priority of my custom directive to a negative number to run last but I think that is only for stacked directives on the same element? I only have a link function defined in my directive which my understanding is that is the same as a post link function and should run once the children complete? Ultimately, my goal is to run my directive 'last' so that I can show both buttons if all the logic in the directive passes.
The shell of my directive :
angular.module('myModule')
.directive("myCustomSecurityDirective", function(a,b) {
//
return {
restrict: "A",
priority: -1,
link: function(scope, element, attrs, ctrl) {
//custom security role/perm logic using injected services a&b etc
if (userHasPermission ) {
element.find('input, textarea, button').addClass('my-show-css');
}
}
};
});
I did recently /today put that priority on the directive but I don't think it does anything in this particular scenario.
Even if my-custom-security-directive is able to attach a CSS class to the button and hide or show it, the button has its own ng-if condition. This means it's possible that the button could be destroyed and recreated later, and it wouldn't have the CSS class anymore. If the button uses ng-show instead of ng-if you may have more control, since the button would become hidden but remain in the DOM.
But I think my-custom-security-directive might want to have more control. You can use transclusion so that my-custom-security-directive acts as a container for each set of elements which should be destroyed or created based on userHasPermission.
Directive:
.directive('myCustomSecurityDirective', function () {
return {
transclude: true,
link: function (scope) {
scope.userHasPermission = true;
},
template: '<div ng-if="userHasPermission" ng-transclude></div>'
}
});
HTML:
<div my-custom-security-directive>
<button ng-if="...">cancel</button>
<button ng-disabled="...">submit</button>
</div>

how to make custom directive in angular?

I am trying to make custom directive in angular .I try to add input field in my view when I click on button .In other words I am trying to make one custom directive in which when user press the button it add one input field in the browser .I think it is too easy if I am not use custom directive Mean If I use only controller then I take one array and push item in array when user click on button and button click is present on controller.
But when need to make custom directive where I will write my button click event in controller or directive
here is my code
http://play.ionic.io/app/23ec466dac1d
angular.module('app', ['ionic']).controller('appcontrl',function($scope){
$scope.data=[]
}).directive('inputbutton',function(){
return {
restrict :'E',
scope:{
data:'='
},
template:'<button>Add input</button> <div ng-repeat="d in data"><input type="text"></div>',
link:function(s,e,a){
e.bind('click',function(){
s.data.push({})
})
}
}
})
I just need to add input field when user click on button using custom directive ..could you please tell me where i am doing wrong ?
can we make button template and click event inside the directive
The reason it doesn't work is because your registering your click handler with jQuery. So when the click handler fires it is out of the scope of angular so angular does not know it needs to update its bindings.
So you have two options, the first is to tell angular in the click handler, 'yo buddy, update your bindings'. this is done using $scope.$apply
$apply docs: https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$apply
e.bind('click',function(){
s.$apply(function() {
s.data.push({});
});
});
However angular already has built in directive for handling things like mouse clicks you can just use that and let angular do the work for you. This would be the better option.
so first in your view register a click handler on your button
<button ng-click="add()">Add input</button> <div ng-repeat="d in data"><input type="text"></div>
Then in your link simply add the add() method of your scope
s.add = function () {
s.data.push({});
}
Heres a working fiddle showing both examples. http://jsfiddle.net/3dgdrvkq/
EDIT: Also noticed a slight bug in your initial click handler. You registering a click but not specifying the button to apply it to. So if you clicked anywhere in the directive, not just the button, the handler would fire. You should be more specific when registering events manually, using ids, class names attributes etc.
The e or element property of the link function is a jqlite or full jQuery object of the entire directive. If you have jQuery included before angular it will be a full jQuery object. If not it will a jqlite object. A thinned out version of jQuery.
Here is a basic example for your logic .
var TestApp = angular.module('App', []);
// controller
TestApp.controller('mainCtrl', function mainCtrl($scope) {
$scope.data = [];
$scope.addDataItem = function () {
$scope.data.push({
someFilield: 'some value'
});
console.log('pushing value ... ');
}
});
// view
<div ng-app="App" class="container" ng-controller="mainCtrl">
<button type="button" ng-click="addDataItem()">Add an input</button>
<div ng-repeat="d in data track by $index">
<custom-directive model="d"></custom-directive>
</div>
</div>
// directive
TestApp.directive('customDirective', function customDirective() {
return {
restrict: 'E',
scope: {
model: '='
},
template: 'item -> <input type = "text" />',
link: function (scope, elem, attrs) {
console.log('scope.model', scope.model);
},
controller: function ($scope) {
// do staff here
}
}
});

How can I call a method on a custom directive's isolated scope from within a transcluded controller in Angular.js

I created a directive called dt-modal under the dt module. In my main app's module called demo, I use this dt-modal which has an isolated scope. I created this directive such that the HTML form written within the directive is transcluded since I want to reuse this modal for many different forms.
<dt-modal>
<form ng-controller="ReviewFormController"
name="reviewForm"
novalidate
ng-submit="reviewForm.$valid && submitReview(review)">
<!-- form contents here -->
</form>
</dt-modal>
This transcluded form has a custom controller called ReviewFormController that listens for the submit event. How can I call the close() method on the dt-modal's scope from within submitReview() in ReviewFormController?
Here is a JSBin. If you hit ESC, you can see close() in the directive run.
http://jsbin.com/cukanole/1/edit
If this isn't possible, is there a better design for this directive?
Thanks in advance!
Since you are using an isolated scope, you could pass a control object to the directive...
<dt-modal id="review-form-modal" api="modal.api">
and add the close method to it via two-way binding:
scope: {
api: '='
},
link: function($scope, $el, attrs) {
$scope.api = {
close: function() {
$el.css({
display: 'none'
})
}
}
...
Then ng-click can use the control object to call close:
<button type="submit" ng-click="modal.api.close()">Submit</button>
If you want to try this code, here it is on Plunker.
My recommendation is to use $emit to trigger the event from the controller and use $on on the directly.
Controller
scope.$emit("ValueChanged", value);
In the directive the event will be captured using $on like:
$scope.$on("ValueChanged", function(event, ars){
... //your event has been triggered.
});
Important:
Directives should be always independent components, if inside the directive there is a call to a method from a controller(outside the directive) this will create a dependency between my directive and the controller and of course this will force one not being able to exist without the other.
If I would have to apply a design principle to a directive it will be the S in SOLID, Single responsibility principle. Directives should be able to encapsulate and work independently.

AngularJS - how do you know when angular has finished processing a page?

How to know when all interpolation and processing on a given page has been completed?
compile: function (tElement, tAttrs) {
return function (scope, element, attrs) {
element.html(tpl);
$compile(element.contents())(scope);
};
},
This is not synchronous. If there are {{stuff}} and ng-repeat="..." etc... they will not all be guaranteed to be completed when the link function returns.
I need a way to know when the page has rendered and there's no more directives to process so that I can then use #hashes to navigate to elements created on the page by angular. (using $anchorScroll)
Maybe try this:
$scope.$on('$viewContentLoaded', function() {
// ....
});
There is a directive in angular for this very specific reason ; ngCloak.
The ngCloak directive is used to prevent the Angular html template
from being briefly displayed by the browser in its raw (uncompiled)
form while your application is loading. Use this directive to avoid
the undesirable flicker effect caused by the html template display.
Doc # https://docs.angularjs.org/api/ng/directive/ngCloak
Similar Question on Quora; How do I hide a div with AngularJS until ng-repeat has finished processing? # https://www.quora.com/How-do-I-hide-a-div-with-AngularJS-until-ng-repeat-has-finished-processing
This is another way to do it, but I prefer to use angular directive.
<li ng-repeat="opt in menuItems" my-custom-repeat-listener> </li>
and then on the directive something sort of like
if(scope.$last) { /*fire event or etc to show the list items*/ }

Resources