Disable Angular 5 Input fields correct way - javascript

I have a FormGroup that was created like that:
form: FormGroup;
constructor(private _formBuilder: FormBuilder) { }
this.form = this._formBuilder.group({
name: ['', Validators.required],
email: ['', Validators.required, Validators.email]
});
When an event occurs I want to disable those inputs, so, in the HTML I added:
<input class="form-control" placeholder="Name" name="name" formControlName="name" [(ngModel)]="name" autocomplete="off" [disabled]="isDisabled" required>
<input class="form-control" placeholder="Email" name="email" formControlName="email" [(ngModel)]="email" email="true" autocomplete="off" [disabled]="isDisabled" required>
Where isDisabled is a variable I toggle to true when the said event happens.
As you can imagine, I get the message:
It looks like you're using the disabled attribute with a reactive form
directive. If you set disabled to true
when you set up this control in your component class, the disabled attribute will actually be set in the DOM for
you. We recommend using this approach to avoid 'changed after checked' errors.
Example:
form = new FormGroup({
first: new FormControl({value: 'Nancy', disabled: true}, Validators.required),
last: new FormControl('Drew', Validators.required)
});
So, with the example this warning shows and with a little search I found that I should declare my controls like:
name: [{ value: '', disabled: this.isDisabled }, Validators.required]
The problem is: It is not toggling between disabled/enabled when the variable changes between true/false
How is the correct way of having a variable controlling if an input is enabled or disabled?
I don't want to do it manually (ex: this.form.controls['name'].disable()) because it doesn't seems a very reactive way, I would have to call it inside a good amount of methods. Probably not a good practice.
Thx

You can change the assignment of the variable to a setter method so that you'd have:
set isDisabled(value: boolean) {
this._isDisabled = value;
if(value) {
this.form.controls['name'].disable();
} else {
this.form.controls['name'].enable();
}
}

One solution is creating a directive and using binding for that as described in here
import { NgControl } from '#angular/forms';
#Directive({
selector: '[disableControl]'
})
export class DisableControlDirective {
#Input() set disableControl( condition : boolean ) {
const action = condition ? 'disable' : 'enable';
this.ngControl.control[action]();
}
constructor( private ngControl : NgControl ) {
}
}
then
<input class="form-control" placeholder="Name" name="name" formControlName="name" autocomplete="off" [disableControl]="isDisabled" required>
NOTE:
Doesn't work with Ivy

For input use [readonly] rather than [disabled] and it'll work

The proper way to disable an form control. With reactive forms you should never disable an input from the template. So in whatever method in your component you are calling you should disable the input like this:
this.form.get('name').disable();

Disable TextBox in Angular 7
<div class="center-content tp-spce-hdr">
<div class="container">
<div class="row mx-0 mt-4">
<div class="col-12" style="padding-right: 700px;" >
<div class="form-group">
<label>Email</label>
<input [disabled]="true" type="text" id="email" name="email"
[(ngModel)]="email" class="form-control">
</div>
</div>
</div>
</div>

You can use this code on your ts file.
All controls:
this.form.disable()
this.form.enable()
Some controls
this.form.get('first').disable()
this.form.get('first').enable()
Or Initial set method.
first: new FormControl({value: '', disabled: true}, Validators.required)

In Reactive Form you can disable all form fields by this.form.disable().
In Template Driven Form you can disable all form fields by this.myform.form.disable() where myForm is #ViewChild('form') myForm;

