Dynamic ng-model for directive re-use - angularjs

I have a page with one controller and three tabs, each tab shows its own ng-grid and a text box for filtering the visible rows (not the built-in filterbox).
I created this simple directive for the filter box that I want to use on each tab (as I have done on other pages).
myDirectives.directive('myGridFilter', function() {
return {
restrict: 'E',
templateUrl: 'filterTemplate.html'
}
});
This is filterTemplate.html:
<label input="filterLabel" for="filterBox">Filter: </label>
<input id="filterBox" type="text" placeholder="type filter here" ng-model="gridOptions.filterOptions.filterText" />
I have created three ng-grids ($scope.gridOptions_1, $scope.gridOptions_2 and $scope.gridOptions_3) with each referencing the appropriate dataset for its tab.
My question is, how can I modify the directive so I can specify the ng-model to be gridOptions_1.filterOptions.filterText vs. gridOptions_2.filterOptions.filterText vs. gridOptions_3.filterOptions.filterText vs. plain old gridOption.filterOptions.filterText?
Here is what I tried, without success...
Added scope: { subgrid: '#subgrid' } to the directive
Changed the template file to have:
ng-model="gridOptions{{subgrid}}.filterOptions.filterText"
ng-model="gridOptions[subgrid].filterOptions.filterText"
ng-model="gridOptions[scope.subgrid].filterOptions.filterText"
Changed the reference to the directive on my page to be <myGridFilter subgrid="_1"></myGridFilter>
I would also like to be able to reuse the directive for the generic gridOptions case on other pages, so I also tried {{subgrid||''}} in the template, but did not go further in trying to accomodate a default case of '' until I figure out how to get the dynamic ng-model working first...
Thanks for your help and suggestions!

