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>
Related
I have component that is used multiple times, 58 times to be exakt. The only thing that differs between them is unique attributes to add validations. What I want to do is to add a array of attributes to my template before is compiled. Is this possible to achieve when working with a Angular component?
component.js
(function () {
'use strict';
angular
.module('upaApp')
.component('component', {
bindings: {
inputAttributes: '#',
},
controller: controller,
restrict: 'E',
templateUrl: 'app/component/component.html'
});
function controller() {
var $ctrl = this;
}
})();
component.html
<input {{ $ctrl.inputAttributes }}
class="form-control"
type="text" />
When I use the component <component input-attributes="directive1, directive2"></component> it doesn't render out my string and even if it did I would not be sure that it would work. So is there a way to dynamically be able to set the attributes in AngularJS?
Is this angular 1 or 2?
Ill assume the former.
I dont know of a way to place a string as an attribute. What you could do as a workaround is conditionally insert attributes with the ng-attr- attribute. This will insert the attribute if the variable is not undefined.
maybe something like this:
$scope.ctrl.inputAttributes = {
directive1:undefined, //this one wont show
directive2:"this one will show"// as directive2="this one will show"
}
then in your markup:
<input ng-attr-directive1="ctrl.inputAttributes.directive1"
ng-attr-directive2="ctrl.inputAttributes.directive2"
class="form-control"
type="text" />
https://www.thinkingmedia.ca/2015/03/conditionally-add-a-html-element-attribute-value-in-angularjs/
EDIT: it may not be clean, but you could create a directive that compiles html.
app.directive('dynamicAttributes', function ($compile) {
return {
restrict: 'E',
scope: {
attributes: '#'
},
link: function (scope, elem, attrs) {
var h = '<input '+scope.attributes + ' class="form-control" type="text" />';
elem.replaceWith($compile(h)(scope));
}
}
});
then in your DOM
<dynamic-attributes attributes="1 2 3"></dynamic-attributes>
fiddle: http://jsfiddle.net/brhardwick/nx16zdrL/1/
There was actually a very simple solution on my problem. You could use ng-model to send the value to the component. And when I placed my directives on the component it validates accordingly since it can access the value from ng-model.
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....
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.
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
<input type='text' ng-model='foo' my-dir='customFunction' />
{{foo}}
.directive('myDir',function(){
scope:{ customFunc : "&myDir"},
});
Now the scope will be overridden with myDir and foo won't be updated on the screen. But still each control that has my-dir attribute should have customFunction in an isolated scope.
Is it possible?
As mentioned in the comments above, one directive probably won't work everywhere. If the directive will be used with other directives like ng-model, ng-repeat, etc., an isolate scope probably won't work. Here's an example of a directive that uses $eval, and does not create a new scope:
<div ng-controller="MyCtrl">
<input type='text' ng-model='foo' my-dir='customFunction'>
<br>foo={{foo}}
</div>
app.directive('myDir', function() {
return {
link: function(scope, element, attrs) {
scope.$eval(attrs.myDir);
},
}
});
function MyCtrl($scope) {
$scope.customFunction = alert('hi');
$scope.foo = '22';
}
Fiddle
See also When writing a directive in AngularJS, how do I decide if I need no new scope, a new child scope, or a new isolated scope?