Passing value to ng-model in custom directive - angularjs

Right, I'm probably missing something fairly obvious here but here goes. I have a bit of HTML from bootstrap that I want to reuse so I wanted to make it into a custom directive.
<label class="toggle">
<input ng-model='model' type="checkbox" class="toggleInput">
<div class="track">
<div ng-show="test" class="toggle-label on">
{{onText}}
</div>
<div ng-show="!test" class="toggle-label off">
{{offText}}
</div>
<div class="handle"></div>
</div></label>
snippet of html I want to use in my pages that Angular will recompile into the above:
<toggle on-text="On" off-text="Off" ng-model="myModelName"></toggle>
My directive is as follows:
.directive('toggle', function() {
return {
restrict: 'AE',
templateUrl: 'views/toggle.view.html',
replace: true,
scope: {
onText: '#',
offText: '#',
ngModel : '=',
},
};
});
However, when looking at the html markup the ng-model attribute has not changed to 'myModelName' and still shows just 'modal' so hasn't updated.
What am I doing wrong?
Thanks all

Try replacing
<input ng-model='model' type="checkbox" class="toggleInput">
with
<input ng-model='{{ngModel}}' type="checkbox" class="toggleInput">
because you want to bind the value of the incoming model name into the ng-model attribute of the template inside the directive.

Figured it out.... used compile in the link function to dynamically add the model to the elements I needed:
return $compile($('input.toggleInput',element).attr('ng-model', model))(scope);

Related

AngularJS client-side validation in directive

I'm using a directive to encapsulate a partial form. There's an enclosing form which passes the model values into the directive. Here's the basic layout:
<form name="userForm" class="form-horizontal" ng-submit="vm.onSubmitForm(userForm.$valid)" novalidate>
<fieldset>
<legend>Account</legend>
<div class="form-group" control-validator="" validator-condition="vm.hasTriedToSubmit">
<label class="col-md-2 control-label">Email (username):</label>
<div class="col-md-10">
<input class="form-control" type="email"
id="email" name="email"
placeholder="Email"
required
ng-model="vm.formData.email">
<control-validator-message>Email is required.</control-validator-message>
</div>
</div>
<!-- some other fields -->
<div ng-if="vm.isUserChecked()">
<!-- directive which is rendered conditionally -->
<dt-user user="vm.user" display-name-fields="false"></dt-user>
</div>
</fieldset>
So the idea is that if the user directive is rendered, some of its fields will be required. This actually works as it is, but I don't get the validation message displayed, nor do I get the error CSS applied to the required fields. I am stopped from submitting the form if a required directive field isn't present, and the fields in the regular parts of the form show the messages and error CSS, but I'm not having luck with those in the directive. Basically I need a way to signal the enclosing form from the directive to trigger the validation.
I think the issue you have is the not the validation, but when to show the errors from the validation, correct? Here is a small example of how I did this
<div ng-controller="subCtrl">
<form name="groupEdit" ng-submit="groupEditSubmit()">
<input required
name="firstName"
ng-class="{ 'highlight-error' : groupEdit.showError &&
groupEdit.firstName.$invalid }" />
<button ng-click="groupEditSubmit()">group edit submit</button>
</form>
</div>
.controller('subCtrl',function($scope) {
$scope.groupEditSubmit = function() {
$scope.groupEdit.showError = $scope.groupEdit.$invalid;
}
});
The problem was a mistake in scope. The validator-condition "vm.hasTriedToSubmit" was part of the outer controller, not the directive's controller. I modified my scope interface to include this value, added it to the scope initializer in the directive, and passed it in where the directive is used.
The interface:
export interface IUserScope extends ng.IScope {
user: UserViewModel;
hasTriedToSubmit: boolean;
displayNameFields: boolean; }
The directive:
var userDirectiveArray = [
(): ng.IDirective => ({
restrict: "E",
replace: true,
scope: {
user: '=',
hasTriedToSubmit: '=',
displayNameFields: '='
},
templateUrl: "/path/user.directive.tpl.html",
controllerAs: 'vm',
controller: UserDirectiveController
})
];
Using the directive:
<dt-user user="vm.formData" has-tried-to-submit="vm.hasTriedToSubmit" display-name-fields="true"></dt-user>
Some checks happen while a submission is attempted, which is where the "vm.hasTriedToSubmit" value is used. It was being evaluated on the outer controller, but in the directive it simply defaulted to "false", so my error feedback wasn't displayed.

Directive template - use attribute for html text interpolation?

