pass ngKeyUp function into directive - angularjs

I’ve got a directive/template that contains an input field.
The input field has an ngKeyup and an ngModel.
I want the ngKeyup function to be passed into the directive. The ngKeyup on the input field within the directive/template should invoke this function.
This plunker shows option 1 and option 2 http://plnkr.co/edit/kN8mitdG6pK5GNqGzYw5?p=preview
Option one is simplest and partially works, the function is simply passed in by '=', the directive references it in the ngKeyUp attribute
Directive
ngApp.directive("searchField", ['$parse',function ($parse) {
return {
restrict: "E",
scope: {
myKeyUp: '=',
Template
<input type="text" ng-model="model" ng-keyup="myKeyUp" />
This partially works, but the $event object is not passed.
In option two the directive receives the function from the controller as an '&', tries to $parse it and invoke it. This simply isn't working for me but I'm not very familiar with $parse.
ngKeyup can receive any parameters e.g. ng-keyup(a,b,c,$index,$event)
A key point/requirement is that this directive should be the same i.e. myKeyUp should be capable of accepting any parameters.
Any help or pointers much appreciated.
Thanks
John

It has to be '&' in scope, as it allows you to pass the function reference.
and what you have to do in the template is:
<input type="text" ng-model="model" ng-keyup="myKeyUp($event)" />
UPDATE:
Here is another approach that you could take: http://goo.gl/a27JrX

It turns out there's a really simple solution.
Quote from question
I want the ngKeyup function to be passed into the directive. The ngKeyup on the input field within the directive/template should invoke this function.
Turns out the div can simply have the ng-keyup attribute, it doesn't have to be on the input field.
This completely solves the problem.
http://plnkr.co/edit/xTigcDaSLpL9pvRtfY5J?p=preview
Now the directive takes only the model as a parameter. The ngKeyup is outside the directive.
ngApp.directive("searchField", ['$parse',function ($parse) {
return {
restrict: "E",
scope: {
model: '='
},
templateUrl: 'searchFieldTemplate',
replace: true,
controller: ['$scope','$attrs', function ($scope, $attrs) {
}]
};
}]);
And the template is now only concerned with the model
<div class="searchField">
<h4>Template</h4>
(observe the console)
<div>
<input type="text" ng-model="model" />
</div>
That actually makes allot of since when you think about it....

Related

Passing An Ng-Model Value to A Directive

I've looked at the previous answers, but I'm not sure if they are the answer I need or not.
I have a directive, let's call it "selectValue". A value can have a default, let's call it "$scope.default".
The directive will look like this in one place:
<select-value ng-model="data.input" controlId="inputSelector" />
But it will look like this in another:
<select-value ng-model="myValue" controlId="inputSelector" />
I do not have the option of making the ng-model inputs the same; they are used in different places and this is a legacy code base.
Here is the definition of the directive:
.directive('selectValue', ['$timeout', function($timeout) {
const directive = {
restrict: 'E',
scope: {
controlId: '#',
model: '=?'
},
controller: 'selectValueCtrl',
template: '<input id="{{controlId}}" name="{{controlId}}" placeholder="Enter Value" type="text" ng-model="model" />'
};
return directive;
}
The question: what do I do to be able to enter different inputs in the "model" attribute of the <select-value> to have it access different scope variables?
Edit: the referenced "duplicate" question refers to setting a value for ng-click, not referencing ng-model in a form control.
From what I can tell, it looks as though you are trying to pass defaults with preset values. The problem you are facing is your select-value element is using the ng-model directive in an attempt to pass the data however your binding in your directive is 'model'.
In order to fix this issue, simply change the 'ng-model' to 'model' and your bindings should then work.
In the end, your element should look like so:
<select-value model="myValue" controlId="inputSelector" />
as opposed to:
<select-value ng-model="myValue" controlId="inputSelector" />

Passing a model to a custom directive - clearing a text input

What I'm trying to achieve is relatively simple, but I've been going round in circles with this for too long, and now it's time to seek help.
Basically, I have created a directive that is comprised of a text input and a link to clear it.
I pass in the id via an attribute which works in fine, but I cannot seem to work out how to pass the model in to clear it when the reset link is clicked.
Here is what I have so far:
In my view:
<text-input-with-reset input-id="the-relevant-id" input-model="the.relevant.model"/>
My directive:
app.directive('textInputWithReset', function() {
return {
restrict: 'AE',
replace: 'true',
template: '<div class="text-input-with-reset">' +
'<input ng-model="inputModel" id="input-id" type="text" class="form-control">' +
'<a href class="btn-reset"><span aria-hidden="true">×</span></a>' +
'</div>',
link: function(scope, elem, attrs) {
// set ID of input for clickable labels (works)
elem.find('input').attr('id', attrs.inputId);
// Reset model and clear text field (not working)
elem.find('a').bind('click', function() {
scope[attrs.inputModel] = '';
});
}
};
});
I'm obviously missing something fundamental - any help would be greatly appreciated.
You should call scope.$apply() after resetting inputModel in your function where you reset the value.
elem.find('a').bind('click', function() {
scope.inputModel = '';
scope.$apply();
});
Please, read about scope in AngularJS here.
$apply() is used to execute an expression in angular from outside of the angular framework. (For example from browser DOM events, setTimeout, XHR or third party libraries). Because we are calling into the angular framework we need to perform proper scope life cycle of exception handling, executing watches.
I've also added declaring of your inputModel attribute in scope of your directive.
scope: {
inputModel: "="
}
See demo on plunker.
But if you can use ng-click in your template - use it, it's much better.
OK, I seem to have fixed it by making use of the directive scope and using ng-click in the template:
My view:
<text-input-with-reset input-id="the-relevant-id" input-model="the.relevant.model"/>
My directive:
app.directive('textInputWithReset', function() {
return {
restrict: 'AE',
replace: 'true',
scope: {
inputModel: '='
},
template: '<div class="text-input-with-reset">' +
'<input ng-model="inputModel" id="input-id" type="text" class="form-control">' +
'<a href ng-click="inputModel = \'\'" class="btn-reset"><span aria-hidden="true">×</span></a>' +
'</div>',
link: function(scope, elem, attrs) {
elem.find('input').attr('id', attrs.inputId);
};
});
It looks like you've already answered your question, but I'll leave my answer here for further explanations in case someone else lands on the same problem.
In its current state, there are two things wrong with your directive:
The click handler will trigger outside of Angular's digest cycle. Basically, even if you manage to clear the model's value, Angular won't know about it. You can wrap your logic in a scope.$apply() call to fix this, but it's not the correct solution in this case - keep reading.
Accessing the scope via scope[attrs.inputModel] would evaluate to something like scope['the.relevant.model']. Obviously, the name of your model is not literally the.relevant.model, as the dots typically imply nesting instead of being a literal part of the name. You need a different way of referencing the model.
You should use an isolate scope (see here and here) for a directive like this. Basically, you'd modify your directive to look like this:
app.directive('textInputWithReset', function() {
return {
restrict: 'AE',
replace: 'true',
template: [...],
// define an isolate scope for the directive, passing in these scope variables
scope: {
// scope.inputId = input-id attribute on directive
inputId: '=inputId',
// scope.inputModel = input-model attribute on directive
inputModel: '=inputModel'
},
link: function(scope, elem, attrs) {
// set ID of input for clickable labels (works)
elem.find('input').attr('id', scope.inputId);
// Reset model and clear text field (not working)
elem.find('a').bind('click', function() {
scope.inputModel = '';
});
}
};
});
Notice that when you define an isolate scope, the directive gets its own scope with the requested variables. This means that you can simply use scope.inputId and scope.inputModel within the directive, instead of trying to reference them in a roundabout way.
This is untested, but it should pretty much work (you'll need to use the scope.$apply() fix I mentioned before). You might want to test the inputId binding, as you might need to pass it a literal string now (e.g. put 'input-id' in the attribute to specify that it is a literal string, instead of input-id which would imply there is an input-id variable in the scope).
After you get your directive to work, let's try to make it work even more in "the Angular way." Now that you have an isolate scope in your directive, there is no need to implement custom logic in the link function. Whenever your link function has a .click() or a .attr(), there is probably a better way of writing it.
In this case, you can simplify your directive by using more built-in Angular logic instead of manually modifying the DOM in the link() function:
<div class="text-input-with-reset">
<input ng-model="inputModel" id="{{ inputId }}" type="text" class="form-control">
<span aria-hidden="true">×</span>
</div>
Now, all your link() function (or, better yet, your directive's controller) needs to do is define a reset() function on the scope. Everything else will automatically just work!

Use ng-model with a primitive value inside ng-transclude

In a legacy project, I want to create a new directive that uses transclude.
A trimmed down version of the directive code is:
app.directive('controlWrap', function() {
return {
restrict: 'E',
transclude: true,
scope: { label: "#" },
templateUrl: "control-wrap-template.html"
}
})
And the template is:
<div>
<label>{{label}}</label>
<div>
<ng-transclude></ng-transclude>
</div>
</div>
This directive is used like this
<control-wrap label="Just a example">
<input type="text" ng-model="input" />
</control-wrap>
Test: {{input}}
I know that the workaround is to use a object in the scope instead of primitive value (ng-model inside ng-transclude). But that is no option for me. It is a ugly, poorly coded, legacy code that relies in those attributes directly on the scope.
Is there a something I can do in the directive to make that html works without change?
You can manually transclude (instead of using ng-transclude) and apply whatever scope (which is, in your case, scope.$parent) you need to the transcluded content:
transclude: true,
scope: { label: "#" },
template: '<div>\
<label>{{label}}</label>\
<placeholder></placeholder>\
</div>',
link: function(scope, element, attrs, ctrls, transclude){
transclude(scope.$parent, function(clone){
element.find("placeholder").replaceWith(clone);
});
}
Demo
The cleanest solution is to do some refactoring and passing an object instead of a primitive value, but if for some reason you cannot do that, you're not out of the options.
However, I wouldn't recommend any of these options
1) Bind input from the parent scope, that prevents creating a new value on the child scope upon write - butt keep in mind that accessing the parent scope hurts reusability of your directive.
Angular 1.2:
<input type="text" ng-model="$parent.input" />
Angular 1.3:
<input type="text" ng-model="$parent.$parent.input" />
(The difference is because the parent of the transcluded scope is the directive scope from 1.3)
2) Create some kind of wrapper object and pass that instead of the primitive value
$scope.inputWrapper = {};
Object.defineProperty($scope.inputWrapper, 'input', {
get: function() { return $scope.input },
set: function(newValue) { $scope.input = newValue; }
})
and pass this to the directive. But again, I would do some refactoring instead.

Angular.js setting the value of ng-show by parameter inside a directive

Sorry if the title isn't clear, here is what I'm trying to do:
I have multiple signup forms and every one of them has a password field. Now, I want to set some requirements to the passwords, ie. I want to get a password that is longer than 5.
I have:
<form name="myForm">
<!-- some elements -->
<input type="password" required ng-model="user.password" name="password" ng-minlength="5">
and right after that:
<div ng-show="myForm.password.$error.minlength">
Password is too short.
</div>
<!-- some other elements -->
</form>
I thought I would refactor this error message into a directive, the only problem is that I can't seem to correctly pass the form's name to the directive.
The directive looks like this:
myApp.directive('passwordLengthError', [function () {
return {
restrict: 'E',
replace: true,
template:'<div ng-show="{{form}}.password.$error.minlength">Password is too short.</div>',
scope: {
form: '#'
}
};
}]);
and I call it like this:
<div>
<password-length-error form="myForm"/>
</div>
If I check in Chrome's web inspector, I see that the parameter is there, I see
<div ng-show="myForm.password.$error.minlength">
however, it doesn't actually work, I don't see the message pop up if the password is shorter than 5 characters.
Is there a way to make this work, or is this not possible? Thanks in advance.
The # in your isolate scope is trying to evaluate an angular expression. You are just passing a string, so you can just set the scope variable directly to the attribute value in your directive, without any isolate scope or evaluation of the attribute.
So:
scope.form = attrs.form;
And the entire directive would be:
app.directive('passwordLengthError', [function () {
return {
restrict: 'E',
replace: true,
template:'<div ng-show="{{form}}.password.$error.minlength">Password is too short.</div>',
link: function(scope, element, attrs){
scope.form = attrs.form // the attribute is a string, so, YAY
}
};
}]);
YOUR DEMO

watching value of inner element in directive

How can I scope some inner element's value to an arbitrary variable and watch for its changes?
//search.html
<div>
<input type="search" class="input-medium search-query" />
</div>
angular.module('search',[])
.directive "searchBar", ->
restrict: "E"
templateUrl: 'search.html'
replace: true
Now I'd like to $watch for input's value change ('keyup') and access (get and set) current value from "outside". Can I have some arbitrary attribute and access it, like:
<div>
<search-bar value='{{searchTerm}}' />
</div>
I hope it's clear what I'm trying to do.
You can set the value of searchTerm inside your directives linking function.
link: function(scope, element, attrs) {
// input element
var input= angular.element(element.children()[0]);
input.bind('keyup', function(){
scope.searchTerm = input.val();
});
}
Jonathan's answer will create a "searchTerm" variable in the parent scope. This is unreliable, because if you put your directive in another scope (say a form) it will not be accessible outside of that form. What you should do is create "searchTerm" where you need it, and then pass it to "searchBar". If you are not using isolated scope for "searchBar" then you don't have to even pass it. If you do use isolated scope then passing would looks something like this.
angular.module('search',[])
.directive("searchBar", function () {
restrict: "E"
scope: {
searchTermInside: '=searchTerm'
},
templateUrl: 'search.html'
replace: true});
And you use the directive like so:
<search-bar searchTerm="searchTermOutside" />
And in template
//search.html
<div>
<input type="search" ng-model="searchTermInside" class="input-medium search-query" />
</div>

Resources