radio button directive not binding the ngModel - angularjs

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.

Related

Angular wrapper component takes additional directives

I have a directive that looks something like the following:
.directive('textInput', function() {
return {
restrict: 'E',
replace: true,
transclude: false,
compile: function(element, attrs) {
var html =
'<div class="form-group">' +
'<label>{{ \'' + attrs.label + '\' | translate }}</label>' +
'<input type="text" class="form-control input-sm" placeholder="' + attrs.placeholder +
'" ng-model="' + attrs.ngModel + '"' + attrs.directives + '>' +
'</div>';
var newElem = $(html);
element.replaceWith(newElem);
return function (scope, element, attrs, controller) { };
}
};
})
Note that the directives attribute was a string that added additional attribute information (e.g. tooltip info)
I have converted it to a 1.5 component but I'm not able to do the same with the directives definition.
.component('textInput', {
bindings: {
label: '#',
placeholder: '#',
directives: '<',
ngModel: '='
},
require: {
ngModelCtrl: 'ngModel'
},
template:
'<div class="form-group">' +
'<label ng-if="$ctrl.label">{{$ctrl.label | translate }}</label>' +
'<input' +
' type="text"' +
' class="form-control input-sm"' +
' placeholder="{{$ctrl.placeholder}}"' +
' ng-model="$ctrl.ngModel"' +
' {{$ctrl.directives}}>' +
'</div>'
})
<text-input directives="tooltip='foobar'"></text-input>
How can I pass in a string to the <text-input/> element such that the underlying template gets the correct information?
It is not possible, according to the docs,:
When not to use Components:
for directives that rely on DOM manipulation, adding event listeners
etc, because the compile and link functions are unavailable when you
need advanced directive definition options like priority, terminal,
multi-element when you want a directive that is triggered by an
attribute or CSS class, rather than an element
and in the same page it says that components doesn't have compile function.

ng-repeat affects the wrong place

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/

Directive to show error messages

I have validation set up, and am using ngMessages to show errors. However I want to create a wrapper directive around ngMessages that adds some HTML (to avoid a lot of code duplication).
So instead of having to write this on each input:
<div class="help-block validator">
<div ng-messages="registerForm.email.$error" ng-if="registerForm.email.$touched">
<p ng-message="required">This field is required.</p>
</div>
</div>
I'd just write something like this:
<error-message messages='{"required": "This field is required."}' error="registerForm.email.$error" touched="registerForm.email.$touched"></error-message>
The issue with my directive is that error and touched come up as strings, they don't get evaluated as JS expressions. I've tried to $eval them, but that throws an error.
Here's my directive:
angular
.module('myApp')
.directive("errorMessage", function () {
return {
restrict: "E",
replace: true,
scope: {
'messages': '=',
'error': '=',
'touched': '='
},
template: '<div class="help-block validator">' +
'<div ng-messages="error" ng-if="touched">' +
'<div ng-repeat="(key, message) in messages">' +
'<p ng-message="key">{{message}}</p>' +
'</div>' +
'</div>' +
'</div>',
link: function (scope, elem, attrs) {
scope.error = attrs.error;
scope.touched = attrs.touched;
scope.messages = scope.$eval(attrs.messages); // this works
}
};
});
Any idea how I should go about doing this?
Found the issue. Looks like attrs wasn't what I needed. The properties were already in the scope. The code I ended up with is:
angular
.module('myApp')
.directive("errorMessage", function () {
return {
restrict: "E",
replace: true,
scope: {
'messages': '=',
'error': '=',
'touched': '='
},
template: '<div class="help-block validator">' +
'<div ng-messages="error" ng-if="touched">' +
'<div ng-repeat="(key, message) in messages">' +
'<p ng-message="{{key}}">{{message}}</p>' +
'</div>' +
'</div>' +
'</div>',
link: function (scope, elem, attrs) {
}
};
});
I think ng-message-include meets your requirements. we can create new html file and place all of our messages in this file and just call it with ng-messages-include.
hope my answer is usable for you.

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

ui-select2 inside directive bind to outer scope

I'm nesting select2 input inside a directive and I want to bind the selected values to outer scope. how can I do that.
plunker example
directive code:
app.directive('optionChoices', function () {
return {
restrict: 'EA',
scope: {
type: '=',
selections: '='
},
template: '<input ng-if="type === 1" ui-select2="textChoices" ' +
'ng-model="selections" style="width:200px" />' +
'<input ng-if="type === 2" ui-select2="colorChoices" ' +
'ng-model="selections" style="width:200px" />' +
'{{\'inner:\' + selections}}',
link: function (scope, element, attrs) {
function Query(query) {
var data={
results:[]
};
if (query.term.length > 0) {
data.results.push({id:query.term,text:query.term});
}
query.callback(data);
}
scope.textChoices = {
query: Query,
tags: [],
initSelection: function () {}
};
scope.colorChoises = angular.extend({}, scope.textChoices, {
formatSelection: function(state) {
if (!state.id) return state.text;
return "<div style='background-color:yellow;'> </div>" + state.text;
}
});
}
};
});
I found the problem, and it's not that hard.
when creating isolated scope, you can not just bind to the parent scope, if doens angular will create a separate instance of the variable.
just need to bind to $parent, or to and parent object:
scope: {
option: '='
}
and in the template:
template: '<input ng-if="option.type === 1" ui-select2="textChoices" ' +
'ng-model="option.selections" style="width:200px" />' +
'<input ng-if="option.type === 2" ui-select2="colorChoices" ' +
'ng-model="option.selections" style="width:200px" />' +
'{{\'inner:\' + selections}}',
njoy!

Resources