Angular 1.*
I am using a directive to make my code drier... or attempting to. But because of variances in the json data structure, I am not sure it's possible because of the interpolation text for each radio button.
<ppv-filter filter-name="Region" radio-value="choice.code"
sort="description" filter-data="regions"></ppv-filter>
<ppv-filter filter-name="Market" display-prop="description"
filter-data="markets"></ppv-filter>
<ppv-filter filter-name="Dealer" display-prop="code"
filter-data="dealers"></ppv-filter>
directive template:
<div ng-if="filterName === 'Region'">
<div ng-repeat="choice in filterData| orderBy: sort">
<input type="radio" value="{{choice.code}}" name="regionRadio">
{{choice.description}}
</div>
</div>
<div ng-if="filterName === 'Market'">
<div ng-repeat="choice in filterData| orderBy: 'code'">
<input type="radio" name="bob">
{{choice.code}}
</div>
</div>
<div ng-if="filterName === 'Dealer'">
<div ng-repeat="choice in filterData| orderBy">
<input type="radio" name="foo">
{{choice}}
</div>
</div>
angular.module('app')
.directive('ppvFilter', ['regionMarketDealerSrv',
function(regionMarketDealerSrv) {
return {
templateUrl: 'app/ppv/ppv-filter.dir.html',
restrict: 'E',
scope: {
filterName: '#',
sort: '#',
radioValue: '#',
filterData: '='
},
Is it possible to pass a attribute binding to take the place, for example, of {{choice.description}}? If not, then I am not really making my code drier by reusing a directive with so many code ng-if blocks.
I would create controller inside Your directive and inside it check properties sended to scope, in this particular example best would be switch statement. So in the switch set which param should be used in view.
( pseudo code in link or controller of directive )
switch (scope.filterName){
case "Market":
scope.field="code";
break;
//other possibilities
}
Next in view we need only one structure by using array syntax [field].
<div>
<div ng-repeat="choice in filterData| orderBy: 'code'">
<input type="radio" name="bob">
{{choice[field]}}<!-- HERE MOST IMPORTANT -->
</div>
</div>
I see that sorting also changes, so create second variable for sort type and assign it in the same switch in controller.
One more thing, directive attributes (props) assigned from parent scope can be used without any controller code, props are available in view, so it can be used in the same syntax like - {{someArr[filterName]}} where filterName was directive attribute.
Returning to Your problem - if we send by attribute name of property which should be used in view for example column:'#' and example value would be code,description then in view only {{choice[column]}} is needed.

AngularJS form validation: indicate required fields to user

I would like my form labels to display a red asterisk next to the label when the corresponding form control has a required attribute.
Instead of hard coding the asterisk, I desire a way to append the asterisk to the label dynamically during page load if the label's corresponding input, select or textarea is required (the element the label corresponds to).
I created the directive below, and the directive works. But is there a better, more native way to accomplish my goal? This directive finds all the div.form-group containers and adds a red * character after the label if the corresponding form control inside the div.form-group has a required attribute.
myApp.directive('labelsRequired',function(){
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, elem, attrs){
elem.find('div.form-group').each(function(i, formGroup){
var formControls = $(formGroup).find('input, select, textarea');
console.log(formControls)
if (0 !== formControls.length && undefined !== $(formControls[0]).attr('required')){
jLabel = $(formGroup).find('label');
jLabel.html(jLabel.html()+ "<span class='red-color'>*</span>");
}
})
}
}
});
The directive assumes all inputs, selects, and textareas are inside a div.form-group container.
<div class='form-group'>
<label>First Name</label><!-- this label gets an asterisk -->
<input name='fname' required />
</div>
<div class='form-group'>
<label>Favorite Food</label><!-- this label does not get an asterisk -->
<input name='favFood' />
</div>
You don't need a directive, there are built-in form properties you can use with filters like ng-show, look:
<div ng-app="myApp" ng-controller="myCtrl">
<form name="userForm" novalidate>
<div class='form-group'>
<label>First Name</label>
<input name='fname' ng-model="fname" required />
<label ng-show="userForm.fname.$dirty && userForm.fname.$error.required">* Required field</label>
</div>
<button type="submit">Submit</button>
</form>
</div>
If you define an ng-model for the input you can deal with it looking if it is filled or not. You can also check it only after the user "dirty" it with userForm.fname.$dirty, so the label will be shown only after a user try to input something but then clear it. Try playing with it here JSFiddle
Building off of Corey's answer:
I just used compile rather than link, as I saw that my required attribute was not being applied to my input elements. I also included a select tag for any dropdowns that I had.
app.directive('inputRequired', function () {
return {
restrict: 'A',
compile: function (elem) {
elem.find('label').append("<sup><i class='fa fa-asterisk'></i></sup>");
elem.find('input').attr('required', 'required');
elem.find('select').attr('required', 'required');
}
};
});
If you're not using the built-in Angular validation, you could restructure your directive and attach it to your .form-group element. Like this:
app.directive('inputRequired', function() {
return {
restrict: 'A',
link: function(scope, elem, attr) {
elem.find('label').append('<span>*</span>');
elem.find('input').attr('required', 'required');
}
};
});
Your HTML would then look like:
<div class="form-group" input-required>
<label>Name</label>
<input name="name" />
</div>
<div class="form-group">
<label>Food</label>
<input name="food" />
</div>
However, if you haven't looked into the built-in validation with Angular, I would recommend using it.
This might come too late and it might not be too elegant but it works, if anyone needs it:
<label ng-show="userForm.fname.$validators.hasOwnProperty('required')">* Required field</label>

AngularJS: Reuse markup for editing many properties of an object

I have an object with a lot of properties (all of type number). I want to edit these properties so for every property I have an example markup:
<div>
propertyA: <input type="number" step="0.1" ng-model="configuration.propertyA" required>
</div>
Plunker
I don't want to repeat the markup for every property. I would like to use ng-repeat or custom directive, but I don't know how to deal with ng-model="...".
Something like:
<div ng-repeat="property in properties">
{{property.???}}: <input type="number" step="0.1" ng-model="property.???" required>
</div>
or custom directive (I know how to transclude static text but what with ng-model):
<my-directive input-value="PropertyA???">PropertyA: </my-directive>
EDIT (maybe will explain more):
I have an configuration object from Server. I don't want to repeat markup I have at the top of the question. I want to have markup once and then loop for every property, so every property will be edited. At the end I want to post configuration back to server.
following the obj you have in your plunkr, it'd just be
<div ng-repeat="item in configuration">
{{item}} <input type="number" step="0.1" ng-model="item" required>
</div>
http://plnkr.co/edit/k1qXyANKRAHJs5drTmXt?p=preview
$scope.configuration = {
propertyA: $scope.(NgModel-Name) <----
}
Instead of value put this $scope, it is called 2 way binding.
And use ng-value to put init value of items. Do you get it ?
It is very easy to do using directive.
app.directive('myInput', function() {
return {
restict: 'EA',
template: '<input type="number" step="0.1" ng-model="value">',
scope: {
value: '=',
},
}
})
Markup:
<span my-input value="configuration.weight"></span>
http://plnkr.co/edit/M9o5A8JYnQeDvhjWDCKU

Angular directive attributes aren't being passed through

I'm trying to shorten my form code by making directives for each element, however my directive is displaying none of what I'm passing to it and the model isn't being bound.
HTML:
<formstring dataBinding="project.title" dataTitle="Title" dataPlaceholder="title" />
directive:
app.directive('formstring', function () {
return {
restrict: 'AEC',
dataBinding: '=',
dataTitle: '#dataTitle',
dataPlaceholder: '#dataPlaceholder',
dataHelp: '#dataHelp',
templateUrl: '/app/js/directives/form/string.html',
};
});
string.html:
<div class="form-group"> 2 <label for="{{dataTitle}}" class="col-sm-2 control-label">{{dataTitle}}</label >
<div class="col-sm-10">
<input type="text" class="form-control" id="{{dataTitle}}" placeholder="{{da taPlaceholder}}" ng-model="dataBinding">
<p ng-show="dataHelp" class="help-block">{{dataHelp}}</p>
</div>
</div>
project is a $scope object that has an attribute 'title'.
What am I missing? Why does this show up at a blank input with none of the attributes filled in and why does the binding not work?
You haven't understood how directives are configured correctly. I suggest you read the documentation, it may help you understand better.
In the meantime, here is what your HTML, directive code and template should look like (there is also a working demonstration on Plunkr):
HTML:
<formstring data-binding="project.title"
data-title="Title Demo"
data-placeholder="title placeholder"
data-help="My help text">
</formstring>
directive:
app.directive('formstring', function () {
return {
restrict: 'E',
scope: {
binding: '=',
title: '#',
placeholder: '#',
help: '#'
},
templateUrl: '/app/js/directives/form/string.html',
};
});
Template (string.html):
<div class="form-group">
<label for="{{title}}" class="col-sm-2 control-label">
{{title}}
</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="{{title}}" placeholder="{{placeholder}}" ng-model="binding">
<p ng-show="help" class="help-block">{{help}}</p>
</div>
</div>
You need to change how you're creating isolate scope:
app.directive('formstring', function () {
return {
restrict: 'AEC',
scope: {
dataBinding: '=',
dataTitle: '#dataTitle',
dataPlaceholder: '#dataPlaceholder',
dataHelp: '#dataHelp'
},
templateUrl: '/app/js/directives/form/string.html',
};
});
Read the doc for details about what an isolate/isolated scope means because it has an effect on the overall scope.
edit:
I didn't notice this additional issue before. Your camel case scope properties become snake cased when you use your directive (see Mobin's answer):
<formstring data-binding="project.title" data-title="Title" data-placeholder="title" />
In your template, however, the properties are still camel cased as you have:
<div class="form-group"> 2 <label for="{{dataTitle}}" class="col-sm-2 control-label">{{dataTitle}}</label >
<div class="col-sm-10">
<input type="text" class="form-control" id="{{dataTitle}}" placeholder="{{da taPlaceholder}}" ng-model="dataBinding">
<p ng-show="dataHelp" class="help-block">{{dataHelp}}</p>
</div>
</div>
This is because the bindings in your template are JSON properties, whereas the attributes when you use your directive's properties are XML properties.
There are minor tweaks I'd apply to your template, for instance id="{{dataTitle}}" can easily break HTML standards that require the id attribute is unique... you probably want to use name="{{dataTitle}}" instead. name could still cause issues, but it wont' break document.getElementById for example.
Also, I'd use ng-bind whenever possible:
<p ng-show="dataHelp" class="help-block" ng-bind="dataHelp"></p>
It should be
<formstring data-binding="project.title" data-title="Title" data-placeholder="title" />
.

Resources