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
Related
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/
I created this simple directive that uses a formatter and it is not updating the input when the directive is used as an element or when it is an attribute of a div.
JSBIN:
http://jsbin.com/rifaxuvihu/edit?html,js,output
angular.module('myApp', [])
.controller('myCtrl', function($scope) {
$scope.foo = '123';
console.log('------ MODEL CHANGED ($scope.foo = "123") ------');
$scope.changeModel = function() {
$scope.foo = 'abc';
console.log('------ MODEL CHANGED ($scope.foo = "abc") ------');
};
})
.directive('myDirective', function() {
return {
restrict: 'AE',
transclude: true,
require: 'ngModel',
template: function($element, $attrs, $ctrls) {
var inp, lbl = 'label';
if ($attrs.type == 'html'){
inp = [
'<summernote class="form-control" ng-model="ngModel" ',
' name="{{name}}" placeholder={{placeholder}} height="{{height}}"></summernote>'
];
lbl = 'div';
} else
inp = [
'<input class="form-control" ng-model="ngModel" ',
' type="{{type}}" />'
];
var ret = [
'<div class="advform-input form-group inp-{{type}} {{cols}}">',
' <' + lbl + ' class="advform-lbl">',
inp.join('\n'),
' <div class="helper" ng-if="type != \'html\'">{{ placeholder }}</div>',
' <div class="desc">{{ desc }}</div>',
' </' + lbl + '>',
'</div>'
].join('\n');
return ret;
},
scope: {
placeholder: '#',
ngModel: '=?',
type: '#',
name: '#',
height: '#'
},
link: function ($scope, $element, $attrs, $ngModel) {
console.log('ooi');
if (true){
$ngModel.$formatters.unshift(function(modelVal) {
console.log('-- Formatter --', JSON.stringify({
modelVal:modelVal,
ngModel: {
viewVal: $ngModel.$viewValue,
modelVal: $ngModel.$modelValue
}
}, null, 2));
return 1 + '-) ' +modelVal;
});
// same as $watch('foo')
$scope.$watch(function() {
return $ngModel.$viewValue;
}, function(newVal) {
console.log('-- $watch "foo" --', JSON.stringify({
newVal:newVal,
ngModel: {
viewVal: $ngModel.$viewValue,
modelVal: $ngModel.$modelValue
}
}, null, 2));
});
}
}
};
})
;
the html:
<!DOCTYPE html>
<html ng-app="myApp" ng-controller="myCtrl">
<head>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.0-beta.18/angular.min.js"></script>
</head>
<body>
<form name="form">
<my-directive type="text" name="foo" ng-model="foo"></my-directive>
<input my-directive type="text" name="foo" ng-model="foo" />
<div my-directive type="text" name="foo" ng-model="foo"></div>
</form>
<button ng-click="changeModel()">Change Model</button>
<p>$scope.foo = {{foo}}</p>
<p>Valid: {{!form.foo.$error.test}}</p>
</body>
</html>
The problem is that the directive creates a new scope and the viewValue of the ngModel of the directive is different from the one created within the template.
To fix it, you can change the ng-model of the template to $parent.ngModel.
So the format will work as expected.
My ngMessages doesnt work inside my directives template!
I have a directive myInput with a template and a link function, inside the template function I create template string for a wrapped <label> and <input>.
Inside the Link function I use the require: '^form' FormController and retrieve the form name. Then I'm putting a ngMessages block after the wrapped elements.
(function () {
'use strict';
angular
.module('app.components')
.directive('myInput', MyInput);
/*#ngInject*/
function MyInput($compile, ValidatorService, _, LIST_OF_VALIDATORS) {
return {
require: '^form',
restrict: 'E',
controller: MyInputController,
controllerAs: 'vm',
bindToController: true,
template: TemplateFunction,
scope: {
label: '#',
id: '#',
value: '=',
validateCustom: '&'
},
link: MyInputLink
};
function MyInputController($attrs) {
var vm = this;
vm.value = '';
vm.validateClass = '';
vm.successMessage = '';
vm.errorMessage = '';
}
function TemplateFunction(tElement, tAttrs) {
return '<div class="input-field">' +
' <label id="input_{{vm.id}}_label" for="input_{{vm.id}}" >{{vm.label}}</label>' +
' <input id="input_{{vm.id}}" name="{{vm.id}}" ng-class="vm.validateClass" type="text" ng-model="vm.value" >' +
'</div>';
}
function MyInputLink(scope, element, attrs, form){
var extra = ' <div ng-messages="' + form.$name + '.' + scope.vm.id + '.$error">' +
' <div ng-messages-include="/modules/components/validationMessages.html"></div>' +
' </div>';
$(element).after(extra);
}
}
})();
Usage:
<h1>Test</h1>
<form name="myForm">
<my-input label="My Input" id="input1" value="vm.input1"></my-input>
-------
<!-- this block is hardcoded and is working, it does not come from the directive! -->
<div ng-messages="myForm.input1.$error">
<div ng-messages-include="/modules/components/validationMessages.html"></div>
</div>
</form>
Instead of adding the ngMessages block inside the link function, add it inside the compile function.
It is not as handy as in the link funciton because of the missing FormController, but it works :-)
Here the code:
compile: function(tElement, tAttrs){
var name = tElement.closest('form').attr('name'),
fullFieldName = name + '.' + tAttrs.id; // or tAttrs.name
var extra = '<div ng-messages="' + fullFieldName + '.$error">' +
' <div ng-messages-include="/modules/components/validationMessages.html"></div>' +
'</div>';
$(element).after(extra);
Here is what I did, I added to scope, myForm: '=' then in the directive's template referred to <div ng-messages="vm.myForm[vm.id].$error" >
I feel this is much cleaner than mucking around in the link function.
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!
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.