I think you can simply add scope: { subgrid: '=subgrid' } to the directive and make sure you call the directive with the attribute: <my-grid-filter sub-grid="gridOptions_1"></my-grid-filter>.
(You tried with #subgrid, but # is for text attributes while = is for actual binding between attributes and directive scope).

Related

Require fields from a directive field type

Reference this example: http://jsbin.com/cenugiziju/edit?html,js,output
I have created a directive: example-directive
This directive is made up of a templateUrl which has a label and an input within this html file. This field is marked as required within vm.formFields.
If you take a peek down within the Form section, you will notice that $valid is already set to true even though the required directive is not populated. I would have expected this to be false.
Is there a way to make Formly require fields that are brought in from a custom directive which has fields on it?
ok
sorry about the mixup and delay.
I have created the answer you were looking for.
because you are entering a directive you actually need to send the options to that directive with the values you need...
this is the working example... this means though you will need to handle all the validations and so on yourself since you are not generating the element via FormalyJS but in your own directive.(make sure there is no other way to do it...)
this is the corrected code with required/ maxlength /minlength:
jsbin
what I actually did was pass the options for the Formly to my directive and add the validations to it
app.directive('exampleDirective', function() {
return {
templateUrl: 'example-directive.html',
scope:{
options:'=options',
ngModel:'=ngModel'
},
link:function(scope, element, attrs){
scope.isRequired = scope.options.templateOptions.required;
scope.minValue = scope.options.templateOptions.min;
scope.maxValue = scope.options.templateOptions.max;
}
};
});
<script type="text/ng-template" id="example-directive.html">
<div class="form-group">
<label for="{{::id}}">{{options.templateOptions.label}}</label>
<input id="{{::id}}" name="{{::id}}" class="form-control" ng-model="ngModel" ng-required="isRequired" ng-minLength="{{minValue}} ng-maxLength={{maxValue}}"/>
</div>
</script>
this is the addition to the template in the vm.formFields
template: ''
so now when you want to add a field you will need to pass the data to the directive and in the directive add the corresponding code...
I am not hugely familiar with Formly, but this is the solution that I can give you
NOTE:
I pass the option item to the directive because this is how FormalyJS constructs it.... you can always use your own... but since it renders after the formly directive, figured it would be easier
EDIT
here is an updated JSBIN
you can see that I had to add to the directive the ngModel... you can also do it by require and then use it, I prefer to do it like this... but you have to pass it to the div defining the directive...
checkout the updated code

Carry over attributes from directive to sub-element

I've got a directive that renders a simple searchbox - its HTML looks as follows:
<div class="search input-group">
<input type="text"
ng-model="text"
ng-change="onChange()"
placeholder="Search here..."
class="form-control">
<span class="input-group-btn">
<button class="btn btn-default glyphicon glyphicon-search"></button>
</span>
</div>
All is well and working, I'm able to use it like this:
<searchbox ng-model="search" />
However, now I would like the searchbox to have autofocus in some cases, and in some cases not, for that, it would be neat to just be able to do:
<searchbox ng-model="search" autofocus />
and have that result in having the autofocus attribute carried over to the <input> tag within the directive. Is this possible? How would I go about doing that? Is there a way to carry over specific attributes over to a specific sub-element?
This is a way: from your directive's link function, read the autofocus attribute and, if it is defined, write it to the <input> using DOM manipulation. (DOM manipulation is OK inside the link function):
link: function(scope,elem,attrs) {
if( angular.isDefined(attrs.autofocus) ) {
var inp = elem[0].querySelectorAll('input');
inp[0].setAttribute('autofocus','autofocus');
}
}
A fiddle demonstrating the principle: http://jsfiddle.net/5yhp2xa0/
Possible catch: I am not sure if HTML's autofocus would work for templates that are inserted to the page "later" (i.e. after Angular route change, when a ng-if is shown etc). If this is the case, then a different solution should be used (could be easy, just call inp[0].focus() instead of inp[0].setAttribute('autofocus','autofocus');).
Since the title of the question is "Carry over attributes from directive to sub-element", let me address the general issue as well:
Attributes are not transferred automatically
If the attribute is non-directive, then techniques similar to the answer above can be used, i.e. manipulate the DOM from the link function. Things can get more complex if the attribute value is dynamic, but the general idea is the same.
If the attribute is a directive things are more difficult. Most probably you will have to use the compile function and manipulate the template of the DOM. In this case however, I would prefer to make the directives cooperate directly using the require configuration, especially with the optional modifier, e.g. require: '?otherAttributeDirective'. Of course this is possible only if you control both directives.
You can use the tab index. I m listing some of the behaviors of tab index as under
The tabindex value can allow for some interesting behaviors .
If given a value of "-1", the element can't be tabbed to but focus can be given to the element programmatically (using element.focus()).
If given a value of 0, the element can be focused via the keyboard and falls into the tabbing flow of the document.
Values greater than 0 create a priority level with 1 being the most important.
Or you can use following javascript code for that.
document.getElementById('txtId').focus();
I would do that programmatically. It feels like you are asking too much of angular to carry the attributes in automatically. The attributes of the directive are available as arguments to the link and compile functions, it should be easy to use the directive template to apply the attribute inside when it's on the outside.
For example, try this:
... directive code
link: function(scope, elem, attrs) {
console.log(attrs.autofocus);
}
You can check the value of autofocus from the attrs like that

Cannot get textarea value in angularjs

Here is my plnkr: http://plnkr.co/edit/n8cRXwIpHJw3jUpL8PX5?p=preview You have to click on a li element and the form will appear. Enter a random string and hit 'add notice'. Instead of the textarea text you will get undefined.
Markup:
<ul>
<li ng-repeat="ticket in tickets" ng-click="select(ticket)">
{{ ticket.text }}
</li>
</ul>
<div ui-if="selectedTicket != null">
<form ng-submit="createNotice(selectedTicket)">
<textarea ng-model="noticeText"></textarea>
<button type="submit">add notice</button>
</form>
</div>
JS part:
$scope.createNotice = function(ticket){
alert($scope.noticeText);
}
returns 'undefined'. I noticed that this does not work when using ui-if of angular-ui. Any ideas why this does not work? How to fix it?
Your problem lies in the ui-if part. Angular-ui creates a new scope for anything within that directive so in order to access the parent scope, you must do something like this:
<textarea ng-model="$parent.noticeText"></textarea>
Instead of
<textarea ng-model="noticeText"></textarea>
This issue happened to me while not using the ng-if directive on elements surrounding the textarea element. While the solution of Mathew is correct, the reason seems to be another. Searching for that issue points to this post, so I decided to share this.
If you look at the AngularJS documentation here https://docs.angularjs.org/api/ng/directive/textarea , you can see that Angular adds its own directive called <textarea> that "overrides" the default HTML textarea element. This is the new scope that causes the whole mess.
If you have a variable like
$scope.myText = 'Dummy text';
in your controller and bind that to the textarea element like this
<textarea ng-model="myText"></textarea>
AngularJS will look for that variable in the scope of the directive. It is not there and thus he walks down to $parent. The variable is present there and the text is inserted into the textarea. When changing the text in the textarea, Angular does NOT change the parent's variable. Instead it creates a new variable in the directive's scope and thus the original variable is not updated. If you bind the textarea to the parent's variable, as suggested by Mathew, Angular will always bind to the correct variable and the issue is gone.
<textarea ng-model="$parent.myText"></textarea>
Hope this will clear things up for other people coming to this question and and think "WTF, I am not using ng-if or any other directive in my case!" like I did when I first landed here ;)
Update: Use controller-as syntax
Wanted to add this long before but didn't find time to do it. This is the modern style of building controllers and should be used instead of the $parent stuff above. Read on to find out how and why.
Since AngularJS 1.2 there is the ability to reference the controller object directly instead of using the $scope object. This may be achieved by using this syntax in HTML markup:
<div ng-controller="MyController as myc"> [...] </div>
Popular routing modules (i.e. UI Router) provide similar properties for their states. For UI Router you use the following in your state definition:
[...]
controller: "MyController",
controllerAs: "myc",
[...]
This helps us to circumvent the problem with nested or incorrectly addressed scopes. The above example would be constructed this way. First the JavaScript part. Straight forward, you simple do not use the $scope reference to set your text, just use this to attach the property directly to the controller object.
angular.module('myApp').controller('MyController', function () {
this.myText = 'Dummy text';
});
The markup for the textarea with controller-as syntax would look like this:
<textarea ng-model="myc.myText"></textarea>
This is the most efficient way to do things like this today, because it solves the problem with nested scopes making us count how many layers deep we are at a certain point. Using multiple nested directives inside elements with an ng-controller directive could have lead to something like this when using the old way of referencing scopes. And no one really wants to do that all day!
<textarea ng-model="$parent.$parent.$parent.$parent.myText"></textarea>
Bind the textarea to a scope variable's property rather than directly to a scope variable:
controller:
$scope.notice = {text: ""}
template:
<textarea ng-model="notice.text"></textarea>
It is, indeed, ui-if that creates the problem. Angular if directives destroy and recreate portions of the dom tree based on the expression. This is was creates the new scope and not the textarea directive as marandus suggested.
Here's a post on the differences between ngIf and ngShow that describes this well—what is the difference between ng-if and ng-show/ng-hide.

