Using a reusable directive for form inputs - javascript

I just read through the directive docs and I'm still not understanding how I'd accomplish the following with reusable code. I have multiple form fields that would best be used in a select > option setup, however I'm looking to replace that with a directive with a template because of how mobile browsers handle selects (iOS magnifies the options and some of my values are too long to be viewed in the display).
So my template would look something like this:
<div class="list">
<div class="option" ng-repeat="option in form.questionOneOptions" ng-click="selectOption(option)">
{{option}}
<i class="checkIcon" ng-if="option.isSelected"></i>
</div>
</div>
This would be the only thing on the detail page. Its parent page being the list of fields you're filling out, which is where the data needs to be available. I just don't know where to start on the directive. There is an isolated scope for each question, which holds the options for that question. There needs to be a way to give the directive the list of options. There is an encompassing scope for all the questions to keep the recorded answers in a single object like form.
I know I could do this with a single controller and copy/pasting the above and changing form.questionOneOptions with one massive object living in the controller, but I'm trying to do this the right way by limiting my DOM manipulation to directives.

You'll want to use the html you have there as the template for your directive. Then you implement selectOptions in your link function.
app.directive('gsQuestion', function() {
return {
restrict: 'E',
require: 'ngModel',
scope: {
ngModel: '=',
options: '='
},
template:'<div class="list">'+
'<div class="option" ng-repeat="option in options" ng-click="selectOption(option)">'+
'{{option}}'+
'<i class="checkIcon" ng-if="option.isSelected"></i>'+
'</div></div>',
link: function(scope, element, attrs) {
scope.selectOption = function(option)
{
// implement selectOption
}
}
};
});
Then you can use the directive in your html.
<gs-question ng-model="myValue1" options="form.questionOneOptions"></gs-question>
<gs-question ng-model="myValue2" options="form.questionTwoOptions"></gs-question>

Just to be clear, a directive can share data to/from the view through the use of the directive's $scope variables.
angular.module('app', [])
.directive('mySampleDirective', function(){
return{
restrict: 'AE',
scope: {
data: '=' // this sets up a two way binding
}
},
link: function(scope, element, attributes){
console.log(scope.data) // <---- this is where you would do DOM manipulation,
// because you have access to the element.
}
})
And then in your markup, pass in the data you want to make available in your directive.
<my-sample-directive data="FeeFee"></my-sample-directive>

Related

AngularJS : DOM manipulation of ng-repeat elements in a directive

