Angular Component variables returning undefined - javascript

I'm using Angular 10, and i'm havin issues to create a Input Component with ControlValueAccessor.
I'm creating public vars and public arrow functions, and when I call the arrow function is returning undefined.
Here is my .ts code:
import { Component, forwardRef, Input } from '#angular/core';
import { ControlValueAccessor, FormControl, FormGroup, NG_VALUE_ACCESSOR } from '#angular/forms';
#Component({
selector: 'app-input',
templateUrl: './input.component.html',
styleUrls: ['./input.component.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(
() => InputComponent
),
multi: true
}
]
})
export class InputComponent implements ControlValueAccessor {
#Input() public parentForm: FormGroup;
#Input() public fieldName: string;
#Input() public label: string;
public value: string;
public changed: ( value: string ) => void;
public touched: () => void;
public isDisabled: boolean;
get formField (): FormControl {
return this.parentForm?.get( this.fieldName ) as FormControl;
}
constructor () { }
public writeValue ( value: string ): void {
this.value = value;
}
public onChange ( event: Event ): void {
const value: string = (<HTMLInputElement>event.target).value;
this.changed( value );
}
public registerOnChange ( fn: any ): void {
this.changed = fn;
}
public registerOnTouched ( fn: any ): void {
this.touched = fn;
}
public setDisabledState ( isDisabled: boolean ): void {
this.isDisabled = isDisabled;
}
}
And my .html file:
<div class="form-group">
<label class="form-label" [for]="fieldName">
{{ label }}
</label>
<input
type="text"
class="form-control"
[ngClass]="{ 'has-error': formField?.invalid && formField?.dirty }"
[id]="fieldName"
[name]="fieldName"
[value]="value"
[disabled]="isDisabled"
(input)="onChange( $event )"
(blur)="touched()"
/>
<app-field-errors [formField]="formField">
</app-field-errors>
</div>
When I interact with the Input (change/input or blur) I get this errors:
ERROR TypeError: this.changed is not a function
ERROR TypeError: ctx.touched is not a function
I believe the this.changed error is because I'm calling on onChange function, and ctx.touched is because i'm calling on HTML file.
I can access normally the Input() vars, like parentForm, fieldName and label.
Thanks for you help.

Change these lines
public changed: ( value: string ) => void;
public touched: () => void;
to
public changed: any = ( value: string ) => void; // personally I prefer {} rather than void
public touched: any = () => void;

Related

Angular: Accessor not doing two-way-binding

I am trying to write an accessor component, for an input that has a label associated with it, but I am not sure if I am doing this correctly. When I attempt to do two-way-binding, the value isn't set in the input field even though it is set within the class MyComponent.
Why isn't my two-way-binding working? I would expect to see Something Special inside the <input> that is within <third-party-input>. However, the input is empty.
When using <third-party-input> on its own, two-way-binding works there, just not when I wrap my component around it.
Main Component
<ui-input [(ngModel)]="myValue"></ui-input>
export class MyComponent {
public myValue = 'Something Special';
}
Input With Label Component
<label>{{label}}</label>
<third-party-input [(ngModel)]="value"></third-party-input>
#Component({
selector: 'ui-input',
templateUrl: './input.component.html',
providers: [{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => InputComponent),
multi: true
}]
})
export class InputComponent extends AccessorComponent {}
#Component({
selector: 'app-accessor',
template: ''
})
export abstract class AccessorComponent implements ControlValueAccessor {
protected _value: any;
public get value() {
return this._value;
}
public set value(val) {
this._value = val;
this.onChange(val);
this.onTouched();
}
protected onChange = (_: any) => { };
protected onTouched: any = () => { };
public registerOnChange(fn: (_: any) => void): void {
this.onChange = fn;
}
public registerOnTouched(fn: () => void): void {
this.onTouched = fn;
}
public writeValue(value: any): void {
if (value !== undefined) {
this._value = value;
}
}
}

How to unselect selected option-element in select-element using Angular 8

