ng-repeat affects the wrong place - angularjs

I have written a custom directive named fRadioButton in AngularJS. As far as I know, ngRepeat directive affects the tag that it's used with. However in my case, ngRepeat behaves strangely. Here are the details:
My directive template:
<label>
<input type="radio" value="fValue"/>
{{fName}}
</label>
My directive's JavaScript file:
directivesModule.directive('fRadioButton', function() {
return {
restrict: 'EA',
replace: true,
scope: {
fName: '#',
fValue: '='
},
transclude: false,
templateUrl: '/directives/f-radio-button.html'
};
});
I use the directive from any page as follows:
<f-radio-button ng-repeat="period in myCtrl.periods track by $index"
f-name="period.name" f-value="{{$index}}""></f-radio-button>
According to the ngRepeat, I expect the genereated HTML to be in the following format:
<label></label>
<label></label>
<label></label>
<label></label>
However, it is generated as follows:
<label>
<f-radio-button></f-radio-button>
<f-radio-button></f-radio-button>
<f-radio-button></f-radio-button>
<f-radio-button></f-radio-button>
</label>
How can I directly duplicate the label tags with ngRepeat? I have tried it with replace=false, but it didn't work either.
I guess that replace=true runs before the ng-repeat. Is there a way to run ng-repeat before the replace=true?

You can pass an array into your directive and render radio items inside:
.directive('fRadioButton', function() {
return {
restrict: 'EA',
replace: true,
scope: {
model: '=ngModel',
options: '=',
fName: '#',
fValue: '#'
},
template: function(element, attrs) {
return '<div> {{model}}' +
'<label ng-repeat="option in options">' +
' <input type="radio" ng-model="$parent.model" ng-value="{{ option.' + attrs.fValue + ' }}" name="' + attrs.name + '" />' +
' {{option.' + attrs.fName + '}}' +
'</label></div>'
}
};
});
Then you could use it like this:
<f-radio-button options="myCtrl.periods" ng-model="selected" f-name="name" f-value="id"></f-radio-button>
Here is a simple demo:
Demo: http://plnkr.co/edit/Xcvt46ljV58513saq7BC?p=info

ng-repeat repeats everything that is inside the element you attach it to
<div ng-repeat="period in myCtrl.periods track by $index">
<f-radio-button f-name="period.name" f-value="{{$index}}"></f-radio-button>
</div>
EDIT
Alternatively
myApp.directive('fRadioButtons', function() {
return {
restrict: 'EA',
replace: true,
scope: {
periods: "="
},
template: '<label ng-repeat="period in periods"><input type="radio" f-name="{{period.name}}" value="{{$index}}"/>{{period.name}}</label>'
};
});
function MyCtrl($scope) {
$scope.periods = [{name: "foo"}, {name: "bar"}, {name: "foobar"}];
}
http://jsfiddle.net/Lvc0u55v/3010/

Related

Concatenating directive attribute to template?

Is there a possible way to use the value of an attribute and use it to concatenate in the directive template
<text-input txt-info="textName" ng-model="pi.medRecNumber"> </text-input>
hcare.directive('textInput', function() {
return {
restrict: 'AE', //attribute or element
scope: {
bindedModel: "=ngModel", // Element Model Data
txtInfo:'#', // Serve as name and id attribute value
},
template:
'<div class="form-group tcell-220" data-ng-class="{true: \'has-error\'}[submitted && formHcare.'+txtInfo+'.$invalid]">'+
'<div class="fg-line">' +
'<input id="'+txtInfo+'" name="'+txtInfo+'" data-ng-model="bindedModel" type="text" class="input-h25 form-control" placeholder="M#####-#" >'+
'</div>'+
'<small class="help-block" data-ng-show="submitted && formHcare.'+txtInfo+'.$error.required">Field Required</small>'+
'</div>',
replace: true,
require: '?ngModel',
link: function($scope, elem){
// LOGIC HERE
}
};
});
I think I got it working..
The thing with directives is that you have the same scope as the parent so you don't really need the 'bindedModel' variable and you can approach the txtInfo attribute with {{}} as we always do in angular.
Here is your code with my fixes to make it working:
hcare.directive('textInput', function() {
return {
restrict: 'AE', //attribute or element
replace: true,
scope: {
//bindedModel: "=ngModel", // Element Model Data
txtInfo:'#', // Serve as name and id attribute value
},
template:
'<div class="form-group tcell-220" data-ng-class="{true: \'has-error\'}[submitted && formHcare.{{txtInfo}}.$invalid]">'+
'<div class="fg-line">' +
'<input id="{{txtInfo}}" name="{{txtInfo}}" data-ng-model="pi.medRecNumber" type="text" class="input-h25 form-control" placeholder="M#####-#" >'+
'</div>'+
'<small class="help-block" data-ng-show="submitted && formHcare.{{txtInfo}}.$error.required">Field Required</small>'+
'</div>',
//require: '?ngModel',
link: function($scope, elem){
// LOGIC HERE
}
};
});
don't forget to initialize the pi object in the controller for it to work:
hcare.controller('AppCtrl', function($scope) {
$scope.pi = {};
});
If you want to make sure it is working you should use the browser's inspect element option on this input field.