AngularJS required radio buttons needs two click events to be valid

I have a very simple form where a radio button is required to be selected in order for a form to be valid. The radio buttons are generated by ngRepeat.
As you can see from this fiddle, while the desired behavior is that when the radio button is clicked for the first time, that should validate the form (being the only element), however notice that it takes an additional click (on the same radio button or any other) to validate the form:
http://jsfiddle.net/Xsk5X/3/
What am I missing?
All the other solutions are work-arounds: All you have to do is remove the name attribute, when you use the ng-model attribute you don't need it and they conflict.
Specifying the name causes Angular to get confused because it changes the value once for the angular model and another time for the form element name.
I had this problem because a colleague had copied the radio buttons in the same page and hidden them for temporary reference, so duplicate radio inputs with the same name
Try adding the ng-click attribute to your radio button input.
Credit to Manny D for noting this first. Yes, this is a little hackish, but it works. E.g.,
<input type="radio"
name="groupName"
ng-model="editObject.Property"
ng-value="someValue"
ng-click />
The reason why this is breaking - is because you're setting all radio boxes to be required. As a result, depending on how you write it - angularjs is saying it's invalid because not all have been selected at some point.
The way around this is to do something like the following:
Using checkboxes and required with AngularJS
(check the 1st and 2nd answers). This will resolve your problem.
Seems like an AngularJS 1.0.3 $scope.$apply update problem.
Tested your exact Fiddle in 1.0.2 (check it out yourself) and it works the way you expect it too.
It doesn't seem like there's anything wrong with your code, just that $scope.$apply(or $digest) isn't working as expected on the first select.
A very simple fix is to force the $scope to also update on every select, try changing the following line of code in your form:
<p>Favorite Beatle</p>
change it too:
<p>Favorite Beatle: {{name}}</p>
And you will see how myForm.$invalid is updated even after the first click.
I would try it out with the latest AngularJs version and let us know if that happens there too.
Another solution I can think of it setting the default selected radio, which will cause myForm.$invalid to be false from the beginning. you can do this by adding the following line in your controller code:
$scope.name = "John";
or any default name you want.
Some times the $digest cycle dosen't $apply(fn) because you have two o more instances.
For fix this you need $apply this trick manually, so put this in your directives:
angular.('myApp',[])
.directive('ngRadioExtend', ['$rootScope', function($rootScope){
return {
require: 'ngModel',
restrict: 'A',
link: function(scope, iElm, iAttrs, controller) {
iElm.bind('click', function(){
$rootScope.$$phase || $rootScope.$apply()
});
}
};
}])
and use it as:
<input type="radio" name="input_name" ng-model="some" value="F" ng-required="true" ng-radio-extend>
<input type="radio" name="input_name" ng-model="some" value="M" ng-required="true" ng-radio-extend>
DONE it's the correct way!
The problem of the scope not getting updated still occurs in 1.1.5
A simple work around is to just add
<span ng-show="false"> {{name}} </span>
Fiddle: http://jsfiddle.net/jonyschak/xaQJH/
For IONIC v1,
add name="" to prevent ionic auto-generate attribute name.
Then, I can change the selected item with only one click.
<ion-radio class="label-ticket"
ng-repeat="topic in vm.listTopic track by $index"
ng-value="topic"
ng-model="vm.topicSupport"
name="">
{{ topic.title }}
</ion-radio>