I have the following requirements:
I got two FormControl objects for select-elements mainSelect and subSelect that are required.
subSelect changes depending on the value from mainSelect.
When mainSelect changes to a value in which the value from subSelect isn't included subSelect needs to become invalid so the FormGroup both of the FormControl's are part of becomes invalid, too.
But if the value from subSelect is included subSelect needs to hold his actual value.
(A concrete example is described after the StackBlitz link.)
My problem solving this requirement:
If the value of mainSelect changes and the value of subSelect isn't included subSelect takes the first value of the list instead of becoming null/invalid.
So the SOLUTION would be if the selected value of 'subSelect' becomes null and no value is selected in the browser.
What I tried so far:
I tried to create a component and implement the ControlValueAccessor interface. Seems like here lies my problem. I think I don't really understand how that works.
I watched the following video on YouTube and read articles (1, 2) related to ControlValueAccessor, but still couldn't solve my problem.
This is part of my code:
Also you can find it on StackBlitz
Example
If in the browser MainSelect has the value thirdMainSelect and SubSelect has the value fifthSubSelect and MainSelect changes his value to firstMainSelect SubSelect should have no selected value.
select.component.ts
export class SomeObject {
value: string;
parameters: {[parameterName: string]: string} = {};
}
#Component({
selector: "app-select",
templateUrl: "./select.component.html",
providers: [{
provide: NG_VALUE_ACCESSOR,
useExisting: SelectComponent,
multi: true
}]
})
export class SelectComponent implements ControlValueAccessor, OnChanges {
#ViewChild("select", {static: true}) select: ElementRef;
#Input() tableId: string;
#Input() filter: { [parameterName: string]: string};
returnedTable: SomeObject[];
onChange: (_: any) => void;
onTouched: () => void;
selected: string;
constructor(private tableService: TableService) { }
loadTable(): void {
this.tableService.getTable(this.tableId, this.filter)
.subscribe(table => {
this.returnedTable = table;
});
}
ngOnChanges(): void {
this.loadTable();
}
registerOnChange(fn: any): void {
this.onChange = fn;
}
registerOnTouched(fn: any): void {
this.onTouched = fn;
}
writeValue(value: string): void {
this.selected = value;
}
}
select.component.html
<select class="form-control" #select (change)="onChange($event.target.value)">
<option *ngFor="let item of returnedTable" [value]="item.value" [selected]="selected === item.value">{{item.value}}</option>
</select>
app.component.ts
#Component({
selector: "app-root",
templateUrl: "./app.component.html",
styleUrls: ["./app.component.scss"]
})
export class AppComponent implements OnInit {
form: FormGroup;
containerObject: ContainerObject;
selectedMainValue: string;
constructor(private tableService: TableService,
private formBuilder: FormBuilder) {
}
ngOnInit(): void {
this.tableService.getContainerObject()
.subscribe(containerObject => {
this.containerObject = containerObject;
this.selectedMainValue = containerObject.mainSelect;
this.initForm();
});
}
private initForm(): void {
this.form = this.formBuilder.group({
mainSelect: [this.containerObject.mainSelect, Validators.required],
subSelect: [this.containerObject.subSelect, Validators.required]
});
this.subscribeToMainSelectChanged();
this.subscribeToSubSelectChanged();
}
onSubmit(): void {
if (this.form.valid) {
this.containerObject.mainSelect = this.form.get("mainSelect").value;
this.containerObject.subSelect = this.form.get("subSelect").value;
this.tableService.saveContainerObject(this.containerObject);
}
}
private subscribeToMainSelectChanged() {
this.form.get("mainSelect").valueChanges
.subscribe(mainSelect => {
this.selectedMainValue = mainSelect;
console.log(this.form.status);
});
}
private subscribeToSubSelectChanged() {
this.form.get("subSelect").valueChanges
.subscribe(() => {
console.log(this.form.status);
});
}
}
app.component.html
<div>
<form id="wrapper" [formGroup]="form" (ngSubmit)="onSubmit()">
<div id="left" class="form-group row">
<label for="mainSelect" class="col-form-label col-sm-2">MainSelect</label>
<div class="col-sm-6">
<app-select
id="mainSelect"
formControlName="mainSelect"
[tableId]="'mainSelectTable'"
[filter]="{firstType: 'firstParameter'}"
></app-select>
</div>
</div>
<div id="right" class="form-group row">
<label for="subSelect" class="col-form-label col-sm-2">SubSelect</label>
<div class="col-sm-6">
<app-select
id="subSelect"
formControlName="subSelect"
[tableId]="'firstParameter'"
[filter]="{firstType: 'firstParameter', secondType: selectedMainValue}"></app-select>
</div>
</div>
<p></p>
<button id="button" type="submit">Submit</button>
</form>
</div>
I think that it's only use valuesChange. If you has two arrays data and subdata and a form like
form = new FormGroup({
prop1: new FormControl(),
prop2: new FormControl()
});
A simple
this.form.get("prop1").valueChanges.subscribe(res => {
this.dataService.getData(res).subscribe(data=>{
this.subdata=data;
if (!this.subdata.find(x=>x.value==this.form.get("prop2").value))
this.form.get("prop2").setValue(null);
}).unsubscribe()
});
must be enough, see stackblitz
After a while trying to solve this problem only using the SelectComponent and especially writeValue, the following code did the job:
I changed the select.component.ts as following:
export class SomeObject {
value: string;
parameters: {[parameterName: string]: string} = {};
}
#Component({
selector: "app-select",
templateUrl: "./select.component.html",
providers: [{
provide: NG_VALUE_ACCESSOR,
useExisting: SelectComponent,
multi: true
}]
})
export class SelectComponent implements ControlValueAccessor, OnChanges {
#ViewChild("select", {static: true}) select: ElementRef;
#Input() tableId: string;
#Input() filter: { [parameterName: string]: string};
returnedTable: SomeObject[];
onChange: (_: any) => void;
onTouched: () => void;
selected: string;
constructor(private tableService: TableService) { }
loadTable(): void {
this.tableService.getTable(this.tableId, this.filter)
.subscribe(table => {
this.returnedTable = table;
if (!!this.select && !!this.select.nativeElement.value) {
this.writeValue(this.select.nativeElement.value);
this.onChange(this.selected);
}
});
}
ngOnChanges(): void {
this.loadTable();
}
registerOnChange(fn: any): void {
this.onChange = fn;
}
registerOnTouched(fn: any): void {
this.onTouched = fn;
}
writeValue(value: string): void {
if (!!this.returnedTable && !this.returnedTable.some(item => item.value === value)) {
this.selected = null;
} else {
this.selected = value;
}
}
}
And the select.component.html like this:
<select class="form-control" #select (change)="onChange($event.target.value)">
<option hidden *ngIf="!selected" value=""></option>
<option *ngFor="let item of returnedTable" [value]="item.value" [selected]="selected === item.value">{{item.value}}</option>
</select>
To deselect an option:— option.selected = false.
Angular library with function decompilation at its core for $scope and Nested Forms, an Angular feature that indicates that Angular team doesn't know how to effectively use HTML. HTML doesn't allow nested forms so why the hell would you try to shoehorn the language to do that? More trouble than it's worth. Of course, you can't expect much better from guys like Bradley Green, former Angular JS manager.

