having optional attributes in an angular directive - angularjs

let's say that I want to create directive to mimic a number input field (doing so just to learn about directives in general)
so, I create a directive like this
angular.module('myApp')
.directive('inputNumber',function(){
return {
replace: true,
restrict: 'E',
scope: {
label: '#',
property: '=',
readonly: '#',
disabled: '#'
},
templateUrl: '/directives/numberInput.html'
};
})
and the templateUrl is this
<md-input-container>
<label>{{ label }}</label>
<input ng-model="property" type="number">
</md-input-container>
and my html text is this
<input-number label="Columns" property="controller.data.cols">
</input-number>
oddly enough this worked ;) I was quite impressed for a first attempt ..
Now, sometimes I want to add the min and max attributes for a number
as these are optional, what do I add to the directive js and html ?
I presume that I add
min: '#',
max: '#',
to the directive js, but what do I put into the html ?
<input ng-model="property" type="number" min="{{min}}" max="{{max}}">
but then what happens if min / max is not supplied ?
thanks

You need to append them with a question mark.
scope: {
label: '#',
property: '=',
readonly: '#',
disabled: '#',
min: '#?',
max: '#?'
}
The question mark declares the scope bindings as optional, allowing you to not include them at all in the markup if you don't need them. If you don't include them then the properties on your isolate scope won't be defined.
<input ng-model="property" type="number" min="{{min}}" max="{{max}}">
Yes that is the correct way to pass values into those attributes, using the {{}} binding syntax. This is because the # binding is a literal binding. It will take the raw string value of that attribute and assign it to that isolate scope variable. By using the binding syntax, Angular will have resolved those bindings to actual values before your directive is parsed and reads those values in.
If you want two-way binding, then you would use the = binding symbol. That one tells angular that whatever you pass to that attribute should be a variable on the parent scope surrounding the directive. Angular knows at that point to create a two-way binding between that outer-scope variable and the variable on your directive's isolate scope. This way, if the variable in your isolate scope changes, so does the outer-scope variable, and vice versa.
<input ng-model="property" type="number" min="minValue" max="maxValue">
In that example your directive would expect minValue and maxValue to be variables already defined on the parent scope surrounding your directive.
I wrote a detailed blog post on isolate scope bindings last year. Hope it helps!

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" />

AJAX query inside directive template

I have a directive that creates multiple form elements:
// Directive
app.directive('comboInput', function(){
return {
scope: {
imodel: '=',
dmodel: '=',
bmodel: '=',
inputname: '#',
integers: '=',
decimals: '='
},
templateUrl: templatePath + 'combo-input.html'
}
});
// Template
<select id="" ng-model="imodel" ng-change="bmodel=imodel+dmodel" ng-options="value for value in integers"></select>
<select id="" ng-model="dmodel" ng-change="bmodel=imodel+dmodel" ng-options="(value|fraction) for value in decimals"></select>
<input type="number" name="{{inputname}}" ng-model="bmodel">
// Usage
<combo-input inputname="width" bmodel="width" imodel="widthI" dmodel="widthD" integers="ints" decimals="decs"></combo-input>
This application's controller has a method to grab a price:
$scope.getProductPrice = function(){
return $http.post('/customize/angular/getProductPrice', {
sku: '$scope.sku',
width: $scope.width,
height: $scope.height
}).then(function(response){
$scope.productPrice = parseFloat(response.data).toFixed(2);
});
}
I use this method on various parts of the form, typically by calling ng-change="getProductPrice()" on some form element; however, this does not work when calling it from the template.
I have tried a number of different answers I've found here on SO, but none of them work as expected (like infinite $http calls that run forever), or maybe if they do, the person submitting the answer provided no comments or other details on why the code works so I am unable to adapt to my needs. On top of this, various methods all use different techniques, some deprecated, so there is no consistency to figuring out what I need to do.
How can I call getProductPrice() from my directive's template?
Edit
I've created a Plunker to demonstrate the old value being passed to the getProductPrice() method.
http://plnkr.co/edit/1nRs26nTaSOztjvVyLYg?p=preview
You directive has an isolated scope (scope: {...}) so it will not have access to the upper controller scope's variables and methods, such as getProductPrice.
What you can do is pass this method as a parameter to your directive:
app.directive('comboInput', function () {
return {
scope: {
imodel: '=',
dmodel: '=',
bmodel: '=',
inputname: '#',
integers: '=',
decimals: '=',
on-change: '&'
},
templateUrl: templatePath + 'combo-input.html'
}
});
Which you then instanciate like this:
<combo-input ... on-change="getProductPrice()"></combo-input>
And then you just need to call scope.onChange() from your directive to call this function that has been bound to the on-change attribute.
Or you can even directly bind it to the built-in ng-change:
<input ... ng-change="onChange()">
UPDATE: if you want to pass specific parameters for this call, you can simply add them in the method signature:
$scope.getProductPrice = function(width, height) { ... }
And also in the directive binding:
<combo-input ... on-change="getProductPrice(width, height)"></combo-input>
And then all you need to do is call onChange with these parameters. If you write that in the HTML, it is as simple as:
<input ng-change="onChange(width, height)">
(Making sure that width and height are in your directive scope obviously so it can be used from the HTML).
If you want to call that from the code, either width and height are already in your scope, in which case you only need to call scope.onChange(). However if they are not, you can add these parameters just for this call with this specific syntax:
scope.onChange({width: value1, height:value2})
Another approach is to make this Ajax method a service instead of a controller function. By injecting the service in your directive, you could directly call myService.getProductPrice(). The advantage of the parameter binding is that you can reuse your directive with a different behavior, but if it will always call that function then a service is prefered.
Since you declared the scope oft your directive to be a new one, just the entries that you declare are bound between this scope and the outer (with your method in it).
You can either pass the function to the directive scope using the scope hash of the directive definition object or you add another ng-controller with the same controller inside the directive.

