Directive template - use attribute for html text interpolation? - angularjs

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.

Related

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

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

idiomatic way of having models and looped forms in angularjs

If I'm building an index page for a blog in AngularJS with comments like this:
<li ng-repeat="post in posts">
<h2>{{post.title}}
<p>{{post.description}}</p>
<h3>Leave a comment</h3>
<form ng-submit="postComment()">
<input type="hidden" value="{{post.id}}">
Name: <input type="text"> <br>
Comment: <textarea></textarea> <br>
<input type="submit" value="Post comment">
</form>
</li>
I can't figure out what the right way is to associate a model with the form to access from the controller's postComment() function. If you use just one model for all the forms, then starting to leave a comment on one post will start to leave a comment on all of them—unless you do some weird stuff with the hidden post.id field, but that starts to feel unidiomatic.
You can pass an object into the function call.
<form ng-submit="postComment(post.id)">
As far as the other inputs, it really depends on the situation, but in this case, the input and textarea is inside an ngRepeat, which creates new scope. For that reason, assigning to a value on the scope will not affect other elements on the page.
<li ng-repeat="post in posts">
<h2>{{post.title}}
<p>{{post.description}}</p>
<h3>Leave a comment</h3>
<form ng-submit="postComment(post.id, commentName, commentText)">
Name: <input type="text" ng-model="commentName"> <br>
Comment: <textarea ng-model="commentText"></textarea> <br>
<input type="submit" value="Post comment">
</form>
</li>
is there any way of taking advantage of the two way communication there? (e.g. add an errmsg field from an AJAX call done in postComment or clear the form on submit?)
I suggest a directive with its own controller. With this approach, the comment model is encapsulated in the directive. The directive needs two pieces of information: 1) the postID 2) what function to call to save the comment. To support showing an error message returned from the server, a callback function is passed along with the comment. This allows the controller to feed the directive any error messages or any other information it might need after a save operation attempt.
app.directive('commentForm', function() {
var template = '<form name="commentForm" '
+ 'ng-submit="save({comment: comment, callbackFn: callbackFn})">'
+ '<h3>Leave a comment</h3>'
+ 'Name: <input type="text" ng-model="comment.name" name="commentName"> <br>'
+ '<span class="error" ng-show="commentForm.commentName.$error.myError">Error</span><br>'
+ 'Comment: <textarea ng-model="comment.text"></textarea> <br>'
+ '<input type="submit" value="Save comment">'
+ '</form>';
return {
restrict: 'E',
template: template,
scope: { postId: '#', save: '&' },
controller: function($scope) {
$scope.callbackFn = function(args) {
console.log('callback', args);
if(args.error.name) {
$scope.commentForm.commentName.$setValidity("myError", false);
} else {
// clear form
$scope.comment.name = '';
$scope.comment.text = '';
}
};
}
};
});
app.controller('MainCtrl', function($scope, $timeout) {
$scope.post = {id: 1};
$scope.saveComment = function(comment, callbackFn) {
console.log('saving...', comment);
// Call $http here... then call the callback.
// We'll simulate doing something with a timeout.
$timeout(function() {
if(comment.name === "name") {
callbackFn( {error: {name: 'try again'}} );
} else {
callbackFn( {error: {}} );
}
}, 1500)
}
});
Use as follows:
<comment-form post-id="{{post.id}}" save="saveComment(comment, callbackFn)"></comment-form>
Note the somewhat odd syntax related to the '&' syntax: when the directive is used in the HTML, you specify arguments for the saveComment() function. In the directive/template, an object/map is used to associate each argument name with its value. The value is a local/directive scope property.
Plnkr. In the plnkr I simulated an AJAX post using a $timeout of 1.5 seconds. If you enter name in the name textbox and click the save button, you'll get an error in 1.5 seconds. Otherwise the form clears after 1.5 seconds.
Depending on how far you want to take this... you could encapsulate your post template into a directive too:
<li ng-repeat="post in posts">
<post=post></post>
<comment-form ...></comment-form>
</li>
You could also put the comment-form inside the post directive template, simplifying the HTML even further:
<li ng-repeat="post in posts">
<post=post></post>
</li>
You could also have a post-list directive, and its template would contain the ng-repeat, reducing the HTML to a single element:
<post-list posts=posts></post-list>
I personally haven't decided how far one should go with custom directives yet. I would be very interested in any comments people have about this.

Resources