Angular 2 custom control inside form, default value is [object Object]

I've a simple angular2 reactive form:
formangular.component.html
<form novalidate (ngSubmit)="onSubmit(formDir.value)" [formGroup]="complexForm" #formDir="ngForm">
<div class="form-group">
<custom-input id="idCausaleRitenuta"
class="form-control"
label="Codice:"
formControlName="idCausaleRitenuta"
[(ngModel)]="idCausaleRitenuta"></custom-input>
<div *ngIf="idCausaleRitenuta.invalid && (idCausaleRitenuta.dirty || idCausaleRitenuta.touched)"
class="alert alert-danger">
........
the relative .ts file:
formangular.component.ts
export class FormAngularComponent {
get idCausaleRitenuta() { return this.complexForm.get('idCausaleRitenuta');}
constructor(private fb: FormBuilder, private ritenuteService: RitenuteService, private sharedService: SharedService) {
this.createForm();}
createForm() {
this.complexForm = this.fb.group({
idCausaleRitenuta: ['', Validators.compose([Validators.required, Validators.maxLength(4)])],
........
...And the custom-input control:
custominput.component.ts
.................
#Component({
selector: 'custom-input',
template: `<div>
<label *ngIf="label" [attr.for]="identifier">{{label}}</label>
<input [id]="identifier"
[type]="type"
[class]="class"
[placeholder]="placeholder"
[(ngModel)]="value"
(blur)="touch()" />
</div>`,
.......
})
export class CustomInputComponent<T> implements ControlValueAccessor {
.........
identifier = `custom-input-${identifier++}`;
private innerValue: T;
private changed = new Array<(value: T) => void>();
private touched = new Array<() => void>();
get value(): T {
return this.innerValue;
}
set value(value: T) {
if (this.innerValue !== value) {
this.innerValue = value;
this.changed.forEach(f => f(value));
}
}
touch() {
this.touched.forEach(f => f());
}
writeValue(value: T) {
this.innerValue = value;
}
registerOnChange(fn: (value: T) => void) {
this.changed.push(fn);
}
registerOnTouched(fn: () => void) {
this.touched.push(fn);
}
}
let identifier = 0;
When I render the form, the value of the input inside the 'custom control' is '[object Object]', can someone tell me why?
When you make this call:
get idCausaleRitenuta() { return this.complexForm.get('idCausaleRitenuta');}
You are actually getting the FormControl object (not the value of the form control).
You can access the value property of that control like this:
get idCausaleRitenuta() { return this.complexForm.get('idCausaleRitenuta').value;}

Angular components as self validating formControls

I'm not sure if there's a better way to do this, I assume there should be. Basically I have a component I want to treat as a standalone form control. This control will always have some sort of special validation attached and I would like it to bubble up to the form whenever the component is used.
I've attached a plunker. Is there a way to for the form to be marked invalid if the component/formControl is invalid? I know I could add the validator to the form itself, but I would like to make things easy and more predictable for future use of this component. I'm also open to better ideas of doing this.
#Component({
selector: 'my-app',
template: `
<form [formGroup]="form">
<my-component-control formControlName="myComponent"></my-component-control>
</form>
<div>Form Value: {{form.value | json}}</div>
<div>Form Valid: {{form.valid}}</div>
`,
})
export class App {
constructor(fb: FormBuilder) {
this.form = fb.group({
myComponent: ''
});
}
}
#Component({
selector: 'my-component-control',
template: `
<div>Control Errors: {{control.errors | json}}</div>
<input [formControl]="control">
`,
providers: [
{
provide: NG_VALUE_ACCESSOR,
multi: true,
useExisting: forwardRef(() => MyComponentComponent),
}
]
})
export class MyComponentComponent implements ControlValueAccessor {
control: FormControl;
onChange: any = () => { };
onTouched: any = () => { };
constructor() {
this.control = new FormControl('', (control) => {
return control.value ? null : {shouldHaveSomething: true};
});
this.control.valueChanges.subscribe((value) => {
this.onChange(value);
this.onTouched();
});
}
writeValue (obj: any): void {
this.control.setValue(obj);
}
registerOnChange (fn: any): void {
this.onChange = fn;
}
registerOnTouched (fn: any): void {
this.onTouched = fn;
}
setDisabledState?(isDisabled: boolean): void {
throw new Error('Method not implemented.');
}
}
One solution could be using setValidators method on host AbstractControl
To do so I'm going to get reference to AbstractControl through NgControl. We can't just inject NgControl in constructor because we'll get issue with instantiating cyclic dependency.
constructor(private injector: Injector) {
...
}
ngOnInit() {
Promise.resolve().then(() => {
const hostControl = this.injector.get(NgControl, null);
if (hostControl) {
hostControl.control.setValidators(this.control.validator);
hostControl.control.updateValueAndValidity();
}
});
}
Ng-run Example