Angular directive: using ng-model within isolate scope

I'm having trouble working out how I can define a custom directive that both:
Uses isolate scope, and
Uses the ng-model directive in a new scope within in its template.
Here's an example:
HTML:
<body ng-app="app">
<div ng-controller="ctrl">
<dir model="foo.bar"></dir>
Outside directive: {{foo.bar}}
</div>
</body>
JS:
var app = angular.module('app',[])
.controller('ctrl', function($scope){
$scope.foo = { bar: 'baz' };
})
.directive('dir', function(){
return {
restrict: 'E',
scope: {
model: '='
},
template: '<div ng-if="true"><input type="text" ng-model="model" /><br/></div>'
}
});
The desired behaviour here is that the input's value is bound to the outer scope's foo.bar property, via the the directive's (isolate) scope model property. That doesn't happen, because the ng-if directive on the template's enclosing div creates a new scope, so it's that scope's model that gets updated, not the directive's scope's.
Ordinarily you solve these ng-model issues by making sure there's a dot in the expression, but I can't see any way to do that here. I wondered if I might be able to use something like this for my directive:
{
restrict: 'E',
scope: {
model: {
value: '=model'
}
},
template: '<div ng-if="true"><input type="text" ng-model="model.value" /><br/></div>'
}
but that doesn't work...
Plunker
You are right - ng-if creates a child scope which is causing a problem when text is entered in the input text field. It creates a shadow property named 'model' in child scope which is a copy of the parent scope variable with the same name - effectively breaking the two-way model binding.
The fix for this is simple. In your template, specify the $parent prefix:
template: '<div ng-if="true">
<input type="text" ng-model="$parent.model" /><br/>
</div>'
This ensures that it will resolve 'model' from the $parent scope, which you've already setup for two-way model binding through the isolated scope.
In the end, the '.' in ng-model saves the day. I find it useful to think about anything left of the dot as a way for Angular to resolve the property through scope inheritance. Without the dot, resolving the property only becomes an issue when we're assigning scope variables (otherwise, lookups are fine, including read-only {{model}} binding expressions).
ng-if creates an additional prototypally inheriting scope, so ng-model="model" binds to the inherited property of the new scope and not to the 2-way binded property of the directive scope.
Change it to ng-show and it will work.
You can use a small Firebug extension i've written to inspect angular scopes.

Declaring and using angular directives that map id, name, and ng-model to a single field

I'd like to create/use an AngularJS directive to avoid creating some redundant HTML and also to simplify how I use Bootstrap elements. I'd like to use a directive this way:
<myDirective id="person.lname" label="Last Name"></myDirective>
The template I'd like AngularJS to write is:
<label for="person.lname">Last Name</label>
<input id="person.lname" name="person.lname" ng-model="person.lname">
How would I declare a directive to be able to create this template and have the binding with ng-model still work? Are there any reasons why this would not be a good idea?
Thanks.
UPDATED
I added the label tag to reflect how the id/name would be used for the input element. The generated template would allow you to click on the label and have the focus be placed on the input element.
This should do what you want:
.directive('myDirective', function() {
return {
scope: {
model: '=identifier',
id: '#identifier',
label: '#',
},
restrict: 'E',
template: '<label for="{{id}}">{{label}}</label><input id="{{id}}" name="{{id}}" ng-model="model">'
};
})
View:
<my-directive label='Last name' identifier="person.lname"></my-directive>
Fiddle

Adding attributes to HTML element in an AngularJS Directive

I am new to angular and am attempting to add validation to a directive based on attributes. Here is how I use the directive:
<div sc-textbox data-bind-to="fieldToBind" data-field-name="fieldName" data-required="someValue != 'Office'"></div>
The data-required attribute may or may not be present. The template for the directive is:
<input id="{{fieldName}}" name="{{fieldName}}" type="text" data-ng-model="bindTo" />
When the data-required attribute is present I would like to add data-ng-required="{{required}}" to the input. How would I go about doing this?
The scope for the directive is:
scope: {
'bindTo': '=',
'fieldName': '#',
'required': '='
}
Any help is greatly appreciated.
Why not just leave ng-required there anyway. This is what it's for.
Because you have limited scope, you need a boolean in your directive's scope that represents "someValue != 'Office'".
Or put someValue in scope and do this:
<input id="{{fieldName}}"
data-ng-required="(someValue != 'Office') && required"
name="{{fieldName}}" type="text"
data-ng-model="bindTo" />
Update
The attribute you want to add "data-ng-required" is a directive. You cannot dynamically add and remove directives from the DOM in Angular. If you really don't want "data-ng-required" in parts of your DOM, and you want this to be dynamic, the closest thing you can do is $compile and append this input as 2 templates-> one with required, and one without

Resources