Reputation: 71
Currently I am created two directive to project . But one directive useed for if checkbox is checked , I will get 1, if checked is false i will get 0. Another Directive is used for convert value "1" to 1 like that.
I am using TrueFalseValueDirective only few checkbox
Intension to achieve the Formatters and Parsers - NumberDirective "ng-true-value" and "ng-false-value" alternatives in Angular - TrueFalseValueDirective
Issue:
Both directive is not working. either one directive is working. I need to make two directive workable
Template:
<form #f="ngForm">
<input type="checkbox" [(ngModel)]="data.option" name="options" checked="data.checked" trueFalseValue>
<select [(ngModel)]="data.selection" name="selection">
<option value="1">option 1</option>
<option value="2">option 2</option>
<option value="3">option 3</option>
</select>
<input type="text" name="Text1" [disabled]="data.selection!==1" [(ngModel)]="data.Text1" />
<input type="text" name="Text2" [disabled]="data.selection!==2" [(ngModel)]="data.Text2" />
<input type="text" name="Text3" [disabled]="data.selection!==3" [(ngModel)]="data.Text3" />
</form>
@Directive({
selector: '[ngModel]',
})
export class NumberDirective implements ControlValueAccessor {
onChange;
constructor( private renderer : Renderer2,
private element : ElementRef, public ngControl: NgControl ) {
if (ngControl) {
ngControl.valueAccessor = this;
}
}
@HostListener('input', [ '$event.target.value' ])
input( value ) {
this.onChange(parsrInt(value, 10);
}
writeValue( value : any ) : void {
const element = this.element.nativeElement;
this.renderer.setProperty(element, 'value', String(value));
}
registerOnChange( fn : any ) : void {
this.onChange = fn;
}
}
import {
Directive,
Input,
forwardRef,
HostListener,
ElementRef,
Renderer2
} from '@angular/core';
import {
ControlValueAccessor,
NgControl
} from '@angular/forms';
@Directive({
selector: 'trueFalseValue',
})
export class TrueFalseValueDirective implements ControlValueAccessor {
private propagateChange = (_: any) => {};
trueValue = 1; // some time true
falseValue = 0; // sometime true
constructor(private elementRef: ElementRef, private renderer: Renderer2, public ngControl: NgControl, ) {
if (ngControl) {
ngControl.valueAccessor = this;
}}
@HostListener('change', ['$event'])
onHostChange(ev) {
this.propagateChange(ev.target.checked ? this.trueValue : this.falseValue);
}
writeValue(obj: any): void {
if (obj === this.trueValue) {
this.renderer.setProperty(this.elementRef.nativeElement, 'checked', true);
} else {
this.renderer.setProperty(this.elementRef.nativeElement, 'checked', false);
}
}
registerOnChange(fn: any): void {
this.propagateChange = fn;
}
registerOnTouched(fn: any): void {}
setDisabledState?(isDisabled: boolean): void {}
}
Refered URL :
Upvotes: 2
Views: 54
Reputation: 57696
Here are two approaches to solve this problem, I prefer the first option, because it works on both the binded data and on the form.
The approach you have taken is partially correct, since write value is the correct way to emulate formatter and parser, but instead of messing with the internal angular ngModel
directive, which can cause wierd bugs throughout the application, you can create a custom component which can do the same functionality.
I created a custom form field which will take the true and false value as input and set them based on the checkbox selection, this method, seems to be best, only downside is you need to create a component.
import {
Component,
Directive,
HostBinding,
HostListener,
Input,
Output,
EventEmitter,
} from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import {
FormsModule,
NG_VALUE_ACCESSOR,
NgControl,
ControlValueAccessor,
} from '@angular/forms';
import { CommonModule } from '@angular/common';
@Component({
selector: 'app-true-false-value',
standalone: true,
template: `
<input type="checkbox" [checked]="_value === this.trueValue" (input)="valueChange($event)"/>
`,
providers: [
{
provide: NG_VALUE_ACCESSOR,
multi: true,
useExisting: TrueFalseValueComponent,
},
],
})
export class TrueFalseValueComponent implements ControlValueAccessor {
_value = 0;
@Input()
value: any;
onChange = (value: any) => {};
onTouched = () => {};
touched = false;
disabled = false;
@Input() trueValue: any = 1; // some time true
@Input() falseValue: any = 0; // sometime true
writeValue(value: number) {
this._value = value;
}
registerOnChange(onChange: any) {
this.onChange = onChange;
}
registerOnTouched(onTouched: any) {
this.onTouched = onTouched;
}
markAsTouched() {
if (!this.touched) {
this.onTouched();
this.touched = true;
}
}
setDisabledState(disabled: boolean) {
this.disabled = disabled;
}
valueChange(event: any) {
const value = event.target.checked;
this.markAsTouched();
this.onChange(value ? 'Y' : 'N');
}
}
@Component({
selector: 'app-root',
imports: [FormsModule, TrueFalseValueComponent, CommonModule],
standalone: true,
template: `
<form #f="ngForm">
<app-true-false-value name="options" [(ngModel)]="data.option" falseValue='N' trueValue='Y' trueFalseValue/>
<br/>
<select [(ngModel)]="data.selection" name="selection">
<option [ngValue]="1">option 1</option>
<option [ngValue]="2">option 2</option>
<option [ngValue]="3">option 3</option>
</select>
<br/>
<input type="text" name="Text1" [disabled]="data.selection!==1" [(ngModel)]="data.Text1" />
<input type="text" name="Text2" [disabled]="data.selection!==2" [(ngModel)]="data.Text2" />
<input type="text" name="Text3" [disabled]="data.selection!==3" [(ngModel)]="data.Text3" />
</form>
{{f.form.value | json}}
`,
})
export class App {
name = 'Angular';
data = {
option: 'Y',
selection: 1,
Text1: 'Text1',
Text2: 'Text2',
Text3: 'Text3',
};
}
bootstrapApplication(App);
The AngularJS formatters and parsers
, are partially replaced by getters and setters
of angular. As the name implies the method gets called when the value is accessed or value is set.
The problem with this method as seen in the stackblitz is that, the data objects gets updated perfectly but the form is not updated with the properly value.
get optionTransformed() {
return this.data.option === 'Y';
}
set optionTransformed(value: boolean) {
this.data.option = value ? 'Y' : 'N';
}
On the HTML, we set the property to ngModel
which both accesses the data and sets the data, here we apply the transform.
<input type="checkbox" name="options" [(ngModel)]="optionTransformed"/>
import { Component } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { CommonModule } from '@angular/common';
@Component({
selector: 'app-root',
imports: [FormsModule, CommonModule],
standalone: true,
template: `
<form #f="ngForm">
<input type="checkbox" name="options" [(ngModel)]="optionTransformed"/>
<br/>
<select [(ngModel)]="data.selection" name="selection">
<option [ngValue]="1">option 1</option>
<option [ngValue]="2">option 2</option>
<option [ngValue]="3">option 3</option>
</select>
<br/>
<input type="text" name="Text1" [disabled]="data.selection!==1" [(ngModel)]="data.Text1" />
<input type="text" name="Text2" [disabled]="data.selection!==2" [(ngModel)]="data.Text2" />
<input type="text" name="Text3" [disabled]="data.selection!==3" [(ngModel)]="data.Text3" />
</form>
<br/>
<br/>
<br/>
Form:
<br/>
{{f.form.value | json}}
<br/>
<br/>
<br/>
<br/>
Data:
<br/>
{{data | json}}
`,
})
export class App {
name = 'Angular';
data = {
option: 'Y',
selection: 1,
Text1: 'Text1',
Text2: 'Text2',
Text3: 'Text3',
};
get optionTransformed() {
return this.data.option === 'Y';
}
set optionTransformed(value: boolean) {
this.data.option = value ? 'Y' : 'N';
}
}
bootstrapApplication(App);
Upvotes: 0