AngularJS ng-change on select with custom directive - javascript

In my application I have a custom directive with select tag.
<div class="row pv10" ng-if="item.visible">
<div class="col-xs-4 text-danger"><span ng-if="item.label_visible"> {{item.label}}</span></div>
<div class="col-xs-8">
<div class="row">
<div class="col-xs-1">
<select ng-options="oneItem for oneItem in item.options" ng-model="selectedItem" ng-change="changeEvent()">
{{selectedItem | json}}
</select>
</div>
</div>
</div>
</div>
I would like to perform an action on every change event. I thought of using ng-change, which btw. worked just fine with radioBtn/checkBox. I tried two approaches here.
Firstly I tried to pass "changeEvent" function to the directive's isolated scope.
<custom-select item='item' change-event="change()" ng-if="item.component == 'select'" />
changeEvent() function exists in current controller (attached to the view holding custom-select)
scope: {
item: "=",
changeEvent: "&"
}
Sadly expression does not evaluate on change.
Secondly I tried to attach the function directly to directive
link: function ($scope, elems, attrs) {
$scope.changeEvent = function () {
alert("Change Event!");
}
}
but it didn't work either.
I read somewhere that ng-change even used within a custom directive operates on a controllers scope, not isolated scope of directive. However am not quite sure about that.
I'd really appreciate your help.
EDIT!
This is a JSFiddle to the directive I use. If I should provide more details, just let me know, this is the first time I've ever used jsfiddle.
This is how the directive is invoked:
<div class="row" ng-repeat="item in jsonData">
<div class="col-xs-12">
<cbp-select item='item' change="change()" ng-if="item.component == 'select'" />
</div>
</div>
https://jsfiddle.net/60nsfg0L/
EDIT2
When I have uploaded my solution to our local server it started to work properly.(Yes, I cleaned browser's cash, and my locals wamp/iss cache too).
But now I have a different problem.
I have a form built from different directives(like the one above), all have ng-click/change/blur. Everything seems to work fine until I change one of the options in my "select" directive. After selecting one of the values, ng-change expression is triggered (for not its just a simple alert) and from that moment onwards am not able to focus on any other elements. Weirdly enough, if I cause ng-change to trigger on any other element be it radiobtn or checkbox this problem does not occur.

Related

Disabling submit button based on fields added with ng-bind-html

JSFiddle here: http://jsfiddle.net/c6tzj6Lf/4/
I am dynamically creating forms and buttons and want to disable the buttons if the required form inputs are not completed.
HTML:
<div ng-app="choicesApp">
<ng-form name="choicesForm" ng-controller="ChoicesCtrl">
<div ng-bind-html="trustCustom()"></div>
<button ng-repeat="button in buttons" ng-disabled="choicesForm.$invalid">
{{button.text}}
</button>
</ng-form>
</div>
JavaScript:
angular.module('choicesApp', ['ngSanitize'])
.controller('ChoicesCtrl', ['$scope', '$sce', function($scope, $sce) {
$scope.custom = "Required Input: <input required type='text'>";
$scope.trustCustom = function() {
return $sce.trustAsHtml($scope.custom);
};
$scope.buttons = [
{text:'Submit 1'},
{text:'Submit 2'}];
}]);
choicesForm.$invalid is false and does not change when entering text into the input field.
Solution:
I ended up using the angular-bind-html-compile directive from here: https://github.com/incuna/angular-bind-html-compile
Here is the relevant bit of working code:
<ng-form name="choicesForm">
<div ng-if="choices" bind-html-compile="choices"></div>
<button ng-click="submitForm()" ng-disabled="choicesForm.$invalid">
Submit
</button>
</ng-form>
And choices might be a snippit of HTML like this:
<div><strong>What is your sex?</strong></div>
<div>
<input type="radio" name="gender" ng-model="gender" value="female" required>
<label for="female"> Female</label><br>
<input type="radio" name="gender" ng-model="gender" value="male" required>
<label for="male"> Male</label>
</div>
The main problem is that ngBindHtml doesn't compile the html - it inserts the html as it is. You can even inspect the dynamic input and see that it doesn't have the ngModel's CSS classes (ng-pristine, ng-untouched, etc) which is a major red flag.
In your case, the form simply doesn't know that you've added another input or anything has changed for that matter. Its state ($pristine, $valid, etc) isn't determined by its HTML but by the registered NgModelControllers. These controllers are added automatically when an ngModel is linked.
For example this <input required type='text'> won't affect the form's validity, even if it's required, since it doesn't have ngModel assigned to it.
But this <div ng-model="myDiv" required></div> will affect it since it's required and has ngModel assigned to it.
The ngDisabled directive on your buttons works as expected since it depends on the form's $invalid property.
See this fiddle which showcases how ngModel registers its controller. Note that the html containing the dynamic input gets compiled after 750ms just to show how NgModelControllers can be added after FormController has been instantiated.
There are a few solutions in your case:
use a custom directive to bind and compile html - like this one
use ngInclude which does compile the html
use $compile to compile the newly added HTML but this is a bit tricky as you won't know exactly when to perform this action
This is an answer yet imcomplete because i cannot do the code at the moment.
I think your html will be included, not compiled. So the inputs are not bind to angular and are not part of the angular form object.
The only way i see is to use a directive that will compile the passed html and add it to your form. This may be quite tricky though, if you want to go on this way i suggest to edit your question to ask for the said directive.
However i'm not really familiar with $compile so i don't know if it'll work to just add $compile around $sce.trustAsHtml()
You can write a method as ng-disabled does not work with booleans, it works with 'checked' string instead:
So on your controller place a method :
$scope.buttonDisabled = function(invalid){
return invalid ? "checked" : "";
};
And on your view use it on angular expression :
<button ng-repeat="button in buttons" ng-disabled="buttonDisabled(choicesForm.$invalid)">
Here is a working fiddle
Working DEMO
This is the solution you are looking for. You need a custom directive. In my example I have used a directive named compile-template and incorporated it in div element.
<div ng-bind-html="trustCustom()" compile-template></div>
Directive Code:
.directive('compileTemplate', function($compile, $parse){
return {
link: function(scope, element, attr){
var parsed = $parse(attr.ngBindHtml);
function getStringValue() { return (parsed(scope) || '').toString(); }
//Recompile if the template changes
scope.$watch(getStringValue, function() {
$compile(element, null, -9999)(scope); //The -9999 makes it skip directives so that we do not recompile ourselves
});
}
}
});
I found the directive in this fiddle.
I believe what is really happening though due to jsfiddle I'm unable to dissect the actual scopes being created here.
<div ng-app="choicesApp">
<ng-form name="choicesForm" ng-controller="ChoicesCtrl">
<div ng-bind-html="trustCustom()"></div>
<button ng-repeat="button in buttons" ng-disabled="choicesForm.$invalid">
{{button.text}}
</button>
</ng-form>
</div>
The first div is your top level scope, your form is the first child scope. Adding the div using a function creates the dynamically added input field as a child of the first child, a grandchild of the top level scope. Therefore your form is not aware of the elements you're adding dynamically causing only the static field to be required for valid form entry.
A better solution would be to use ng-inclue for additional form fields or if your form isn't to large then simply put them on the page or template you're using.

Radio buttons in angular directive not working

I'm trying to make an angular directive that takes in an object with a question and a number of answers. Then it should show the answers as radio buttons so the user can select one to vote for, then it could be sent back to the server.
On my version the ng-model/scope variable isn't updating.
<div>
<h3> {{poll.question}}</h3>
<div class="list-group">
<form>
<label ng-repeat="option in poll.options" for="{{option.optionName}}">{{option.optionName}}
<input type="radio" id="{{option.optionName}}" ng-model="selectedOption" ng-value="option.optionName" name="option"/>
</label>
</form>
</div>
<button ng-class="btn" ng-click="sendOption()">Send Vote</button>
<p>the option you selected is: {{selectedOption}}</p>
.directive('voter', function () {
return {
templateUrl: 'app/voter/voter.html',
restrict: 'EA',
scope:{
poll:'='
},
controller:function($scope,$http,Auth){
$scope.selectedOption = 'no option selected';
$scope.sendOption = function(){console.log($scope.selectedOption);};
},
link: function (scope, element, attrs) {
}
};
})
It displays the options for the poll answers but $scope.selectedOption doesn't change? I've not used radio buttons on Angular before so probably missed something obvious.
Thank for any help
The problem is that you are using ng-model inside ng-repeat. When you use ng-repeat each item in the repeater has its own scope created. When you click on a radio button, you update selectedOption on this newly created scope ... not the scope on the directive. This is why the binding in your paragraph isn't being updated.
You can quickly fix this by using an object (vote) to hold the voting result:
<input type="radio" id="{{option.optionName}}" ng-model="vote.result" ng-value="option.optionName" />
...
<p>the option you selected is: {{vote.result}}</p>
...
controller:function($scope) {
$scope.vote = {result: null}
$scope.sendOption = function(){console.log($scope.vote.result);};
}
See this plunker.
Edit: I fixed a small bug. It was accessing vote via undefined. Instead of square brackets, my answer now uses a dot. Plunker has also been updated.
For more information see
Binding using ng-model inside ng-repeat in angularjs
You can use this method to keep track of the selectedOption assuming that you only have one selectedOption at a time. It initializes the selectedOption variable on the form and then each time you click an input, it tells the parent form to change the variable to the selected index.
<form ng-init="selectedOption=0">
<label ng-repeat="option in poll.options" for="{{option.optionName}}">{{option.optionName}}
<input type="radio" id="{{option.optionName}}" ng-value="option.optionName" ng-click="$parent.selectedOption=$index" name="option"/>
</label>
</form>

How to include a filter in a custom directive

I'm new to Angular JS and am having trouble with custom directives. I've tried to copy a tutorial and I can't get it working using my own code.
Here's the relevant part of my HTML:
<div ng-controller="calcController" class="container">
<div class="form-group">
<label for="balInput">Balance:</label>
<input id="balInput" type="text" class="form-control" ng-model="balance" ng-change="updateAnnualInt(balance)" placeholder="Please enter your balance here...">
</div>
<p>{{'At a '+(interestRate)+'% interest rate you would save...'}}</p>
<p style="text-indent: 30px;" ng-repeat="interest in interests">{{'per '+interest.time+': '}}{{(interest.factor*annualInterest*0.01) | currency:'£'}}</p>
To start with, I'm just trying to turn the last paragraph into a custom directive. Here's my attempt:
app.directive('interest-amount',function(){
var directive={};
directive.restrict='E';
directive.template="<p style='text-indent: 30px;'>'per '+{{interest.time}}+': '{{(interest.factor*annualInterest*0.01) | currency:'£'}}</p>";
directive.compile=function(element,attributes){
var linkFunction=function($scope, element,attributes){
element.html("<p style='text-indent: 30px;'>per "+$scope.interest.time+": "+($scope.interest.factor*$scope.annualInterest*0.01)+"</p>");
}
return linkFunction;
}
return directive;
})
This isn't giving the HTML template I expect when I insert it as follows:
<interest-amount ng-repeat="interest in interests"></interest-amount>
My first question is why is this not working?
I am also confused as to how to include the currency filter in the linkFunction. What is the syntax for encoding this in Javascript rather than Angular-powered HTML?
Thanks
app.directive('interestAmount',function($filter){ // camel case, as #isha aggarwal noted
$filter('filterName')('argument');
});
Answer to your first question, the directive should be declared in camel case. So, declare the directive like this :
app.directive('interestAmount',function(){
And call it the way you are doing currently.
<interest-amount ng-repeat="interest in interests"></interest-amount>
This should take you inside the directive code.
For your second question, you should use a directive controller. Just create a controller for this directive, inject $filter in that controller and call function of that controller from link function of your directive.
Refer this : AngularJs - Use custom filter inside directive controller

Input checked has not working in side partial in angular

I m beginner in Angular. I m working on a angular project. I have a input checkbox in my partial source. When it checked a popup should appear, not checked state have a popup.
I found the below code on google and it works separately. But when i put it my partial source it doesn't work.
<div>
<div >
<input type="checkbox" ng-true-value="A" ng-false-value="B" ng-model="check"/>
</div>
<div ng-show="check == 'A'">
Checked
</div>
<div ng-show="check == 'B'">
Unchecked
</div>
Can you anyone help me
Assuming You want to access the parent controller value inside partial which has been loaded using ng-controller directive, In that you need to follow dot rule in your code, As a ng-controller create a new child scope whenever it adds the specified template.
For solving issue you should have your data in object structure in-order to follow prototypal inheritance
Code
$scope.model = {};
Markup
<div>
<div >
<input type="checkbox" ng-true-value="A" ng-false-value="B" ng-model="model.check"/>
</div>
<div ng-show="model.check == 'A'">
Checked
</div>
<div ng-show="model.check == 'B'">
Unchecked
</div>
Other way to do it would be is you can simply use $parent before the check scope variable, that will refer to the parent scope of the controller.
Just replace check with $parent.check everywhere in the view, This will work but the first approach is most preferable.
Add single quotes to 'A' and 'B'
<div >
<input type="checkbox" ng-true-value="'A'" ng-false-value="'B'" ng-model="check"/>
</div>

ng-include changing behavior of angular-ui datepicker

I was using ng-include on a few of my pages, however I had to stop using ng-include because it was breaking the angular-ui datepicker. I opened this Github bug.
I am wondering if anyone else was having issues with directives not functioning the same way when being used as part of a ng-include.
Is there a way to make the datepicker work as expected as part of a ng-include?
Here is a plunker showing how it is broken. http://plnkr.co/edit/AboEJGxAK3Uz76CfpaZ0?p=preview
Here is the html that works when on the view, but does not work when part of a ng include.
<div class="row">
<div class="col-md-2">
<p class="input-group">
<input type="text" class="form-control" datepicker-popup="yyyy/MM/dd" ng-model="something.dt2" is-open="secondCal"
min-date="minDate" name="secondCal" max-date="'2015-06-22'" datepicker-options="dateOptions"
date-disabled="disabled(date, mode)" ng-required="true" close-text="Close"/>
<span class="input-group-btn">
<button type="button" class="btn btn-default" style="line-height: 1.2em" ng-click="open($event, 'secondCal')">
<i class="ss-icon ss-calendar"></i>
</button>
</span>
</p>
</div>
</div>
Here is the JS from the controller.
$scope.open = function ($event, elementOpened) {
$event.preventDefault();
$event.stopPropagation();
$scope[elementOpened] = !$scope[elementOpened];
};
And two ways I was doing ng-include
<div ng-include src="'dist/partials/myPartial.html'"></div>
<div ng-include="'dist/partials/myPartial.html'"></div>
Update
I found that this is because the ng-include directive creates a new scope for each include. This SO post creates a new directive that does the include without creating a new scope. However there seems there "should" be a way to fix it without using a different include.
The datepicker will be unable to open as soon as the is-open is changed by the datepicker directive itself (e.g. click outside to close the datepicker).
This is a common issue regarding the "Prototypal Inheritance" characteristic of scope.
For a complete detail, you could read this: Understanding-Scopes
You could easily solve this by not storing any primitive values directly into $scope, but some object and using dot-notation like this:
<input type="text" class="form-control"
datepicker-popup="yyyy/MM/dd" ng-model="something.dt2"
is-open="model.secondCal"
and in your controller:
app.controller('MainCtrl', function($scope) {
$scope.model = {};
$scope.open = function($event, elementOpened) {
$event.preventDefault();
$event.stopPropagation();
$scope.model[elementOpened] = !$scope.model[elementOpened];
};
});
Example Plunker: http://plnkr.co/edit/dJNIwSz2Uot3McmIMhd4?p=preview
I've created Plunker to debug it but it works fine with your code
http://plnkr.co/edit/nxYCiwRqdWMOkfZoRhGY?p=preview
<body ng-controller="MainCtrl">
<div ng-include="'partial.html'"></div>
</body>
after clarification and further tests i see that calendar with ng-include lose the scope when triggering the change not by scope method, the easy workaround would be as per this plunker
http://plnkr.co/edit/nxYCiwRqdWMOkfZoRhGY?p=preview
Don't remember which one of the angular team said it but if you don't have a dot in your model you are doing it wrong.
a little explanation why it works:
if you do
$scope.valueName = value
it will assign value to the current scope immediately
but if you do
$scope.something.valueName = value
what will happen is that angular will first locate $scope.something, if it doesn't exists on current scope it will look in parent (as long as you don't work in isolated scope)
then when it finds it it will assign value or return something like 'cant find valueName of undefined'
from the angularjs documentation:
https://docs.angularjs.org/api/ng/directive/ngInclude
This directive creates new scope.
This directive executes at priority level 400.

Categories

Resources