I am having troubles with aplying validations on radio buttons in angular, on other input types, i usualy just create the #templateRefVariable on the input and can then access the NgControl that allows me to use things like the touched property of the control.
What im trying to achieve currently is setting the touched property of the div acording to if any of the radio buttons in the group were touched. (seting it in the div because of css dependencies if it is not in that outer div the validations will not show), but typeCode is always undefined.
<div class="form-group">
<label>Label</label>
<div class="btn-group btn-group-toggle w-100"
[class.ng-invalid]="!viewmodel.typeCode"
[class.ng-touched]="typeCode?.touched">
<label *ngFor="let domain of types" class="btn btn-toogle"
[class.active]="domain.code==viewmodel.typeCode">
<input type="radio" [(ngModel)]="viewmodel.typeCode"
#typeCode="ngModel" name="typeCode"
[value]="domain.code">
{{domain.description}}
</label>
</div>
<validation-message *ngIf="!viewmodel.typeCode"
[message]="'Required'"></validation-message>
</div>
With invalid i can workarround it by using the information in the view model to see if it was set already but the information on touched is not in the view model.
And i cant do the same for ng-touched because i need to set touched when there is an atempt to submit the form (even if the inputs were not actualy touched).
Any idea why typeCode (templateVariableRef) is undefined while using it in radio buttons ? i suspect it might be because of there being multiple in the page but i am not sure.
P.S: Using template driven forms
StackBlitz as requested (note errors on console because of typeCode undefined):
https://angular-5vqi5c.stackblitz.io
It is because ngFor is a structural directive and creates a nested template, and therefore these template variable(s) are out of scope.
Would moving your logic inside the ngFor be an option for you e.g.?
<div class="btn-group btn-group-toggle w-100"
[class.ng-invalid]="!viewmodel.typeCode">
<ng-container *ngFor="let domain of types">
<div [class.ng-touched]="typeCode?.touched>
<label class="btn btn-toogle"
[class.active]="domain.code==viewmodel.typeCode">
<input type="radio" [(ngModel)]="viewmodel.typeCode"
#typeCode="ngModel" name="typeCode"
[value]="domain.code">
{{domain.description}}
</label>
</div>
</ng-container>
</div>
PS. I haven't tested the code above.
Related
Radio button's disabled property is not working inside the reactive form but it's working fine when I put radio button outside the reactive form.
I have a condition like if my current status is CLOSED I should allow the user to edit radio button inside the reactive form I already tried with Disabled property of radio button but no luck
component.ts
conducttestlm:any={
isReadOnly:false
}
this.condutTestLM=this.formBuilder.group({
"testStatus":['',Validators.required],
})
if(success.data.status=='CLOSED'){
this.conducttestlm.isReadOnly=true;
}
component.html
<form [formGroup]="condutTestLM">
<div class="row radio-top">
<div class="col-md-6 p-l-15">
<label class="radio-box">
<input formControlName="testStatus" name="testStatus" [(ngModel)]="conducttestlm.teststatus" [disabled]="conducttestlm.isReadOnly" value="Pass" type="radio">
<span class="checkmark"></span>
<span class="font-style">Pass</span>
</label>
</div>
<div class="col-md-6 p-l-15">
<label class="radio-box">
<input formControlName="testStatus" name="testStatus" [(ngModel)]="conducttestlm.teststatus" [disabled]="conducttestlm.isReadOnly" value="Fail" type="radio">
<span class="checkmark"></span>
<span class="font-style">Fail</span>
</label>
</div>
</div>
</form >
I need to disable the radio button
Don't use template driven approach while using reactive forms. Have your code like:
<form [formGroup]="condutTestLM">
<div class="row radio-top">
<div class="col-md-6 p-l-15">
<label class="radio-box">
<input formControlName="testStatus" name="testStatus" [attr.disabled]="conducttestlm.isReadOnly ? true : null" value="Pass" type="radio">
<span class="checkmark"></span>
<span class="font-style">Pass</span>
</label>
</div>
<div class="col-md-6 p-l-15">
<label class="radio-box">
<input formControlName="testStatus" name="testStatus" [attr.disabled]="conducttestlm.isReadOnly ? true: null" value="Fail" type="radio">
<span class="checkmark"></span>
<span class="font-style">Fail</span>
</label>
</div>
</div>
</form>
See an example here: https://stackblitz.com/edit/angular-dncxac?file=src%2Fapp%2Fapp.component.html
You will notice that I have used attr.disabled instead of disabled to disable individual radio buttons.
To know the difference between attr.disabled and disabled, you can have a look at this link. In a gist, attr.disabled is an HTML attribute while disabled is a DOM property. There are some HTML attributes for which DOM properties don't exist, as shown in the added link.
From angular docs
Attributes are defined by HTML. Properties are defined by the DOM (Document Object Model).
A few HTML attributes have 1:1 mapping to properties. id is one example.
Some HTML attributes don't have corresponding properties. colspan is one example.
Some DOM properties don't have corresponding attributes. textContent is one example.
Many HTML attributes appear to map to properties ... but not in the way you might think!
This is not true for input boxes and disabled DOM property. There indeed is a disabled DOM property but there is some issue while using it individually over radio buttons. See this Github issue. The solution I provided is more of a workaround to achieve individual disabling of radio buttons.
One possibility is that the status change is not being detected while rendering. First of all:
// instead of using this:
[disabled]="conducttestlm.isReadOnly"
// Use the readonly attribute:
[readonly]="conducttestlm.isReadOnly"
Next, use ChangeDetectorRef to detect any changes manually:
// import it:
import { ChangeDetectorRef } from '#angular/core';
// and inject it into your contructor:
constructor(private ref: ChangeDetectorRef) {
...
}
// call it
if(success.data.status=='CLOSED'){
this.conducttestlm.isReadOnly=true;
this.ref.detectChanges();
}
Should detect changes in status immedietly on run. If not, try console logging your conducttestlm.isReadOnly value to ensure it's changing.
If you want to use template driven attribute, you can use [attr.disabled] instead [disabled] as well.
what i am trying to do here is, i have an ng-repeat in a form and if i click anyone of those input buttons corresponding all buttons get disabled
<div ng-repeat="question in sinSurCtrl.singleSurvey" ng-show="!$first">
<hr>
<p>Q. {{question.questionText}}</p>
<form >
<div ng-repeat=" option in question.questionOptions track by $index">
<label>
<input name="options" type="radio" value="$index" ng-click="sinSurCtrl.questionId=question.questionId; sinSurCtrl.answer=$index+1; sinSurCtrl.createAnswer()" onclick="this.disabled = true">
<span> {{option}} {{$index+1}} {{question.questionId}} </span>
</label>
</div>
</form>
</div>
here is the view-
as you can see if i select anyone of those option it is getting disabled but what i am trying to do is if i attempt anyone option then options corresponding to the same question get disabled.
for example-
in Q3. which is a better orator ?
if i choose option (a) then it get selected and after that automatically bot options (a) and (b) get disabled.
Note- please try to keep solution completely in angularjs or if you want use affordable amount of javascript other then that please avoid using external libraries like jQuery(i know nobody in his senses will handle trouble of using jQuery and angular together but for the sake of example i have included its name)
Proposed solution with some suggested refactoring...
First change the ng-click directive to point to a new onOptionButtonClicked function on sinSurCtrl which takes in the two parameters question and index (which it needs to carry out it's work):
<div ng-repeat="question in sinSurCtrl.singleSurvey" ng-show="!$first">
<hr>
<p>Q. {{question.questionText}}</p>
<form>
<div ng-repeat="option in question.questionOptions track by $index">
<label>
<input
name="options"
type="radio"
value="$index"
ng-click="onOptionButtonClicked(question, $index)"
ng-disabled="question.disabled">
<span> {{option}} {{$index+1}} {{question.questionId}} </span>
</label>
</div>
</form>
</div>
Also take note of the newly added ng-disabled="question.disabled" directive. This is part of the mechanism that will enable/disable the question's controls.
Now move the variable assignments to the new onOptionButtonClicked function. The controller is generally a better place (than the view) for variable assignments, especially if there are several of them on the same directive.
sinSurCtrl.onOptionButtonClicked = onOptionButtonClicked;
function onOptionButtonClicked(question, index){
sinSurCtrl.questionId=question.questionId;
sinSurCtrl.answer=index;
sinSurCtrl.createAnswer();
question.disabled = true; // <--- This is what disables the option buttons
}
This is where an answered question object gets it's disabled property set to true. This in combination with the ng-disabled directive mentioned previously is what disables the option buttons.
On your controller, create a function that checks if a given questionId has been answered and return truthy if it has. Call that function in ng-disabled in the input tag:
<input type="radio" ng-disabled="sinSurCtrl.questionHasAnswer(question.questionId)" ... />
I'm using ui-router and the view below accesses the controller using the 'Controller As' syntax. I'm adding the 'invalid' class onto the 'shift'-radios container. When form loads you can see both validation required messages (for requestDate and shift). When requestDate and shift are set validation messages disappear. Form's $valid is true. The only problem is that the 'invalid' class on the radio-wrapper div stays.
What am I doing wrong?
<form novalidate ng-submit="_form.$valid && formCtrl.getFormData()" name="_form">
<div class="datepicker-wrapper clearfix">
<datepicker date-format="yyyy-MM-dd">
<input required ng-model="formCtrl.requestDate" name="requestDate" type="text" />
</datepicker>
</div>
<div ng-messages="_form.requestDate.$error" role="alert">
<div ng-message="required">Введите дату</div>
</div>
<div class="radio-wrapper clearfix" ng-class="{invalid: _form.shift.$error}">
<div ng-repeat="shift in formCtrl.shifts" class="item half-width pull-left">
<label for="<% shift.name %>">
<input required ng-model="formCtrl.currentShift"
name="shift"
ng-value="shift"
id="<% shift.name %>"
type="radio" />
<span class="text"><span ng-bind="shift.text"></span></span>
</label>
</div>
</div>
<div ng-messages="_form.shift.$error" role="alert">
<div ng-message="required">Укажите смену</div>
</div>
<!-- other inputs... -->
</form>
I think you need to use the $invalid property, which is a boolean, instead of the $error property, which is a hash.
See: https://docs.angularjs.org/api/ng/type/ngModel.NgModelController
Your ng-class definition is checking the wrong property of the form controller. You are checking the truthyness of _form.shift.$error. This object will always exist, even if there are no validation errors. When you don't have any errors, the value of _form.shift.$error is empty object {} which is truthy.
You should instead check the _form.shift.$invalid property which will properly update whether there are any problems with that form field.
There is no _form.shift. The input shift is declared inside an ng-repeat so it is a property on the _form.
To fix you could for example, move your ng-class inside the ng-repeat and reference the shift using ng-form to create a sub-grouping:
<div ng-repeat="shift in formCtrl.shifts" ng-form='nestedShiftForm'>
<div ng-class="{invalid: nestedShiftForm.shift.$error}">
<input name="shift" />
I have an input field that is nested within another <div> element, and I am trying to use ngMessages on that inside input field, but I can't seem to get it to validate correctly.
<div class="form-group" ng-model="object.idnumber" ng-hide="condition.userObjectsHidden">
<label class="form-control-label col-lg-12">ID Number</label>
<div class="col-lg-12">
<input type="text" name="idnumber" placeholder="111001111"
ng-model="user.idnumber"
ng-pattern="idpattern"
class="form-control input-lg"
required="required"></input>
<div ng-messages="idnumber.$error" ng-if="idnumber.$dirty">
<p ng-message="pattern">You are wrong!</p>
</div>
</div>
</div>
I'm not sure if it matters in terms of functionality where the <div ng-messages...> tag is, but I have also tried having it completely outside of this element with the same results. If I understand Angular and ngMessages correctly, I need to assign ng-messages to a directive--$error in this case--that I get to by dot-walking across name assignments. As far as I know, I have done this with idnumber.$error, although to be fair, I have also tried a more extensive dot-walk by using kiosk-form.uin.$error, where kiosk-form is the name of the entire form.
I have tried both ng-message="pattern" as well as ng-message="required". Also, just for clarity, idpattern is defined in my Javascript file as a regex string. It is defined correctly.
Rename your form as kioskFormand then ng-messages ="kioskForm.idnumber.$error"
I have a form with an ng-repeat directive. I'm using ng-show to display validation error messages. I'm attempting to add a validation message within the ng-repeat directive, but I'm having trouble. This is what I have so far:
<form name="rentalApp" ng-controller="RentalAppCtrl">
<label for="name">Full Name</label>
<input id="name" name="name" ng-model="app.name">
<div ng-show="rentalApp.name.$invalid">Please enter your name</div>
<div ng-repeat="history in app.history">
<label for="address{{ $index }}">Street Address</label>
<input id="address{{ $index }}" name="address{{ $index }}" ng-model="history.address">
<div ng-show="rentalApp.('address' + $index).$invalid">Please enter an address</div>
</div>
</form>
As you can see, I am using the $index variable to make sure my IDs and names are unique.
In the rest of my form I'm doing something along the lines of:
<div ng-show="rentalApp.whatever.$invald">Error Message</div>
What I'm having trouble with is that within the ng-repeat directive, the indexes are dynamic. I've been fiddling around with this for a while, and the closest I think I've gotten to a solution is this line:
<div ng-show="rentalApp.('address' + $index).$invalid">Please enter an address</div>
However that doesn't work.
How can I properly concatenate the word "address" with the $index variable, to create a string along the lines of address0, which can then be used in my ng-show directive to determine if the form element is invalid? Or, alternatively, am I going about this completely wrong (not the "Angular way")?
EDIT
I'm looking at the rentalApp object from the console, and AngularJS is not evaluating the name attribute as I would expect. Specifically, the rentalApp object contains a address{{ $index }} object, instead of address0. So it looks like I can't use the expression {{ $index }} within the name attribute.
UPDATE
I make a research and find out it is not possible to give dynamic value to name besides create a custom directive for it...
but there is a solution for you to validate dynamic form with ng-form directive for each individual ng-repeat element...
<div ng-repeat="history in app.history">
<ng-form name="addressForm">
<label>Street Address</label>
<input name="address" ng-model="history.address" ng-required="true">
<div ng-show="rentalApp['address' + $index].$invalid">Please enter an address</div>
<span class="alert error" ng-show="addressForm.address.$invalid">Please enter an address</span>
</ng-form>
</div>
and here is your PLUNKER...
Just put:
<input name="{{input.name}}"></input>