I developed a custom componet as form field, for doing so I implemented a directive following this article on mediumenter link description here,
this is the directive:
import { Directive, forwardRef, OnDestroy } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { Subject } from 'rxjs';
@Directive({
selector: '[appValueAccessor]',
standalone:true,
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => ValueAccessorDirective),
multi: true,
},
],
})
export class ValueAccessorDirective<T>
implements ControlValueAccessor, OnDestroy
{
private onChange: any;
private onTouched: any;
private valueSubject = new Subject<T>();
private disabledSubject = new Subject<boolean>();
readonly value = this.valueSubject.asObservable();
readonly disabled = this.disabledSubject.asObservable();
constructor() { }
ngOnDestroy(): void {
this.valueSubject.complete();
this.disabledSubject.complete();
}
valueChange(v: T) {
if(this.onChange)
this.onChange(v);
}
touchedChange(v: boolean) {
if(this.onTouched)
this.onTouched(v);
}
writeValue(obj: any): void {
this.valueSubject.next(obj);
}
registerOnChange(fn: any): void {
this.onChange = fn;
}
registerOnTouched(fn: any): void {
this.onTouched = fn;
}
setDisabledState(isDisabled: boolean): void {
this.disabledSubject.next(isDisabled);
}
}
this is the component that import the directive, that should be my custom form field:
import { CommonModule } from '@angular/common';
import { Component, OnInit,Input, EventEmitter,Output, OnChanges, SimpleChanges, OnDestroy, ChangeDetectionStrategy } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { IonicModule, SearchbarCustomEvent } from '@ionic/angular';
import { ValueAccessorDirective } from '../../directives/valueAccessor/value-accessor.directive';
import { Subscription } from 'rxjs';
@Component({
standalone:true,
imports:[
IonicModule,
CommonModule,
FormsModule,
ReactiveFormsModule
],
inputs:['data','multiple','itemTextField','value'],
selector: 'app-searchable-select',
templateUrl: './searchable-select.component.html',
styleUrls: ['./searchable-select.component.scss'],
hostDirectives:[ValueAccessorDirective],
changeDetection:ChangeDetectionStrategy.OnPush
})
export class SearchableSelectComponent implements OnInit,OnChanges,OnDestroy {
isOpen = false
@Input() value = {}
@Input() title = "Search"
@Input() data:any[]
@Input() multiple= false
@Output() selectedChanged:EventEmitter<any> = new EventEmitter()
@Input() itemTextField= 'name'
disabled = false
text =""
touched = false
selected =[]
filtered =[]
subscription = new Subscription()
constructor(
private valueAccessor:ValueAccessorDirective<{value:string,key:string,selected:boolean}[]>
){
this.subscription.add( valueAccessor.value.subscribe((v)=>{
console.log("subscription", v)
}))
}
ngOnDestroy(): void {
this.subscription.unsubscribe()
}
writeValue(obj: string): void {
this.value = obj
}
setDisabledState?(isDisabled: boolean): void {
isDisabled=isDisabled
}
ngOnChanges(changes: SimpleChanges): void {
this.filtered = this.data
}
ngOnInit() {
if(this.value){
this.selected.push(this.value)
}
if( !this.multiple){
this.data.forEach(d=>{d.selected = false})
this.selectedChanged.emit([this.value])
}
}
open(){
this.isOpen=true
}
cancel(){
this.isOpen= false
}
select(){
this.isOpen= false
}
itemSelected(item){
console.log("clicked",item )
//remove not selected item
this.selected = this.multiple? this.data.filter((item)=>item.selected):[item]
this.valueAccessor.valueChange(this.selected)
this.valueAccessor.touchedChange(true)
this.valueAccessor.writeValue(this.selected)
if(!this.multiple){ // we can select only one item
this.data= this.data.map(d=>{
if(d['key']!= item['key'])
d['selected']= false
return d
})
}
this.selectedChanged.emit(this.selected)
this.isOpen= false
}
filter(event:SearchbarCustomEvent){
const filter = event.target.value.toLocaleLowerCase()
this.filtered = this.data.filter(item=>{
return this.leaf(item).toLocaleLowerCase().indexOf(filter)>=0
})
}
leaf= (obj)=>obj[this.itemTextField]
}
after I added the field to the form I get this error message in the console of chrome:
core.mjs:8400 ERROR TypeError: Cannot read properties of null (reading 'writeValue')
at setUpControl (forms.mjs:2939:23)
at FormGroupDirective.addControl (forms.mjs:4771:9)
at FormControlName._setUpControl (forms.mjs:5321:43)
at FormControlName.ngOnChanges (forms.mjs:5266:18)
at FormControlName.rememberChangeHistoryAndInvokeOnChangesHook (core.mjs:1521:14)
at callHook (core.mjs:2444:18)
at callHooks (core.mjs:2403:17)
at executeInitAndCheckHooks (core.mjs:2354:9)
at selectIndexInternal (core.mjs:9059:17)
at ɵɵadvance (core.mjs:9042:5)
the component works as expected, but it is no part of the form. this is the first time I have used standalone component and this directive, I do not think that the poblem is related to the directive.