Angular custom form input with error message

I'd like to create a custom form input that is just a wrapper around the normal input and that displays an error message in case the formControl isn't valid.
So I made a component implementing ControlValueAccessor but now that I want to display the error message I realize I have no hold of the FormControl, thus I need to pass it as #Input(). The problem is... Why implement the ControlValueAccessor in the first place if I can just pass the FormControl as input ? I'm confused.
html
<input
[placeholder]="placeholder"
[type]="type"
[(ngModel)]="value"
(keyup)="onChange()"
(change)="onChange()"
(blur)="onTouched()"
[disabled]="disabled"/>
ts
#Component({
selector: 'input-app',
templateUrl: './input.component.html',
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => InputComponent),
multi: true
}
]
})
export class InputComponent implements ControlValueAccessor {
#Input() type = 'text';
#Input() placeholder = 'placeholder';
#Input() errorMsg = '';
value: string;
disabled: boolean;
private onChangeFn;
private onTouchedFn;
writeValue(val: string | number): void {
this.value = val;
}
registerOnChange(fn: any): void {
this.onChangeFn = fn;
}
registerOnTouched(fn: any): void {
this.onTouchedFn = fn;
}
setDisabledState?(isDisabled: boolean): void {
this.disabled = isDisabled;
}
onChange(){
this.onChangeFn(this.value);
}
onTouched(){
this.onTouchedFn();
}
}

Categories