Angular directive attributes aren't being passed through - angularjs

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

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.

Udating parent object from isolated scope in directive is not working at all

I have a directive defined like that:
myApp.directive('someDirective', function() {
return {
restrict: 'A',
scope: {
disableButton: '=',
},
templateUrl: 'sometemplate.html',
controller: 'SomeDirectiveController'
}
});
The controller for the directive looks as follows:
mLoan.controller('GiversDirectiveController', function (
$scope,
) {
$scope.checkGiversAmount = function () {
var giversCurrentTotalValue = $scope.stocksTotalAmount - $scope.giversCurrentAmount;
}
this is the directive view
<div class="row">
<div class="col-lg-2">
<label for="customer">Wybierz klienta</label>
<select id="customer"
name="customer"
class="form-control"
ng-model="selectedCustomerId"
ng-options="customer.Id as customer.Name for customer in customers"
ng-change="getGiversFunds(selectedCustomerId)"
required=""></select>
</div>
<div class="col-lg-2" ng-show="customerFunds.length > 0">
<label for="customerFunds">Wybierz fundusz</label>
<select id="customerFunds"
name="customerFunds"
class="form-control"
ng-model="selectedFundId"
ng-options="customerFund.Id as customerFund.Name for customerFund in customerFunds"
ng-blur="updateGiverFund(selectedCustomerId, selectedFundId)"
ng-required="customerFunds.length > 0"></select>
</div>
<div class="col-lg-2">
<label for="giversCurrentAmount">Ilość</label>
<input type="number" min="1"
max="{{giversCurrentAmount}}"
id="giversCurrentAmount"
name="giversCurrentAmount"
class="form-control"
ng-model="giversCurrentAmount"
ng-change="checkGiversAmount(selectedFundId)"
ng-blur="updateGiverStockAmount(selectedCustomerId, giversCurrentAmount)"
required />
</div>
<div class="col-lg-2">
<label for="commission">FEE%</label>
<input type="number"
min="1"
id="commission"
name="commission"
class="form-control"
ng-model="commission"
ng-blur="updateGiverCommission(selectedCustomerId, commission)"
required />
</div>
<div class="col-lg-4">
<label for="minimalCommission">FEE Min</label>
<input type="number"
min="1"
id="minimalCommission"
name="minimalCommission"
class="form-control"
ng-model="minimalCommission"
ng-blur="updateGiverMinimalCommission(selectedCustomerId, minimalCommission)"
required />
</div>
</div>
And a parent view where I'm Using the directive with the button:
<div ng-repeat="giver in givers">
<div givers-directive givers="givers"
givers-current-amount="giversCurrentAmount"
disable-giver-side-add="parent"
stocks-total-amount="stocksTotalAmount">
</div>
</div>
<div class="row">
<div class="col-lg-12 margin-top-10px">
<button id="addGiverSide"
name="saveLoan"
class="btn btn-primary"
ng-click="addGiverSide(AddLoansForm)"
ng-disabled="disableButton">
Dodaj
</button>
</div>
</div>
Now my problem and question ath the same time is how I can update the disableButton from the parent model. Before you say this is a duplicate I've ran through the solutions taht was on stackoverflow mainly this one: How to access parent scope from within a custom directive *with own scope* in AngularJS? and that one: What are the nuances of scope prototypal / prototypical inheritance in AngularJS? but nothing seems to help. What is straneg (or not) I can't use the $parent to get to that. I have to go through: $scope.$parent.$parent path and then it's getting updated. Besides that it looks ugly and strange, it's not working when I'm trying to updated this variable from the parent view. To sum up one effect is working and the other is not. And after changing is the other way around. I've tried to wrap up the disable button into an object and try to do the change but this does not work also. Or maybe I'm doing it wrong. Please kindly help me because I'm running out of ideas. What am I doing wrong?
UPDATE
I'm adding the rest of the directive view to show the concept what I'm trying to achieve here. THere are drop downs wich I want them to be independent. Because without the directive with separated (or isolated) scope the two dropdowns are getting filled by changing the other. I want to change them indepenently. I've managed to achieve thet through out the isolated scope. But now I can't seem to get the disabling button working.
By defining a scope inside your directive, you lock your directive out of the parent's scope. If you want to make use of a parent scope, you should set your scope to true.
You can read more on the matter here: http://www.sitepoint.com/practical-guide-angularjs-directives/
EDIT: Example
myApp.directive('someDirective', function() {
return {
restrict: 'A',
scope: true,
templateUrl: 'sometemplate.html',
controller: 'SomeDirectiveController'
}

Passing value to ng-model in custom directive

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);

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

Resources