I am new to angularJS. I am trying to implement the a simple functionality wherein there are a number of checkbox options and one specific function needs to be called on click of these checkbox options something similar to
$(parentDiv).on("click",'input[type="checkbox"]', functionToBeCalled});
I am trying to use a directive for this section with content similar to
<div>
<span ng-repeat="subsection in section"><input type="checkbox" value="subsection.name"></input> {{subsection.name}} </span>
</div>
Directory
app.directive('myDir', function() {
return {
restrict: 'AE',
replace: true,
templateUrl: 'filter.html',
link: function(scope, elem, attrs) {
elem.find("input").bind('click', function() {
//do some data manipulation
});
});
};
});
When i use the link function I find that if I use elem.find('input[type="checkbox"]) gives me an empty object so it is not compiled before that. Kindly let me know how to address this situation. I did a bit of a research and found out about compile function which manipulates DOM before linking,but i am not able to think of the approach ahead. Any help will be appreciated. Thanks in advance.
Use ngClick or ngChange Directive inn angularjs
<div>
<span ng-repeat="subsection in section">
<input type="checkbox" value="subsection.name" ng-click="myFunc(subsection.name)" /> {{filter.name}} </span>
</div>
here in the example i used ng-click = "myFunc" and pased the value in that function
It might help for us to know how you directive looks like and specifically what element that directive is attached to, since you're speaking about elem.
Nevertheless, if you just need functions to be called when the checkbox is clicked, you can use built-in angular functions for this:
ngChange
ngClick

AngularJS custom directive with input element, pass validator from outside

I'm using a simple custom directive for a modified input field which occurs throughout my application:
app.directive('editor', function() {
return {
restrict: 'E',
templateUrl: 'editor.html',
scope: { value: '=' }
};
});
The editor.html basically creates an input element with additional controls. Simplified it looks like this:
<div>
<input ng-model="value">
<!-- more code here -->
</div>
I access my directive using <editor value="{{object.name}}"></editor>. This works perfect. Now I need to perform different validations on the input. The necessary validators to use vary, so I would like to be able to pass the actual validators to my directive. Something like:
<editor value="{{object.name}}" validator-a validator-b></editor>
or
<editor value="{{object.name}}" validators="validatorA,validatorB"></editor>
How could I achieve that?
You are creating a custom input control, so you might as well support ng-model - which is the right thing to do.
And, validators - built-in and custom - only require: "ngModel" for their function and they are independent (by design) from the underlying DOM implementation, so having ng-model automatically supports all ng-model-based validators.
Having ng-model support will also make your directive participate in form validation.
Since you are using an existing directive inside the template (i.e. <input>), you don't even need to bother with DOM, as you would've had you built something from scratch.
app.directive("editor", function(){
return {
require: "?ngModel",
scope: true,
template: "<input ng-model='value' ng-change='onChange()'>",
link: function(scope, element, attrs, ngModel){
if (!ngModel) return;
scope.onChange = function(){
ngModel.$setViewValue(scope.value);
};
ngModel.$render = function(){
scope.value = ngModel.$modelValue;
};
}
};
});
Then you can just apply validators directly:
<editor ng-model="foo" minlength="3"></editor>
http://plnkr.co/edit/k21Oem6kT8SXUefyhbI6?p=preview

Angular Expressions Missing in Dynamically Generated Template

I am having problems with a dynamically generated directive (using a function) and angular expressions being filtered out in the final markup ending up on the screen. I've created a JSFiddle displaying the problem here.
I am generating the template dynamically because we needed a way to generate the markup based on the attributes inside the element.
Basically when defining an expression like so:
angular.module('ui.directives', []).directive('uiBar', function() {
return {
restrict: 'E',
template: function(element, attrs) {
console.log('hello');
return '<div>lol: {{ user }}</div>';
}
};
});
And place it into the body like so:
<div ng-app="myApp">
<ui-bar>I should change to iambar</ui-bar>
</div>
The resulting markup is:
<div ng-app="myApp" class="ng-scope">
<ui-bar><div class="ng-binding">lol: </div></ui-bar>
</div>
The expression is stripped out for some reason. Has anyone experienced this?
I do not quite understand what you are trying to accomplish with a dynamically generated template. Below is what I would typically do to solve your example.
The HTML would look like this.
<ui-bar new-value="hello">I should change</ui-bar>
The return statement of the directive would look something like this
return {
restrict: 'E',
template: "<div>lol: {{ user }}</div>",
link: function (scope, element, attrs){
scope.user = attrs.newValue;
} // end link
} // end return
The above code works in your jsFiddle.
If you could add more information we may be able to give more examples.

How to use ng-bind on html that is inserted with innerHTML inside a directive

I made a custom directive that had two ng-repeats inside of it. One ng-repeat was nested inside the other one. I got the code to work, and it performs well in chrome but on the iPad and iPhone it is sluggish.
There are 10 sections with 5 rows each and it needs to be very fast when it comes to scrolling and changing the bindings. I think the slowdown comes from all the loops through the bindings but only one array needs to be changed on user input. The rest of the bindings never change after the page loads.
So I am trying to figure out a way to load nested unordered lists while only binding one variable. This is pseudo code for my directive.
.directive('myDirective', function($compile) {
return {
restrict: 'A'
link: function(scope, elm, attrs) {
outerList = '<ul><li>statically generated content that does not change'
outerList += '<ul><li ng-bind="I only need to bind one thing"><li></ul>'
outerList += < /ul>'
elm[0].innerHTML = outerList
}
}
});
As you can see I am trying to generate the html content, and then insert it with innerHTML. The problem is the ng-bind isn't working when I do it this way. I tried to $compile it again but that didn't change anything.
Does anyone have a better way? I don't care how hideous the solution is I just really need this part of the app to be super fast. The main thing is I don't want ng-repeat unless there is a way to make it do its thing on load and then never loop through anything again.
I would like to do this in the most Angular way possible but I realize I might have to do something that goes completely against Angular philosophy
Here is an example of how to modify your code in order to bind some variable in a directive from a scope outside of it. I've used $compile to ensure that your directive DOM manipulation has its own directives compiled. I've used replaceWith to replace the directive element with your compiled DOM:
HTML
<div ng-app="myApp">
<div ng-controller="ctrlMain">
<div my-directive="bindMe"></div>
</div>
</div>
JavaScript
var app = angular.module('myApp',[]);
app.controller('ctrlMain',function($scope){
$scope.bindMe = {id:1,myvar:"test"};
});
app.directive('myDirective', function($compile){
return{
restrict: 'A',
scope: {
varToBind: '=myDirective'
},
link: function(scope, elm, attrs){
outerList = '<ul><li>statically generated content that does not change'
outerList += '<ul><li ng-bind="varToBind.myvar"><li></ul>'
outerList += '</ul>';
outerList = $compile(outerList)(scope);
elm.replaceWith(outerList);
}
}
});
Here is a demo

Validation messages into Directive - AngularJS

I'm trying do a small reusable component in AngularJS using directives.
I have made good progress but I have a problem with the validations. For example the required validation not working. I think is "binding" issue.
My HTML code: also in http://jsfiddle.net/pQwht/17/
<html ng-app="myApp">
<body>
<form ng-controller="Ctrl"
id="paymentCallForm"
action="#"
name="paymentCallForm">
<table>
<tr tdfield
labelname="Primary Account Number:"
fieldname="primaryAccountNumber"
title="Primary title"
>
</tr>
</table>
My directive script:
angular.module('myApp').directive('tdfield', function() {
return {
restrict: 'A',
replace:false,
transclude: false,
scope: { labelname: '#', fieldname: '#', title: '#'},
templateUrl:'element.html'
};
});
My element.html code:
<td id="lbl_paymentReference" class="formInputLabelWrapper">{{labelname}}</td>
<td class="formInputTextWrapper">
<input id="{{fieldname}}"
name="{{fieldname}}"
title="{{title}}"
class="large empty"
required>
<span data-ng-show="paymentCallForm.{{fieldname}}.$error.required"
class="error">Error</span></td>
Well, I solved this, but for what a price. There is a number of issues and angular related among them. I may not recall all, so here is the working example https://github.com/yaroslav-ulanovych/soq16245177.
When you define scope parameter like scope: { labelname: '#', fieldname: '#', title: '#'}, (with an object as a value), that creates an isolated scope, which means not inherited from parent one's. Therefore here ng-show="paymentCallForm.{{fieldname}}.$error.required" is no access to the form. As a workaround ng-show="$parent.paymentCallForm.{{fieldname}}.$error.required", but I didn't check whether your inputs are published in the form in case of the isolated scope. Or scope: true and inject attributes into the scope manually.
compile: function() {
return {
pre: function (scope, element, attr) {
scope.fieldname = attr.fieldname;
}
}
}
Note on using prelink function, so that it's called before children are linked.
Next ng-show will actually use not interpolated expression and there is obviously no property named {{fieldname}} in the form. That is fixed in later versions of Angular, don't know when exactly, cause I'm using master.
But what is not fixed is ngModelController. It gets it's name very early so publishes itself on the form under wrong one. I had to fix that myself, good that there is only one line to do that, in file src/ng/directive/input.js.
// add
modelCtrl.$name = attr.name;
// before this
formCtrl.$addControl(modelCtrl);
I believe you need a controller attached to your view. The form object will be attached to property with the id of the form on $scope object of this controller. Once you add that, I think it will start showing up.

Categories