ng-model is not being exposed outside of directive

I am using angular accordion directive in a form. Unfortunately, the model inside the input field value could not be exposed or display outside of the directive. Not really sure which part is wrong.
Fiddler link:Click here
Html
<body ng-app="btst">
<h3>BootStrap Accordion Directives</h3>
<form>
<pre>{{ form | json }}</pre> <!-- Not Working -->
<btst-accordion>
<btst-pane title="<b>First</b> Pane">
<pre>{{ form | json }}</pre> <!-- Working -->
Text: <input type="text" ng-model="form.textInput1"><br>
Date: <input type="date" ng-model="form.dateInput1">
</btst-pane>
<btst-pane title="<b>Second</b> Pane">
<pre>{{ form | json }}</pre> <!-- Working -->
Text: <input type="text" ng-model="form.textInput2"><br>
Number: <input type="date" ng-model="form.numberInput2">
</btst-pane>
</btst-accordion>
</form>
</body>
Directive
angular.module("btst", []).
directive("btstAccordion", function () {
return {
restrict: "E",
transclude: true,
replace: true,
scope: {},
template:
"<div class='accordion' ng-transclude></div>",
link: function (scope, element, attrs) {
// give this element a unique id
var id = element.attr("id");
if (!id) {
id = "btst-acc" + scope.$id;
element.attr("id", id);
}
// set data-parent on accordion-toggle elements
var arr = element.find(".accordion-toggle");
for (var i = 0; i < arr.length; i++) {
$(arr[i]).attr("data-parent", "#" + id);
$(arr[i]).attr("href", "#" + id + "collapse" + i);
}
arr = element.find(".accordion-body");
$(arr[0]).addClass("in"); // expand first pane
for (var i = 0; i < arr.length; i++) {
$(arr[i]).attr("id", id + "collapse" + i);
}
},
controller: function () {}
};
}).
directive('btstPane', function () {
return {
require: "^btstAccordion",
restrict: "E",
transclude: true,
replace: true,
scope: {
title: "#",
category: "=",
order: "="
},
template:
"<div class='accordion-group' >" +
" <div class='accordion-heading'>" +
" <a class='accordion-toggle' data-toggle='collapse'> {{category.name}} - </a>" +
" </div>" +
"<div class='accordion-body collapse'>" +
" <div class='accordion-inner' ng-transclude></div>" +
" </div>" +
"</div>",
link: function (scope, element, attrs) {
scope.$watch("title", function () {
// NOTE: this requires jQuery (jQLite won't do html)
var hdr = element.find(".accordion-toggle");
hdr.html(scope.title);
});
}
};
})
There are several issues. First, you have to define the form object on the scope. Since you want to see it outside of the btstAccordion directive (see the <pre> element), you have to define it in a separate controller:
function myCtrl($scope) {
$scope.form = {};
}
and then use that controller in your HTML:
<form ng-controller="myCtrl">
This will make <pre>{{ form | json }}</pre> behave like you expect it to.
Next, you're isolating the scope on your btstAccordion directive, which means that the scope outside of the directive isn't visible inside the directive:
directive("btstAccordion", function () {
return {
restrict: "E",
transclude: true,
replace: true,
scope: {}, // <= isolated scope
You can either
remove the isolate scope
pass the scope.form object as a parameter to the directive
Demo of first approach http://jsfiddle.net/rLksvezd/4/

Bind custom methods and class mentioned in HTML to Directive template

Hi I have created a directive for toggle button.
DIRECTIVE
app.directive('toggleBtn',[function () {
return {
restrict: 'EA',
replace: true,
require: ['name', '^ngModel'],
scope: {
isDisabled: '=',
name: '#',
ngModel: '='
},
template:
' <div class="toggle-switch on off"> ' +
' <input ng-model="ngModel" id="{{name}}" type="checkbox" ng-disabled="{{isDisabled}}" ' +
' hidden=""><label for="{{name}}" ' +
' class="ts-helper"></label> ' +
' </div> '
};
}]);
HTML
<input ng-model="sample1" name="sample1"
type="checkbox" class="someclass" ng-change="doSomething()" toggle-btn>
Directive is working fine, except ng-change. ng-change attribute is added to div, not to input-checkbox.
How to add those attributes to input-checkbox?
Not just ng-change, there can be other attributes also. Like ng-focus, title, ng-click, etc... (ng-click and title will work as it will append to main div, I'm just giving an example here).
Plunkr Demo here
Change your code to this
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope) {
$scope.name = 'World';
$scope.doSomething = function() {
console.log("Do something");
}
});
app.directive('toggleBtn', [function() {
return {
restrict: 'EA',
replace: true,
require: ['name', '^ngModel'],
scope: {
isDisabled: '=',
name: '#',
ngModel: '=',
ngChange: '&'
},
template: ' <div class="toggle-switch on off"> ' +
' <input ng-model="ngModel" id="{{name}}" type="checkbox" ng-change="ngChange()" ng-disabled="{{isDisabled}}" ' +
' hidden=""><label for="{{name}}" ' +
' class="ts-helper"></label> ' +
' </div> '
};
}]);
Demo

AngularJS transcluded nested directive

i'm trying to create an angular directive like this :
<radio-button-group group="myGroup" label="My Group" data-model="myModel.attribute">
<radio-button id="value_1" value="1">Value 1</radio-button>
<radio-button id="value_2" value="2">Value 2</radio-button>
</radio-button-group>
The main goals are reuse and avoid repeating ng-model on every <input type="radio" ng-model="myModel.attribute"> and code duplication in general.
I've written the followings:
formjs.js
var formjsModule = angular.module('hp.formjs', []);
// Directive
formjsModule.directive('radioButtonGroup', function () {
return {
restrict: 'E',
transclude: true,
scope: {
dataModel: '#',
group: '#',
label: '#?'
},
templateUrl: function (elem, attr) {
return 'formjs/radio/radio-group-tpl.html';
}
}
}).directive('radioButton', function () {
return {
restrict: 'E',
require: ['^radioButtonGroup'],
transclude: true,
scope: {
value: '#',
dataModel:'#',
group: '#'
},
templateUrl: function (elem, attr) {
return 'formjs/radio/radio-item-tpl.html';
}
}
});
formjs/radio/radio-item-tpl.html
<input type="radio" name="{{group}}" id="{{group+'_'+id}}" ng-value="value" ng-model="dataModel"/>
formjs/radio/radio-group-tpl.html
{{label}} m: {{dataModel}}
<div class="col-sm-10" id="g_{{group}}" ng-transclude>
</div>
</div>
I'm aware of transcluded scope, but i don't see the elegant way to do this binding.
Help, please!
It has to be directive? Wouldn't this be sufficient?
// HTML template
<div ng-repeat="option in options">
<input type="radio" ng-model="$parent.selected" ng-value="option"> {{ option }}
</div>
...
// Controller
$scope.selected = null;
$scope.options = ['Radio one', 'Radio two', 'Radio three'];
Of course, if you insist, you CAN make it a directive.
Usage in HTML, where options is array like above.
<radio-group group-name="My group"
ng-model="selected"
options="options"></radio-group>
Actual directive
app.directive('radioGroup', function () {
return {
restrict: 'E',
scope: {
groupName: '#',
options: '=',
ngModel: '='
},
template: '<legend>{{ groupName }}</legend>' +
'<div ng-repeat="option in options">' +
' <input type="radio" ' +
' ng-model="$parent.ngModel" ng-value="option"> {{ option }}' +
'</div>'
};
});

radio button directive not binding the ngModel

i am trying to create a generic radioButton directive which will take options from controller for display:
<cs-radio-field options="gender" ng-model="genderValue"></cs-radio-field>
and the options would be like
$scope.gender = { label: "Gender", required:true, valueList: [{ text: "Male", value: "yes" },{text:"Female", value:"no"}] };
the directive is defined as:
app.directive("csRadioField", function () {
var templateHtml = function () {
return '<div ng-form="myform">' +
'<div class="control-group" class="{{options.class}}">' +
'<div class="control-label">{{options.label || "Radio"}} {{ options.required ? "*" : ""}} </div>' +
'<div class="controls">' +
'<div class="radio" ng-repeat="(key, option) in options.valueList">' +
'<label> <input type="radio" name="myfield" ng-value="option.value" ng-model="ngModel" ng-required="options.required" />{{option.text}} </label>' +
'</div>' +
'<div class="field-validation-error" data-ng-show="myform.myfield.$invalid && myform.myfield.$dirty"> ' +
'<div data-ng-show="myform.myfield.$error.required">{{options.label}} is required!!!</div>' +
'</div>' +
'</div>' +
'</div>' +
'</div>';
};
return {
scope: { options: '=', ngModel: '=' },
required: ['ngModel', '^form'],
restrict: 'E',
template: templateHtml,
};
});
but the ngModel is not binding in the parent scope.. mostly because of new scopes being created by ng-repeat.
how to solve this issue?
the plunker link: http://plnkr.co/edit/myS5hXwxjoDEqQI2q5Q7?p=preview
Try this in your template:
ng-model="$parent.ngModel"
DEMO
You're correct that ng-repeat creates child scopes. You're actually binding to child scopes' property.

Resources