Not the clean or dry'st I imagine. Bu I tried the "set method" and didn't work out of the box...
Needed some refactoring () => {simpleVersion} (hope it helps someone)
component.ts
...
// standard stuff...
form: FormGroup;
isEditing = false;
...
// build the form...
buildForm() {
this.form = this.FormBuilder.group({
key: [{value:'locked', disabled: !this.isEditing}],
name: [],
item: [],
active: [false]
})
}
// map the controls to "this" object
// => i.e. now you can refer to the controls directly (ex. this.yourControlName)
get key() { return <FormControl>this.form.get('key') }
get name() { return <FormControl>this.form.get('name') }
...
// ----------------------------------------
// THE GRAND FINALÉ - disable entire form or individual controls
// ----------------------------------------
toggleEdit() {
if(!this.isEditing) {
this.key.enable(); // controls
this.name.enable();
// this.form.enable(); // the form
this.isEditing = !this.isEditing;
} else {
this.key.disable(); // the controls
this.name.disable(); // the controls
// this.form.disable(); // or the entire form
this.isEditing = !this.isEditing;
}
}
& perhaps overkill on the HTML logic, so hope you find the bonus integrated ngClass toggle just as helpful.
component.html (toggle button)
<div class="btn-group" (click)="toggleEdit()">
<label
class="btn"
role="button"
[ngClass]="{'btn-success': isEditing,
'btn-warning': !isEditing}">toggle edit
</label>
</div>

The solution by creating a directive and using binding for that worked for me in Angular 10 is described in here
Template:
<mat-form-field>
<input matInput class="form-control" formControlName="NameText" [disableControl]="condition" type="text">
</mat-form-field>
TypeScript:
import { Directive, Input } from '#angular/core';
import { NgControl } from '#angular/forms';
#Directive({
selector: '[opDisabled]'
})
export class DisabledDirective {
#Input()
set opDisabled(condition: boolean) {
const action = condition ? 'disable' : 'enable';
setTimeout(() => this.ngControl.control[action]());
}
constructor(private ngControl: NgControl) {}
}

