#Reactive form with child input components

1 messages · Page 1 of 1 (latest)

neat sable
#

Greetings! Hope you're doing well ngheart

I need a little help with an implementation I'm doing with FormGroup and FormControlName. I'm developing an app where I created the Modal component (which has a form) and FormField component. This is in order to reuse the modal aand to reuse the form fields which is most likely the case in most applications, and to do some boilerplate since I'm using a library (TaigaUI). I'm using ReactiveFormsModule for forms handling and defined the form group attribute in the modal component, which instantiates the FormField that would represente each individual formControlName from the form.

The thing is that I've been encountering some errors, I did some research and apparently, I need to implement the ControlValueAccesor interface in order to achieve this, but even so I don't see the way to get the value the user types on the inputs from the child component all the way up to the parent component. Here's the code:

#

Sign up (Main feature)

<cf-modal [form]="form" (submitModal)="onSubmit()" />
import { Component } from '@angular/core'
import { FormGroup, FormControl, ReactiveFormsModule } from '@angular/forms'
import { ModalComponent } from '@components/index'

@Component({
    selector: 'cf-sign-up',
    standalone: true,
    imports: [ModalComponent, ReactiveFormsModule],
    templateUrl: './sign-up.component.html',
    styleUrl: './sign-up.component.sass'
})
export class SignUpComponent {
    form = new FormGroup({
        name: new FormControl(''),
        email: new FormControl(''),
        ticket: new FormControl(0)
    })

    onSubmit() {
        console.log(this.form.value)
    }
}
#

Modal component

<main>
    <header>
        <section>
            <i class="ph-duotone ph-film-slate"></i>
            <h1 class="tui-text_h1">CINEMAFUN</h1>
        </section>
        <section>
            <h2>Welcome!</h2>
            <h3>Enter your name, email and the ticket price to continue</h3>
        </section>
    </header>

    <form [formGroup]="form" (ngSubmit)="onSubmit()" id="form-modal">
        <cf-form-field [type]="'text'" [label]="'Full name'" [placeholder]="'Jane Doe'" [iconLeft]="'tuiIconUser'" formControlName="name" />
        <cf-form-field [type]="'email'" [placeholder]="'[email protected]'" [iconLeft]="'tuiIconMail'" formControlName="email" />
        <cf-form-field [type]="'number'" [placeholder]="'4.45'" [iconLeft]="'tuiIconDollarSign'" formControlName="ticket" />
    </form>

    <footer>
        <cf-button label="Sign up" type="submit" />
    </footer>
</main>
import { Component, EventEmitter, Input, Output } from '@angular/core'
import { FormGroup, FormControl, ReactiveFormsModule } from '@angular/forms'
import { ButtonComponent, FormFieldComponent } from '@components'

@Component({
    selector: 'cf-modal',
    standalone: true,
    imports: [ButtonComponent, FormFieldComponent, ReactiveFormsModule],
    templateUrl: './modal.component.html',
    styleUrl: './modal.component.sass'
})
export class ModalComponent {
    @Input() form!: FormGroup
    @Output() submitModal = new EventEmitter()

    onSubmit() {
        console.log(this.form.value)
        this.submitModal.emit(this.form)
    }
}
#

FormField component

@if (['text', 'email'].includes(formField)) {
    <tui-input
        [tuiTextfieldLabelOutside]="true"
        [tuiTextfieldCleaner]="showCleaner"
        [tuiTextfieldIconLeft]="iconLeft"
        [tuiTextfieldIcon]="iconRight">
        {{ placeholder }}
        <input tuiTextfield [type]="type" [placeholder]="placeholder" />
    </tui-input>
} @else if (formField === 'number') {
    <tui-input-number [tuiTextfieldLabelOutside]="true" [tuiTextfieldIconLeft]="iconLeft" [tuiTextfieldIcon]="iconRight">{{
        placeholder
    }}</tui-input-number>
}
#
import { Component, Input, forwardRef } from '@angular/core'
import { ControlValueAccessor, FormsModule, NG_VALUE_ACCESSOR, ReactiveFormsModule } from '@angular/forms'
import { TuiTextfieldControllerModule } from '@taiga-ui/core'
import { TuiInputModule, TuiInputNumberModule } from '@taiga-ui/kit'

@Component({
    selector: 'cf-form-field',
    standalone: true,
    imports: [TuiInputModule, TuiInputNumberModule, TuiTextfieldControllerModule, ReactiveFormsModule, FormsModule],
    templateUrl: './form-field.component.html',
    styleUrl: './form-field.component.sass',
    providers: [
        {
            provide: NG_VALUE_ACCESSOR,
            useExisting: forwardRef(() => FormFieldComponent),
            multi: true
        }
    ]
})
export class FormFieldComponent implements ControlValueAccessor {
    @Input({ required: true }) placeholder = ''
    @Input({ required: true }) type = ''
    @Input() formField = ''
    // @Input() formControlName = ''
    @Input() label = ''
    @Input() showCleaner = false
    @Input() iconLeft = ''
    @Input() iconRight = ''
    value = ''

    ngOnInit(): void {
        if (this.type !== 'select') this.formField = this.type
    }

    propagateChange = (_: any) => {}
    onTouched = () => {}

    writeValue(_value: string): void {
        if (_value === undefined) return
        this.value = _value
        this.propagateChange(this.value)
    }

    registerOnChange(fn: any): void {
        this.propagateChange = fn
    }

    registerOnTouched(fn: any): void {
        this.onTouched = fn
    }
}
#

As you can see, when I execute the submit event I'm only printing the form (to do some testing). If I type something on one of the fields (the name field, for example) and then press enter, then all the fields of the form are printed as empty, as if they didn't receive the value typed by the user.

I've consulted several blogs (and AIs ofc 😂 ) and followed all the implementation they mentioned for the ControlValueAccesor interface but to no avail 😭

Could you enlighten me in this matter, please? 🥺

neat sable
neat sable
#

@toxic compass Hi theere, apologies for the ping, but it has been a few days and I'm a little stuck with this 🥺 (just in case I'm pinging you since I noticed you're the one that answered the last forums so... 😂)

toxic compass
#

You don't seem to ever call propagateChange, except when you shouldn't (in writeValue).