How to set ngModelOptions by javascript? - javascript

I'm creating a custom directive, and this directive will needing handle ngModelOptions of element, maybe I can do this changing element html with jQuery selector, but I wanted to know if can I do this by javascript for better consistency
UPDATE 1
Because I will need set ngModelOptions="{updateOn:'blur'} for all input in my form
So I want create a directive for do this, likely <input type='text' updateBlur/>
I wanted to know if is possible set this options with code, likely this
myElement.attrs.ngModelOptions.updateOn = "blur default";

You have to do it in the "angular way", so you should do something like this in your directive:
angular.module("myApp").directive('myDirective', function($compile){
link: function(scope, element, attrs) {
scope.$watch( 'scope.someValue', function(){
element.attr('ng-model-options', "new value");
$compile(element.contents())(scope);
});
}
});
In this way you might check on the change of scope.someValue, and apply the new value to the directive. You cannot do this with jquery, because angularJS won't get the changes.
But, using this $compile(element.contents())(scope); will do the trick.
EDIT: $scope.apply(), shouldn't work. Because it's necessary to force a digest cycle (such as more elements are added to an array). Here you're changing the structure, so you need to re-compile it

Please be more specific on your problem.
The answer for the general question is that you should not use jQuery selectors for manipulating the DOM if you are using angular.
If what you need is to manipulate the ngModelOptions attribute, you should do that from within your directive, using the arguments of the link function:
link: function(scope, element, attrs){
...
//if you want to change the ngModelOptions attribute
$(element).attr('ng-model-options', 'whatever')
}
For a more detailed explanation:
https://docs.angularjs.org/guide/directive

Related

Add DOM element when transcluding except for the last one child

I would like to add a <hr> element between each clone of my transclude function except for the last one clone.
The parent directive have a custom HTML template.
In this template, I call a Attribute directive to transclude the elements.
Sample:
function directiveTransclude() {
return {
link: function (scope, element, attr, ctrl, transclude) {
transclude(function (clone, scope) {
element.append(clone);
});
}
};
}
This directive help me to manually handle the transclude.
I just have to figure out now how to conditionnally add the <hr>.
I assume to do that I need to add for example element.append('<hr>'); after the first append.
So how could I know how many elements the transclude have to append ?
Is there a $last value or something to tell me that this is the last loop ?
Thanks for the help !
The controller has been instantiated by the time the link function runs, so I would just keep track of it in a controller attribute.
I'm assuming in the below example that you're passing this directive a collection to iterate over, or at least a count of number of elements you want. I'll also assume that argument is named collection, and that you're using bindToController so that this directive binding is accessible on the controller by this point. Finally, I'm assuming you've also attached an attribute to your controller named iterationCount.
function directiveTransclude() {
return {
link: function (scope, element, attr, ctrl, transclude) {
ctrl.iterationCount++;
transclude(function (clone, scope) {
element.append(clone);
if(ctrl.iterationCount < (ctrl.collection.length - 1)) {
element.append('hr');
}
});
}
};
}
Although, my suggestion would be to do this with css, if <hr> isn't some sort of strange requirement. Using a border and the last-child selector would be MUCH simpler.
Just in case you wonder:
It's late but I just find the solution by using an alternative (with CSS).
As mentioned by #Aaron Pool, I can use last-child selector to achieve that.
the last-of-type selector is finicky, and the last-child selector is actually applied to the child
It's better (faster than DOM injection and safer visually speaking, because <hr> could easily get edited by CSS rules).
I already tried that nevertheless it didn't works as expected.
Despite, today I figure out that my LESS code was that:
&:last-child
Instead of
:last-child
So I was focusing the wrong DOM element, sadly.
I'm happy even if now I feel so dumb.
Greetings and happy new year !

Angularjs - is there a way to programatically click on an HTML <select> element?

