Angular Directive for keydown breaks other functionality - angularjs

I'm trying to listen to keydown events on a formfield. To do this I've built a directive with a "keydown" attribute as follows:
var keydown = function() {
return {
restrict: 'A',
scope: {
keydown: '=keydown'
},
link: function(scope, elem, attr) {
$(elem).keydown(function(evt){
scope.keydown(evt);
});
}
};
};
Now I add this to my inputs markup:
<input class="search-input" type="text" ng-model="queryStr" ng-change="redrawUI()" keydown="processSearchBox" >
Now my keydown function is called and that works fine... however now my model is no longer bound to the input form field... if I change the model the form doesn't update

The problem is you are creating a new "isolate" scope which is detached from the scope where redrawUI() lives. Take a look at this fiddle for an alternate approach where you inherit from the parent scope and use scope.$eval to process your event.

Related

Angular remove class on location change

I want to remove a class when ever the user changes page, mostly because I want to collapse the bootstrap navbar. To do this I have created a directive that will remove a specified class. Whats the best way to trigger my directive on the $routeChangeSuccess event without listening to to the event within the directive as I would like to keep it flexible.
app.directive('removeClass', function() {
return {
scope: {},
restrict: 'A',
link: function(scope, element, attrs) {
//someway to trigger this method e.g. from an event listener outside the directive
scope.removeClass = function() {
element.removeClass(attrs.removeClass);
}
}
};
});
You can change a $rootScope variable with ng-class. Set that variable when there in a controller depending on page.

Can I set a property like $invalid myself?

I would like to set a property (blurred in this case) on an input element, just like AngularJs sets $invalid, $valid etc. on elements.
Is this possible from a directive, and if so, how?
Angular set's properties on the element's controller, not on the element itself.
Controls (e.g. inputs, selects etc) have an NgModelController, while forms/ngForms have a FormController.
The NgModelController is responsible for informing the parent FormController (if any) about changes in properties like $invalid.
The directives that use NgModelController (e.g. input, select etc) are responsible for determining when a value is valid or not and call the appropriate methods of their NgModelController.
For something as simple as setting a property on the controller, you need a directive that gets access to the controller instance (by require-ing it) and you are good to go:
.directive('blurMonitor', function () {
return {
restrict: 'A',
require: 'ngModel', // to get access to the controller
link: function postLink(scope, elem, attrs, ctrl) { // ctrl===NgModelController
/* Initialize the property */
ctrl.$blurred = true;
/* Update the property value on focus */
elem.on('focus', function () {
/* Since JS events fire asynchronously and outside
* of the "Angular context" we need to call `$apply()` */
scope.$apply(function () {
ctrl.$blurred = false;
});
});
/* Update the property value on blur */
elem.on('blur', function () {
/* Since JS events fire asynchronously and outside
* of the "Angular context" we need to call `$apply()` */
scope.$apply(function () {
ctrl.$blurred = true;
});
});
}
};
});
<form name="form1">
<input type="text" name="text1" ng-model="something" blur-monitor />
<p>Blurred: {{form1.text1.$blurred}}</p>
</form>
See, also, this short demo.

How do I bind to a custom event in an angularjs directive?

I am having trouble catching an event which is sent like this:
document.body.dispatchEvent(event);
The directive below is on a div, inside the body:
.directive('myDirective',function(){
return {
restrict: 'A',
link: function(scope, element, attrs, pageCtrl){
element.bind('myEvent', function(){
console.log('caught my event!');
});
}
};
});
This works if the event is sent with triggerHandler on the div.
event = new Event('myEvent');
elem.triggerHandler(event);
How can I catch a custom event on the document body inside a directive?
Update:
This is for use in a Cordova app, with an existing plugin, which dispatches events on document.body. So is there another way I can catch those events in a directive on a div?
The problem is that events bubble, meaning when you dispatch the event from document.body, it propagates up through its ancestors. Since it doesn't travel down through its descendants, your directive's event listener is never fired.
You can, however, catch the event on body and fire to your div using Angular's internal event system.
Create two directives
One to decorate the body element with the following behavior:
catches the Cordova event, then...
broadcasts an Angular-internal event from rootScope
.directive('bodyDirective',function($rootScope){
return {
restrict: 'A',
link: function(scope, element, attrs){
element.bind('myEvent', function(){
$rootScope.$broadcast('cordovaEvent');
});
}
};
})
... and another to decorate the div with behavior that will catch the Angular event:
.directive('divDirective', function(){
return {
restrict: 'A',
link: function(scope, element, attrs) {
scope.$on('cordovaEvent', function(){
// do something here
});
}
}
})
Plunker (you must view the preview in a separate window and watch console there)