I have a function that enables a control on click.
controlClick(control: any) {
this.form.controls[control.ngControl.name].enable();
}
Originally i was using
control.disabled = false;
But this did not work for controls with <input> for example in my mat-chip-list.
I use FormGroup and disable each control in the constructor
constructor(
private fb: FormBuilder,
private dialogRef: MatDialogRef<EditDialogComponent>,
#Inject(MAT_DIALOG_DATA) data
) {
this.data = data;
this.multiEdit = data.multiSelect;
this.form = new FormGroup({
autoArchive: new FormControl({
value:
this.getPreFill(data.selectedPolicy.autoArchive, this.multiEdit),
disabled: true
/*, Validators.required*/
}),
...
<mat-form-field (click)="controlClick(retrieveChipList)">
<mat-chip-list #retrieveChipList formControlName="retrieveChipList">
<mat-chip
*ngFor="let email of data.selectedPolicy.retrieveEmailsToBeNotified"
(removed)="remove(email)" [selectable]="selectable"
[removable]="removable"
>
{{ email }}
<mat-icon matChipRemove>cancel</mat-icon>
</mat-chip>
<input
placeholder="Retrieve Emails to be Notified"
formControlName="retrieveChipList"
[matChipInputFor]="retrieveChipList"
[matChipInputAddOnBlur]="true"
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"
(matChipInputTokenEnd)="addRetrieveEmails($event)"
/>
</mat-chip-list>
</mat-form-field>

As control can't be accessed in reactive forms. This is due to migration to Ivy. You can use can access the html attribute directly and specify your condition. See this issue #35330 for more details and alternative methods.
[attr.disabled]="true || false"

You can create set and get method to achieve conditionally enable/disable functionality for Angular material Reactive Forms:
*// 1st Step:
set isDisabled(value:boolean) {
if(value){
this.form.controls['Form controller name'].disable(); //you can keep empty if you don't add controller name
}
else{
this.form.controls['Form controller name'].enable();
}
}
// 2nd Step: Add conditions in getter
get isDisabled(){
return condition ? true : false;
}
// 3rd Step
this.form = this._formBuilder.group({
name: [{value: '', disabled: this.isDisabled }, [Validators.required]],
});

Remove [disabled]="isDisabled" from input fields and add ng-disabled="all" and in the event field add ng-model="all"
<body ng-app="">
Click here to disable all the form fields:<input type="checkbox" ng-model="all">
<input class="form-control" placeholder="Name" name="name" formControlName="name" [(ngModel)]="name" autocomplete="off" ng-disabled="all" required>
<input class="form-control" placeholder="Email" name="email" formControlName="email" [(ngModel)]="email" email="true" autocomplete="off" ng-disabled="all" required>
</body>

Related

Angular forms require one of two form groups to be valid

I am trying to implement a reactive angular form where either A or B has to be entered. A is a unique id and B is a set of values which identify the id. Now I try to validate a Form that is valid if either A is entered or B is entered including all the required values. I found several solutions that implement this behavior based on FormFields but was not able to get it working with the group of values.
<form class="container" [formGroup]="myForm" (ngSubmit)="onSubmit()">
<mat-form-field class="w-1/2">
<mat-label>ID</mat-label>
<input matInput type="number" formControlName="id">
</mat-form-field>
<div class="grid grid-cols-3 gap-4" formGroupName="innerGroup">
<mat-form-field>
<mat-label>First Name</mat-label>
<input matInput type="number" formControlName="firstName">
</mat-form-field>
<mat-form-field>
<mat-label>Last Name</mat-label>
<input matInput type="number" formControlName="lastName">
</mat-form-field>
</div>
</form>
My first idea was to override the default validator for the form but I could not figure out how to do that. Not even sure if it would be possible. I was trying to adjust https://stackoverflow.com/a/48714721 to work in my scenario but I had no idea how to get it to work because of the additional complexity with the inner form group.
Using angular 14 I was able to produce a similar result to what you are describing I am not sure it will 100% solve your issue however it might help.
Basically what I did was create a validator function that is to be applied at the group level. This validator will check the valid state of any given controls be they a FormGroup or a FormControl. However, this alone will not solve the problem as if you have a form group angular will see that any underling control or group that is invalid will also invalidate the parent. So what I did was call .disable() on any control that was being checked by the validator function. This will disable the UI element and disable validation checking by angular allowing the parent to be considered valid when one of the children is valid but the other is invalid effectively creating a one and only one validator.
My specific example I was trying to get the OnlyOneValidator to work for a MatStepper.
Validator
export function onlyOneValidator(controlKeys: string[]) {
return (control: AbstractControl): ValidationErrors | null => {
let countOfValidControls = 0;
for (let key of controlKeys) {
const controlToCheck = control.get(key);
if (controlToCheck === null || controlToCheck === undefined) {
throw new Error(`Error: Invalid control key specified key was ${key}`);
}
countOfValidControls += controlToCheck?.valid ? 1 : 0;
}
if (countOfValidControls !== 1) {
// the count is not exactly one
return {
onlyOneValid: {
actualValidCount: countOfValidControls,
expectedValidCount: 1
}
};
}
return null;
};
}
Controller
#Component({
selector: "app-equipment-creation-page",
templateUrl: "./equipment-creation-page.component.html",
styleUrls: ["./equipment-creation-page.component.scss"],
})
export class EquipmentCreationPageComponent implements OnInit, OnDestroy {
public categories = [null, "Tools", "Vehicles"];
constructor(private _formBuilder: FormBuilder) {}
public categoryInformationGroup = this._formBuilder.group({
existingCategory: this._formBuilder.group({
category: new FormControl(null, [ Validators.required ])
}),
newCategory: this._formBuilder.group({
name: new FormControl("", [Validators.required]),
description: new FormControl("", [Validators.required])
})
}, {
validators: [
onlyOneValidator(["existingCategory", "newCategory"])
],
});
public ngOnDestroy(): void {
this.subscriptions.forEach(sub => {
sub.unsubscribe();
});
}
private subscriptions: Subscription[] = [];
public ngOnInit(): void {
this.subscriptions.push(this.categoryInformationGroup.controls.existingCategory.statusChanges.pipe(
tap((status: string) => {
if (status === "VALID") {
this.categoryInformationGroup.controls.newCategory.disable();
} else {
this.categoryInformationGroup.controls.newCategory.enable();
}
})
).subscribe());
this.subscriptions.push(this.categoryInformationGroup.controls.newCategory.statusChanges.pipe(
tap((status: string) => {
if (status === "VALID") {
this.categoryInformationGroup.controls.existingCategory.disable();
} else {
this.categoryInformationGroup.controls.existingCategory.enable();
}
})
).subscribe());
}
}
Template
<form [formGroup]="categoryInformationGroup.controls.existingCategory">
<mat-form-field>
<mat-label>Apply to existing category?</mat-label>
<mat-select formControlName="category">
<mat-option *ngFor="let category of categories" [value]="category">
{{ category ?? "None" }}
</mat-option>
</mat-select>
</mat-form-field>
</form>
OR
<form [formGroup]="categoryInformationGroup.controls.newCategory">
<mat-form-field>
<mat-label>Create New Category</mat-label>
<input matInput formControlName="name" placeholder="Name">
<mat-error *ngIf="categoryInformationGroup.controls.newCategory.controls.name.hasError('required')">This field
is required
</mat-error>
</mat-form-field>
<mat-form-field>
<mat-label>Create New Category</mat-label>
<input matInput formControlName="description" placeholder="Description">
<mat-error *ngIf="categoryInformationGroup.controls.newCategory.controls.description.hasError('required')">
This field is required
</mat-error>
</mat-form-field>
</form>
Hopefully this helps or at least gives you some ideas about how to approach this. If anyone else has any thoughts on this please let me know I would love to find a better way to do this.

how to pass data to a form in another component in Angular

I have a form in a seperate component which looks like this:
<form [formGroup]="form">
<div class="card" style="margin-bottom: 10px" >
<input
formControlName="name"
type="text"
placeholder="please enter"
/>
</div>
</form>
the ts file looks like this (formcontrol part)
form = this.builder.group({
name: new FormControl(),
})
this is how I call it from the other component:
component B:
<form></form>
What I want to achieve now is the following I am filling some values from my backend which works, I know want to asign these values to my form from the form component. I know I have to use #Input I am kinda stuck against a wall and dont know how to connect them. Can someone give me a small example how I can fill my form from values from component b?
To my knowledge I have posted the most important part of this question, if anything else is needed feel free to ask
UPDATE_
Form.ts
#Input()
myObject: { name: string };
form = this.builder.group({
name: new FormControl()
});
ngOnChanges(changes: SimpleChanges): void {
if (changes.myObject?.currentValue) {
this.form.patchValue(this.myObject);
}
}
UPDATE2:
my form ts looks like this now:
#Input()
myObject: { name: string };
form = this.builder.group({
name: new FormControl()
});
ngOnChanges(changes: SimpleChanges): void {
if (changes.myObject && changes.myObject.currentValue) {
this.form.patchValue(this.myObject);
}
}
My form.hml like this:
<form [formGroup]="form">
<div class="card" style="margin-bottom: 10px" >
<input
formControlName="name"
type="text"
placeholder="please enter"
/>
</div>
</form
my calling component ts like this:
this.form.controls.legitimiertePersonNameField.setValue(response.vorname)
this.someName = this.form.controls.legitimiertePersonNameField.value -> gets the data from formcontrol in parent/calling
html from caling looks like this:
<gwg-form [myObject]="someName"></gwg-form>
The someName value is correct I could see it in a console.log but it does not set the value for the fomrCOntrol of the form.html it is just empty input
form.component.ts
#Component({
selector: 'my-form'
...
})
export class FormComponent implements OnChanges {
#Input()
myObject: { name: string };
form = this.builder.group({
name: new FormControl()
});
ngOnChanges(changes: SimpleChanges): void {
if (changes.myObject?.currentValue) {
this.form.patchValue(this.myObject);
}
}
}
form.component.html
<form [formGroup]="form">
<div class="card" style="margin-bottom: 10px" >
<input
formControlName="name"
type="text"
placeholder="please enter" />
</div>
</form>
calling.component.ts
#Component({ ... })
export class CallingComponent {
someObject: { name: string };
loadData(): void {
this.someObject = ...
}
}
calling.component.html
<my-form [myObject]="someObject">
</my-form>