Using {{$index}} in compiled directive within ng-repeat (jQuery UI buttonset)

In an ng-repeat list, I'm having a terrible time putting an ON/OFF button (using JQ UI wrapping radio buttons) for each item in the list.
When using radio buttons, it seems JQ UI buttonset needs both the "input" and "label" tags plus also the 'for' of the label must match the 'id' of the input.
I can use {{$index}} to make them unique, like this:
<label for='algoOn{{$index}}'>ON</label>
<input type='radio' [... blah blah ..] id='algoOn{{$index}}'>
The problem is calling $().buttonset() once the DOM is ready. I've tried various things (dom.ready, link function etc), but had to resort to calling it after a delay [ $('.buttonme').buttonset() ] to trigger all buttons on the page. Hacky.
However, I'd like to wrap the on/off button in a directive. Still have the same problems with needing unique IDs. (If you don't have unique IDs the buttons get bigger and bigger on each successful call in the directive's link function)
BUT... using {{$index}} in the template gives me a mysterious syntax error:
Syntax error, unrecognized expression: [for=on{{$index}}] <onoffbtn prop="win.runstate" class="ng-isolate-scope ng-scope">
(even though code doesn't have 'for=on{{$index}}' in it!)
The directive is the preferred approach but can't figure out how to get around this one.
Secondly, in the directive, all radio buttons are in sync after the first click, but when the page first loads the buttons in the directive are both blank. It doesn't set itself to the model right away. I thought to do that in the link function (eg. element -> find the input -> set the value) but angular has re-written all of the 'names' and 'ids'.
Plunker showing both issues is here: http://plnkr.co/edit/DTy8dGsRDVVDnWZBYlqQ
Thanks!
Like you said this is doable from a directive. Using your html, I just added buttonset to the wrapping div:
<div id='A{{$index}}' buttonset>
<label for='algoOn{{$index}}'>ON</label>
<input class="buttonme" type='radio' name='onoff{{$index}}' ng-model='win.runstate' ng-name='onoff' value='running' id='algoOn{{$index}}'>
<label for='algoOff{{$index}}'>OFF</label>
<input class="buttonme" type='radio' name='onoff{{$index}}' ng-model='win.runstate' ng-name='onoff' value='stopped' id='algoOff{{$index}}'>
</div>
Here is how the buttonset directive looks
angular.module('button', [])
.directive('buttonset', function() {
return function(scope, elm, attrs) {
$(function(){
$(elm).buttonset();
});
};
});
Here is the plunker, no more hacks :)
Update:
The errors you are getting have to do with the fact that the dwbuttonset directive is executing before the code is compiled by angular. Therefore, what you need to do is to wait until this has been done. You can use $timeout with a 0 value (see this question) in order to queue your method until everything has been loaded.
Example:
.directive('dwbuttonset', function($timeout){
return function(scope, elm, attrs) {
$timeout(function(){
$(elm).buttonset();
});
}})

Resources