I have a formControl which I'm trying to setValue and then trigger change detection so valueChanges in my other components would get the change i'm setting here- but it's not working. Even if i put emitEvent: true. Doesn't trigger valueChanges
<mat-select [formControl]="myCtrl" (selectionChange)="changed($event.value)">
<mat-option ... >{{ ... }}</mat-option>
</mat-select>
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => MyComponent),
multi: true,
},
],
...
public selectedValue: number;
public changed: (value: number) => void;
public touched: () => void;
public isDisabled: boolean;
writeValue(value: any): void {
this.selectedValue = value;
}
registerOnChange(fn: any): void {
this.changed = fn;
}
registerOnTouched(fn: any): void {
this.touched = fn;
}
setDisabledState?(isDisabled: boolean): void {
this.isDisabled = isDisabled;
...
myCtrl = = new FormControl();
...
ngOnInit(): void {
this.subject$.subscribe((data) => {
this.data = data
if (data) {
//this line below doesn't seem to kick off change detection
this.myCtrl.setValue(1, { emitEvent: true })
/*I also tried forcing change detection with the this.changed method but got an
error like ERROR TypeError: this.changed is not a function*/
//this.changed(1)
}
}
)
}
I guess you are not getting it right. you just need to make a function and on selectionChange() call that. like in your case make a function named changed. try to console the whole event and check if value shows in the object if it does. you can use that value anywhere you need to.i.e. pass to a variable or an observer
Related
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;
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;
}
}
}
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
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();
}
}
I wrote a very simple custom form control and I didn't change it's changeDetectionStrategy.
#Component({
selector: 'counter',
template: `
<button (click)="increase($event)">+</button>
{{counter}}
<button (click)="decrease($event)">-</button>
`,
providers: [{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => CounterComponent),
multi: true
}]
})
export class CounterComponent implements OnInit, ControlValueAccessor {
private counter: number = 0;
private onChange: (_: any) => void;
private onTouched: () => void;
constructor(private _cdr: ChangeDetectorRef) { }
ngOnInit() { }
writeValue(value) {
console.log(`Write value`, value);
this.counter = value;
// this._cdr.markForCheck(); // it works
// Use onChange works too
// if (this.onChange) {
// this.onChange(value);
// }
}
registerOnChange(fn: (_: any) => void): void { this.onChange = fn; }
registerOnTouched(fn: () => void): void { this.onTouched = fn; }
increase() {
this.counter++;
this.onChange(this.counter);
}
decrease() {
this.counter--;
this.onChange(this.counter);
}
}
Then I use it in a component named ngmodel-demo with onPush changeDetectionStrategy.
#Component({
selector: 'ngmodel-demo',
template: `
<h3>NgModel Demo</h3>
<p>Count: {{count}}</p>
<counter [(ngModel)]="count"></counter>
`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class NgmodelDemoComponent {
#Input() name: string;
public count = 1;
constructor(private _cdRef: ChangeDetectorRef) {}
}
When I ran the app, I found that the counter component have got the value 1, but its view didn't update.
Then I set a timer to update the ngModel and mark for check.
ngOnInit() {
setInterval(() => {
this.count = ++this.count;
this._cdRef.markForCheck();
}, 3000);
}
The result is that each time the value that counter component's view shows is the value of the last ngModel's.
Manually calling markForCheck in writeValue method works. But I did not use the onPush strategy, I do not understand why to manually call?
There is also a puzzle that is why calling onChange in writeValue also works.
Online demo link on stackblitz: https://stackblitz.com/edit/angular-cfc-writevalue
It is a bug in Angular. I opened an issue about it. You can subscribe if interested.