Angular 5 reactive form- updating formControl through the html does not update the value, only the control

I have a service with a FormGroup named mainForm-
export class DepositFormDataService{
this.mainForm = this.formBuilder.group({
title: ['', Validators.required],
category: '',
type: '',
languages: this.formBuilder.array([]),
});
}
get title(): FormControl{
return this.mainForm.get('title') as FormControl;
}
}
In a component I use this service-
constructor(public depositFormDataService: DepositFormDataService) {}
HTML:
<input matInput [formControl]="depositFormDataService.title">
When the user writes "b" for example I see in the debug a difference if I look on the FormGroup depositFormDataService.mainForm:
in: Controls --> title --> value = b
in: value --> title --> null
Why?
I found this post in git-
https://github.com/angular/angular/issues/13792
I tries to do this hack in the component-
ngOnInit() {
this.onChanges();
}
onChanges(): void {
this.depositFormDataService.title.valueChanges
.pipe(takeUntil(this.titleDestroy))
.subscribe(value => {
this.depositFormDataService.mainForm.get('title').patchValue(value, {emitEvent: false});
}
But it did not work and the value was still "null".
Any idea how to make the value be sync with the value inside the controls?
It causes a strange problem that depositFormDataService.mainForm.status differs from the depositFormDataService.title.status.
I think In the HTML you should use formControlName instead of formcontrol like this:
<input formControlName="title" matInput >

Add inputs dynamically when click on button in angular 4

I would create a form with the possibility to add inputs dynamically
I found a question about the same problem in angular 2 but I can't make it working in my exemple
Here's my component ts file :
export class AjoutProjetComponent implements OnInit {
isLinear = false;
firstFormGroup: FormGroup;
secondFormGroup: FormGroup;
constructor(private _formBuilder: FormBuilder) {}
ngOnInit() {
this.secondFormGroup = this._formBuilder.group({
pers: [this._formBuilder.array([this.createItem()])]
});
}
createItem(): FormGroup {
return this._formBuilder.group({
name: ['', Validators.required]
poste: ['', Validators.required],
});
}
addItem(): void {
const control = < FormArray > this.secondFormGroup.controls['pers'];
control.push(this.createItem());
}
}
then HTML file
<mat-step [stepControl]="secondFormGroup">
<form [formGroup]="secondFormGroup">
<ng-template matStepLabel>Constituez votre équipe</ng-template>
<div formArrayName="pers">
<mat-form-field *ngFor="let control of secondFormGroup.controls.pers.controls; let i= index">
<input matInput placeholder="Nom collaborateur" formControlName="name" required>
</mat-form-field>
</div>
</form>
</mat-step>
<div>{{secondFormGroup.value | json}}</div>
When I click in my favorite icon I get this error :
ERROR TypeError: control.push is not a function at AjoutProjetComponent.addItem
How can I make adding dynamically inputs working ?
UPDATE
I have updated my html code so that I could print two inputs but when I run my code I get this error now
ERROR Error: Cannot find control with path: 'pers -> name'
You did not declare your FormArray properly. You use arrays only to initialize simple FormControls, not FormGroups or FormControls, change to :
this.secondFormGroup = this._formBuilder.group({
pers: this._formBuilder.array([this.createItem()]) // remove opening and closing brackets
});
To see the inputs added dynamically to the html, you need to use an ngFor loop. I think you somewhat misunderstood the usage of formArrayName, which only adds context to the template to use with FormArrays. Try this:
<ng-container formArrayName="pers">
<input placeholder="Address"
*ngFor="let control of secondFormGroup.controls.pers.controls"
[formControl]="control.controls.name" required />
</ng-container>
And read more about FormArrayName directive here

How to use onBlur event on Angular2?

How do you detect an onBlur event in Angular2?
I want to use it with
<input type="text">
Can anyone help me understand how to use it?
Use (eventName) while binding event to DOM, basically () is used for event binding. Also, use ngModel to get two-way binding for myModel variable.
Markup
<input type="text" [(ngModel)]="myModel" (blur)="onBlurMethod()">
Code
export class AppComponent {
myModel: any;
constructor(){
this.myModel = '123';
}
onBlurMethod(){
alert(this.myModel)
}
}
Demo
Alternative 1
<input type="text" [ngModel]="myModel" (ngModelChange)="myModel=$event">
Alternative 2 (not preferable)
<input type="text" #input (blur)="onBlurMethod($event.target.value)">
Demo
For a model-driven form to fire validation on blur, you could pass updateOn parameter.
ctrl = new FormControl('', {
updateOn: 'blur', //default will be change
validators: [Validators.required]
});
Design Docs
You can also use (focusout) event:
Use (eventName) for while binding event to DOM, basically () is used for event binding. Also you can use ngModel to get two way binding for your model. With the help of ngModel you can manipulate model variable value inside your component.
Do this in HTML file
<input type="text" [(ngModel)]="model" (focusout)="someMethodWithFocusOutEvent($event)">
And in your (component) .ts file
export class AppComponent {
model: any;
constructor(){ }
/*
* This method will get called once we remove the focus from the above input box
*/
someMethodWithFocusOutEvent() {
console.log('Your method called');
// Do something here
}
}
you can use directly (blur) event in input tag.
<div>
<input [value]="" (blur)="result = $event.target.value" placeholder="Type Something">
{{result}}
</div>
and you will get output in "result"
HTML
<input name="email" placeholder="Email" (blur)="$event.target.value=removeSpaces($event.target.value)" value="">
TS
removeSpaces(string) {
let splitStr = string.split(' ').join('');
return splitStr;
}
/*for reich text editor */
public options: Object = {
charCounterCount: true,
height: 300,
inlineMode: false,
toolbarFixed: false,
fontFamilySelection: true,
fontSizeSelection: true,
paragraphFormatSelection: true,
events: {
'froalaEditor.blur': (e, editor) => { this.handleContentChange(editor.html.get()); }}
This is the proposed answer on the Github repo:
// example without validators
const c = new FormControl('', { updateOn: 'blur' });
// example with validators
const c= new FormControl('', {
validators: Validators.required,
updateOn: 'blur'
});
Github : feat(forms): add updateOn blur option to FormControls
Try to use (focusout) instead of (blur)
Another possible alternative
HTML Component file:
<input formControlName="myInputFieldName" (blur)="onBlurEvent($event)">
TypeScript Component file:
import { OnInit } from '#angular/core';
import { FormBuilder, FormGroup, Validators } from '#angular/forms';
export class MyEditComponent implements OnInit {
public myForm: FormGroup;
constructor(private readonly formBuilder: FormBuilder) { }
ngOnInit() {
this.myForm = this.formBuilder.group({
myInputFieldName: ['initial value', { validators: [Validators.required, Validators.maxLength(100), anotherValidator], updateOn: 'blur' }],
});
}
onBlurEvent(event) {
// implement here what you want
if (event.currentTarget && event.currentTarget.value && event.currentTarget.value !== '') { }
}
}
I ended up doing something like this in Angular 14
<form name="someForm" #f="ngForm" (ngSubmit)="onSubmit(f)"> ...
<input
matInput
type="email"
name="email"
autocomplete="email"
placeholder="EMAIL"
[ngModel]="payload.email"
#email="ngModel"
(blur)="checkEmailBlur(f)"
required
email
tabindex="1"
autofocus
/>
then ... in .ts
checkEmailBlur(f: NgForm) {
const email = f.value.email;

Categories

Resources