Updating ng-model within a directive that uses a template - javascript

I have a directive that is instantiated like this:
<datepicker ng-model="foo"></datepicker>
Inside the directive, the datepicker tag is replaced by this template:
template: '<div class="datepicker-wrapper input-append">' +
'<input type="text" class="datepicker" />' +
'<span class="add-on"><i class="icon-calendar"></i></span>' +
'</div>'
The value that I want ng-model bound to is the value of the input field. What is the best way to go about this so I maintain the two-way data binding of ng-model?

Depending on how complicated your passthrough is, you can just use the = scope to do a bidirectional bind between a local name and ngModel, like in this fiddle:
http://jsfiddle.net/mThrT/22/
I had a hell of a time setting up the fiddle for some reason (first time trying with angular) but here's the money shot:
template: '<div class="datepicker-wrapper input-append">'
+ '<input type="text" class="datepicker" ng-model="bar" />'
+ '<span class="add-on"><i class="icon-calendar"></i></span>'
+ '</div>',
scope: {
bar: '=ngModel'
},

There are a few ways to do this..
There is a function on the ctrl parameter of the linking function called .$setViewValue(valueHere) that you can use to set the value of whatever ngModel is referencing as well. It will do the work of setting things $dirty etc. There is also a property called .$viewValue you can use to get the current value. So you can set up a $watch on an isolate scope property to update the ngModel values.
The more correct way to do this would still be in the linking function, but it would look like so:
app.directive('myDirective', function() {
restrict: 'E',
require: 'ngModel',
scope: {}, //isolate the scope
template: '<div class="datepicker-wrapper input-append">' +
'<input type="text" class="datepicker" ng-model="date" />' +
'<span class="add-on"><i class="icon-calendar"></i></span>' +
'</div>',
controller: function($scope) {
},
link: function(scope, elem, attr, ctrl) {
//get the value from ngModel
scope.date = ctrl.$viewValue;
//set the value of ngModel when the local date property changes
scope.$watch('date', function(value) {
if(ctrl.$viewValue != value) {
ctrl.$setViewValue(value);
}
});
}
});

Related

Reuse directive multiplie times with dynamic attributes in another directive's template

What i want to do is to be able to use a directive with different attributes in the same ng-app. The main goal is to run different code when the directive's input (ng-model) changes.
This is what i have now:
app.directive('customInput',
function ($compile) {
var customInputDefinitionObject = {
restrict: 'E',
replace: true,
scope: {
ident: '#'
},
template: '<input type="text" >',
controller: 'customInputController',
compile: function (tElement, tAttrs) {
$('input').removeAttr('ident')
.attr('ng-model', tAttrs.ident)
.attr('ng-change', tAttrs.ident + 'Change()');
var elemLinkFn = $compile(tElement);
return function (scope, element) {
elemLinkFn(scope, function (clone) {
element.replaceWith(clone);
})
}
}
}
return customInputDefinitionObject;
});
It works well in html e.g.:
<custom-input ident="var1"></custom-input>
<custom-input ident="var2"></custom-input>
i'm going to get to input with different ng-model and ng-change function, the controller uses dynamic names to get the $scope variables( $scope.var1Change).
The problem start when i want to use this directive inside another template.
app.directive('customInputGroup', function ($compile) {
var customInputGroupDefinitonObject = {
restrict: 'E',
replace: true,
scope: {
rident: '#',
},
template:''+
'<div>'+
'<custom-input id="first"></custom-input>'+
'<custom-input id="second"></custom-input>'+
'</div>',
controller: 'customInputGroupController',
compile: function (elem, attrs) {
$('#first', elem).removeAttr('id').attr('ident', attrs.rident + 'Start');
$('#second', elem).removeAttr('id').attr('ident', attrs.rident + 'End');
var rangeLinkFn = $compile(elem);
return function (scope, element) {
rangeLinkFn(scope, function (clone) {
element.replaceWith(clone);
})
}
}
}
return customInputGroupDefinitonObject;
});
In this case if i'm going to use it inside the HTML e.g.:
<custom-input-group rident='sg'></custom-input-group>
what i get rendered:
<div>
<input ng-model="sgEnd" ng-change="sgEndChange()">
<input ng-model="sgEnd" ng-change="sgEndChange()">
<input ng-model="sgEnd" ng-change="sgEndChange()">
</div>
For the 3rd rendered input the ng-change does not working.
If set terminal:ture in the inputGroup directive i get only to "input" rendered but both of them has the same ng-model and ng-change.
So how can i make it to render something like this:
<div>
<input ng-model="sgStart" ng-change="sgStartChange()">
<input ng-model="sgEnd" ng-change="sgEndChange()">
</div>
And if u know how would u be so nice to let me know only the "how" but the "why" aswell.
Thank you in advance.

Multiple-tags input field