Angular.js child input element not getting parents scope

This should be pretty simple, but for some reason when I have
<div class="file-navigator" ng-controller="FileSystemCtrl">
<input type="file" id="openFile" ng-model="path" ng-change="openFolder()" nwdirectory />
The ng-change doesn't get triggered.
If I use
onchange="angular.element(this).parent().scope().openFolder()"
the onchange event gets triggered, but obviously, that's ugly.
The FileSystemCtrl is defined as a module which I'm importing into my app, it's structured like this.
angular.module('myApp.FileSystemModule',[])
.factory('FileSystemModel',function($rootscope){...})
.controller('FileSystemCtrl',function(){...});
Any ideas why the child doesn't know about it's parent controller? Particularly as the child doesn't have a controller of it's own?
AngularJs doesn't support input with type file. See this issue. And this. Your onchange event is the best option for now.
Another way would be to use a directive taking advantage of $compile to interact with a ng-model:
.directive('path', function($compile) {
return {
restrict: 'E',//<path></path> in your markup
replace: true,
template: '<input type="file" />',
link: function(scope, elem, attrs) {
var textField = $(elem).attr('ng-model', 'something');
$compile(textField)(scope);
$(elem).change(function() {
//do stuff
});
}
};
});
I didn't test it, but it provides you with a get-go.

How to register my own event listeners in AngularJS?

How do I register my own event listeners in an AngularJS app?
To be specific, I am trying to register Drag and Drop (DND) listeners so that when something is dragged and dropped in a new location of my view, AngularJS recalculates the business logic and updates the model and then the view.
Adding an event listener would be done in the linking method of a directive. Below I've written some examples of basic directives. HOWEVER, if you wanted to use jquery-ui's .draggable() and .droppable(), what you can do is know that the elem param in the link function of each directive below is actually a jQuery object. So you could call elem.draggable() and do what you're going to do there.
Here's an example of binding dragstart in Angular with a directive:
app.directive('draggableThing', function(){
return {
restrict: 'A', //attribute only
link: function(scope, elem, attr, ctrl) {
elem.bind('dragstart', function(e) {
//do something here.
});
}
};
});
Here's how you'd use that.
<div draggable-thing>This is draggable.</div>
An example of binding drop to a div or something with Angular.
app.directive('droppableArea', function() {
return {
restrict: 'A',
link: function(scope, elem, attr, ctrl) {
elem.bind('drop', function(e) {
/* do something here */
});
}
};
});
Here's how you'd use that.
<div droppable-area>Drop stuff here</div>
I hope that helps.
Hiding event handling and dom manipulation in a directive is pretty much the the angularjs way. Calling scope.$apply when an event fires tells angular to update the view.
You might consider using jquery-ui like in this sample (see angular wiki of examples
I work with the angular-ui group and there is a simple event wrapper you might find useful.
Nice solution by Ben but keep in mind you will need to access originalEvent and original element. According to Mozilla documentation two conditions must meet https://developer.mozilla.org/en-US/docs/DragDrop/Drag_Operations
draggable is true
Listener for dragstart
So directive might look something like this
app.directive('draggableThing', function () {
return function(scope, element, attr) {
var pureJsElement = element[0];
pureJsElement.draggable = true;
element.bind('dragstart', function(event) {
event.originalEvent.dataTransfer.setData('text/plain',
'This text may be dragged');
//do something here.
});
}
});
A good step by step example is available here http://blog.parkji.co.uk/2013/08/11/native-drag-and-drop-in-angularjs.html

Resources