I've seen a javascript solution that goes a little something like this:
var select = document.getElementById('selectId')
select.click();
Is there an AngularJS approach/best practice to the same thing? (Off the top of my head, you'd wrap the above code in an ng-click)
Yes there is. Here's the angular equivalent of what you have in JavaScript
angular.element('#selectId').trigger('click');
Working example
Any DOM manipulation in angular should occur inside of a directive.
View
<div id="selectId" clickMe>content</div>
Inside of a directive the link function triggers after the view is compiled. The second parameter in the link function is the element which the directive is placed on, this gives performance benefits since there is no need to traverse the dom. It is a JQlite element which you can directly call methods on.
Directive
app.directive('click-me', function(){
return{
link(scope, el, attr){
$(el).trigger('click');
}
}
});

trying to add click inside a re-usable directive to a element that may have different classes be different element and may have different text

so here is the desire: I need to add a directive to any html element and pass it an id. This id is then used to populate a method that submits a form that has that id on the page.
Some devs have used a button some have used an anchor tag so I do not want the type of element to be part of the method. It is intended to be highly re-usable.
I already have it working like this:
angular.module("App").directive("submitform", submitFormDirective);
function submitFormDirective(){
return {
restrict: 'A',
scope: true,
link: function($scope, $element, $attrs){
var selector = $attrs.submitform;
$element.on('click', function(){
angular.element(selector).submit();
});
}
}
}
But this uses jquery for the "on" method. I want a fully angular method. Or one that does not "fight" with the framework or use jquery.
The way I am doing it now I only need to apply this attribute to the element. submit-form="#id-of-form" And I would like to keep that functionality becasue it is clean and concise. So I need to be able to add the ng-click to the element dynamically without using the limited template function which would limit the element being effected, inside the submitFormDirective.
The problem is that the $attrs.$set('ngClick', 'submitForm()') runs after the digest and $compile does not seem to work for me. I need to be able to add the click and run the compile on only this element or use a different phase that will happen before the app compile will run. But I have tried using compile to att the attr in conjunction with link adding the method to the scope to no avail. Seems strange that you would not be able to add a event from a directive. I must be missing something. Can someone help?

Getting element width inside directive the Angular way

I have a directive which contains an element inside it with width: 100%. However, the directive must know the clientWidth of that element -- that is, in pixels rather than percent. The element is buried deep in the directive's DOM.
The containing controller may have multiple instances of this directive, so doing document.getElementsByClassName('myclass')[0].clientWidth won't work. Selecting the directive's element works, but the code for that looks similar to element.children().children().children()[32].clientWidth, which doesn't look very stable. What is the right, Angular way of doing this?
Whenever you need to do any DOM manipulation in Angular, directives are the way to go. The "Angular" approach would be to use your directive's link function.
DEMO: http://plnkr.co/edit/83aZUmsRzuTUFotxUYSB?p=preview
Template Use Case:
<div element-width></div>
Directive Example:
angular.module('myApp').directive('elementWidth',function(){
return {
restrict: "A",
link:function($scope, element){
// element param being passed in refers
// to the element the directive is bound to, in this case, the DIV
// do something with the element width here
}
}
});
Hope this helps and let me know if you have any questions.
I figured it out. Although single elements do not have .getElementsByClassName, you can still use .querySelector. So my finished code was:
element[0].querySelector('.myclass').clientWidth

Angular.js directive transclude ngRepeat - error

I'm trying to create a chart directive ( not interested in existing solutions ). I want to have definable templates for each bar using a sub-directive of bartemplate. I want to iterate the bartemplate so I thought using a template with ng-repeat and ng-transclude would do the trick ... instead angular threw an error.
Error: [ngTransclude:orphan] Illegal use of ngTransclude directive in the template! No parent directive that requires a transclusion found. Element: <div ng-repeat="bar in bars track by $index" ng-transclude="" class="ng-scope">
http://jsfiddle.net/jt4Y2/7/
So you may ask, why the transclude? I want to be able to include any element I add to the chart (ie. a label ) and not interfere with the <bartemplate>.
You have several problems with your code. First you are complicating it by doing transclusions withing transclusions within transclusions. Second, you are combining directives that already do transclusions such as ng-repeat and trying to transclude within it, which just won't work.
I have provided a simplified version of your code that does what you want:
http://jsfiddle.net/jt4Y2/6/
Let me try to explain what it is doing. First we have the barchart directive. I took the liberty of simplifying but just making the entire body act as the template for a bar rather than having a bar-template directive.
Because we have set transclude: true the actual contents inside the barchart div are stripped out of the dom, but made available through a transclude function that you can inject into the controller via the $transclude parameter. We take this function and save it on our controller, so we can access it later on.
The barchart directive is then replaced by this template:
<div xmlns="http://www.w3.org/2000/svg">
<div ng-repeat="bar in bars track by $index" render-bar></div>
</div>
Notice that there is no ng-transclude. Instead we create our own directive render-bar to render the template (this avoids any conflicts with ng-repeat and it's own transclusion).
The renderBar directive is pretty simple:
directive('renderBar', function(){
return {
require: '^barchart',
link: function(scope, element, attrs, controller){
controller.renderBarTemplate(scope, function(dom){
element.append(dom);
});
}
}
});
First, we require that there be a parent barchart directive. In the link function, we can then get its controller and access the stored transclude function which we named renderBarTemplate.
The first parameter we pass is the scope, which makes the function link against the current scope (basically the scope provided by ng-repeat which gives us access to the iteration variables including bar).
The compiled dom is returned via the a callback (the second paramter), which we can then attach to the <div> provided by ng-repeat.
Let me know if you have any questions or need additional clarification.
UPDATE:
Here's a version that let's you keep the <bartemplate> directive:
http://jsfiddle.net/jt4Y2/10/
I added the bartemplate directive:
directive('bartemplate', function(){
return {
transclude: true,
restrict: 'E',
require: '^barchart',
link: function(scope, element, attrs, barChartController, transclude){
element.remove();
barChartController.renderBarTemplate=transclude;
}
};
})
It is now responsible for taking its transclude function and storing it in the parent's controller. (so it can later be use by the render-bar directive.
Also notice the element.remove(). This is not absolutely necessary but is simply removes the remaning <bartemplate> tag from the html.

Categories