I recently started with AngularJS, and am currently working on a simple form with a tags input field and a submit button. The input field is supposed to accept multiple tags so that on clicking submit all the tags get saved as an array.
Now I am currently using ngTagsInput directive which I found on github(http://mbenford.github.io/ngTagsInput/). This directive gives me <tags-input></tags-input> HTML element which creates an input field that accepts multiple tags before submission. Here is what it look like(look at the Tags field):
This works fine, what I need now is a directive which gives me similar functionality but instead of an element ie. <tags-input>, I want an attribute which I can include inside the conventional <input> element like <input attribute='tags-input'> .
Question:
Is there a way I can use ngTagsInput as an attribute?
Are there any other directives out there which may suit my need? If yes then please post the link in the answer.
Thanks in advance.
No. As you can see on tags-input.js file, the directive is configured as an element:
return {
restrict: 'E',
require: 'ngModel',
scope: {
tags: '=ngModel',
text: '=?',
templateScope: '=?',
tagClass: '&',
onTagAdding: '&',
onTagAdded: '&',
onInvalidTag: '&',
onTagRemoving: '&',
onTagRemoved: '&',
onTagClicked: '&',
},
But you can write your own directive with attribute type and "replace" your div element to the tags-input element.
I wrote this example:
app.directive('tagsInputAttr',
function($compile){
return {
restrict: 'A',
require: '?ngModel',
scope:{
ngModel: '='
},
link: function($scope, element, attrs, controller) {
var attrsText = '';
$.each($(element)[0].attributes, function(idx, attr) {
if (attr.nodeName === "tags-input-attr" || attr.nodeName === "ng-model")
return;
attrsText += " " + attr.nodeName + "='" + attr.nodeValue + "'";
});
var html ='<tags-input ng-model="ngModel" ' + attrsText + '></tags-input>';
e =$compile(html)($scope);
$(element).replaceWith(e);
}
};
}
);
Now you can configure your tags-input elements in two ways:
Element way:
<tags-input ng-model="tags" add-on-paste="true" display-property="text" placeholder="Add a Tag here..." ></tags-input>
Attribute way:
<input tags-input-attr ng-model="tags" add-on-paste="true" display-property="text" placeholder="Add a Tag here..." />
You can see this in action at Plunker:
https://plnkr.co/edit/yjkX22

Passing attrs as $scope variables in Angular

I have a directive and I'm trying to get the Attrs and pass them to the $scope, but I'm not quite sure how to do that. More specifically I'm trying to set attributes in my template equal to what the name is set in my date-picker tag. I tried setting them as a variable, but obviously that didn't work.
Help and further clarification is greatly appreciated. Thanks!
HTML
<date-picker id="dateendPicker" name="date_end"></date-picker>
JS
App.directive('datePicker', function(){
return {
scope: {
name : '#'
},
restrict: 'AE',
replace: 'true',
template: '<div class="date"><div class="input-group"><input type="text" class="form-control" id="{{this_name}}" name="{{this_name}}" ng-model="event.{{this_name}}" required/><span class="input-group-addon"><i class="fa fa-calendar"></i></span></div></div>',
controller: ['$scope', function($scope){
$scope.this_name = this_name;
}],
link: function(scope, element, attrs){
var this_name = attrs.name;
}
}
});
Because of how you've defined your directive scope:
scope: {
name : '#'
}
name is already a variable on your scope. If you're not doing anything special with it on your controller\link functions, you can drop them entirely, and in your template reference it with {{name}}. Just note that if you're creating scope bindings with '#', then in your html you should pass your data as an angular expression, meaning:
name="{{date_end}}"
As I could see from your snippet, you want to use name attribute as ng-model into directive. I think, you should consider using another type of isolated scope (related SO answer)
Here is my approach (jsFiddle):
app.directive('datePicker', function () {
return {
scope: {
name: '=name'
},
restrict: 'AE',
replace: 'true',
template:
'<div class="date">' +
'<div class="input-group">' +
'<input type="text" ' +
'class="form-control" ' +
'id="bad-idea-to-make-it-editable-id" ' +
'name="{{ name }}" ' +
'ng-model="name" ' +
'required/>' +
'<span class="input-group-addon">' +
'<i class="fa fa-calendar"></i>' +
'</span>' +
'</div>' +
'</div>'
}
});
So now you can edit parent scope from directive. But if you dont want this kind of communication between scopes, use # scope, as #haimlit has mentioned.
There are some issues with your code snippet:
First you do not assign the assign the attrs to the scope but to a local variable, so it won't be available in the controller function.
You could try this:
App.directive('datePicker', function(){
return {
scope: {
name : '#'
},
restrict: 'AE',
replace: 'true',
template: '<div class="date"><div class="input-group"><input type="text" class="form-control" id="{{this_name}}" name="{{this_name}}" ng-model="event.{{this_name}}" required/><span class="input-group-addon"><i class="fa fa-calendar"></i></span></div></div>',
controller: ['$scope', function($scope){
$scope.this_name = this_name;
}],
link: function(scope, element, attrs){
scope.this_name = attrs.name;
}
}
});
I do not know if this really works since it is not recommended to use controller and link functions at the same time (taken from the directive guide):
Best Practice: use controller when you want to expose an API to other directives. Otherwise use link.
But according to the docs the attrs are also available as $attrs to the controller function:
App.directive('datePicker', function(){
return {
scope: {
name : '#'
},
restrict: 'AE',
replace: 'true',
template: '<div class="date"><div class="input-group"><input type="text" class="form-control" id="{{this_name}}" name="{{this_name}}" ng-model="event.{{this_name}}" required/><span class="input-group-addon"><i class="fa fa-calendar"></i></span></div></div>',
controller: ['$scope', '$attrs', function($scope, $attrs){
$scope.this_name = $attrs.name;
}]
}
});
But you have already defined name in the isolate scope, so it should be availble as scope.name in the conroller or link function:
App.directive('datePicker', function(){
return {
scope: {
name : '#'
},
restrict: 'AE',
replace: 'true',
template: '<div class="date"><div class="input-group"><input type="text" class="form-control" id="{{this_name}}" name="{{this_name}}" ng-model="event.{{this_name}}" required/><span class="input-group-addon"><i class="fa fa-calendar"></i></span></div></div>',
link: function(scope, element, attrs){
console.log(scope.name); // this is your name defined as attribute name="..." on the tag
}
}
});

Directive Isolate Scope 1.2.2

I'm working with Angular version 1.2.2 for the first time and trying to make a simple directive that uses isolate scope with '=' binding to pass in an object. I've done this a few times before so I'm wondering if maybe there was a change in 1.2.2 that changed this?
Here is my directive:
.directive('vendorSelector', function (VendorFactory) {
return {
restrict: 'E',
replace: true,
scope: { vendorId: '=' },
template: '<select ng-model="vendorId" ng-options="id for id in vendorIds">' +
'<option value="">-- choose vendor --</option>' +
'</select>',
link: function (scope, element, attrs) {
VendorFactory.getVendorIds().then(function(result) {
scope.vendorIds = result;
});
}
}
})
My HTML template using the directive is as follows:
<div class="padding">
<vendor-selector vendorId="someValue"></vendor-selector>
{{ someValue }}
</div>
And the backing controller:
.controller('AddProductController', function($scope, ProductFactory, AlertFactory) {
$scope.vendorId = 0;
$scope.someValue = undefined;
})
I've tried using both $scope.someValue and $scope.vendorId as the supplied object in the html template. In both cases the error I'm getting back is Expression 'undefined' used with directive 'vendorSelector' is non-assignable!. Am I missing something obvious that is preventing these values from being 2-way bound in the isolate scope?
In your html:
<vendor-selector vendorId="someValue"></vendor-selector>
Change vendorId="someValue"
to vendor-id="someValue"
HTML attributes are case insensitive so to avoid confusion Angular converts all camel cased variables (vendorId) to snake case attributes (vendor-id).
So someValue wasn't bound to vendorId. Resulting in vendorId being undefined in the template. And thus your error.

AngularJS: How to pass arguments/functions to a directive?

Look at this Fiddle, what do I have to change, that the expressions in the template get evaluated using the arguments I defined in the HTML? The SAVE-button should call the blabla()-function of the controller, since I pass it?
var myApp = angular.module('MyApp',[])
myApp.directive('editkeyvalue', function() {
return {
restrict: 'E',
replace: true,
scope: {
accept: "expression"
},
template : '<div><label class="control-label">{{key}}</label>' +
'<label class="control-label">{{key}}</label>' +
'<input type="text" ng-model="value" />'+
'<button type="button" x-ng-click="cancel()">CANCEL</button>' +
'<button type="submit" x-ng-click="save()">SAVE</button></div>',
controller: function($scope, $element, $attrs, $location) {
$scope.save= function() {
$scope.accept();
};
}
}
});
I do not really see through that. Thanks for help!
You can set two way data binding with property: '=' as Roy suggests. So if you want both key and value bound to the local scope you would do
scope: {
key: '=',
value: '='
},
Since you are passing these values, you have access to them in your directive's controller. But in case you want to run a function in the context of the parent scope, which seems to be what you want to do with the accept attribute, then you would need to tell angular like this
scope: {
accept: "&"
}
Now, from your save method you could call the function passed via accept
controller: function($scope, $element, $attrs, $location) {
$scope.save= function() {
$scope.accept()
};
}
Here's a jsfiddle
scope: {
accept: "&"
}
Use lowercase letters for function names, otherwise it doesn't work.
Just a quick note that you dont need the wrapping function save. Just call this in the template:
'<button type="submit" x-ng-click="accept()">SAVE</button></div>',
That transposes the function call and passes the parameters as expected.
This simplifies code and makes it a lot easier to read.

Categories

Resources