Angular FormArray child component with value accessor

Viewed 25

I have a list of tasks along with some other fields, and I want to separate the list form from the main one, because it's used twice on the same page. One for just showing tasks with priority 0, and the other part just for showing tasks with priority 1, but both should be equally editable

Issue is I can't seem to find a way to bind the parent form to the child form, right now it returns a Must supply a value for form control with name: 'tasks' error message, and if I change formControlName on the parent to formArrayName, the values won't change at all.

Here's a general idea of what the two forms look like:

// parent form structure
this.tasksForm = this.fb.group({
    tasks: this.fb.array(
        [
            this.fb.group({
                id: [null],
                name: [null, customValidator()],
                priority: [0]
            }),
            this.fb.group({
                id: [null],
                name: [null, customValidator()],
                priority: [0]
            })
        ],
    )
    // ...some other fields
});


// parent template
<form [formGroup]="editForm">
   <app-tasks-form formControlName="tasks" [displayPriority]="0"></app-tasks-form>
   <hr />
   <app-tasks-form formControlName="tasks" [displayPriority]="1"></app-tasks-form>
</form>

child form

// child form
export class TasksFormComponent implements ControlValueAccessor, OnDestroy, Validator {
    @Input() displayPriority: number;
    tasksForm: FormGroup;
    touched = false;
    disabled = false;

    onTouched = () => {};
    onChangeSubs: Subscription[] = [];

    constructor(private fb: FormBuilder) {
        this.tasksForm = this.fb.group({
            tasks: this.fb.array([])
        });
    }

    get tasksField() {
        return this.tasksForm.get('tasks') as FormArray;
    }

    ngOnDestroy() {
        for (let sub of this.onChangeSubs) {
            sub.unsubscribe();
        }
    }

    registerOnChange(onChange) {
        const sub = this.tasksForm.valueChanges.subscribe(onChange);
        this.onChangeSubs.push(sub);
    }

    registerOnTouched(onTouched) {
        this.onTouched = onTouched;
    }

    writeValue(value) {
        if (value) {
            this.tasksForm.setValue(value, { emitEvent: false });
        }
    }

    markAsTouched() {
        if (!this.touched) {
            this.onTouched();
            this.touched = true;
        }
    }

    setDisabledState(disabled) {
        if (disabled) {
            this.tasksForm.disable();
        } else {
            this.tasksForm.enable();
        }
    }

    validate() {
        if (this.tasksForm.valid) {
            return null;
        }
        let errors = {};
        if (this.tasksField.errors) {
            errors.tasks = this.tasksField.errors;
        }

        return errors;
    }

    onDeleteOption = () => {
        this.tasksDels.push(this.tasksField.at(index).get('asc_lib_option_id')?.value);
        this.tasksField.removeAt(index);
    };
}



// child template
<div [formGroup]="taskForm">
    <div
        formArrayName="tasks"
        *ngFor="let task of taskForm.get('tasks').controls; let i = index"
    >
        <div *ngIf="task.value.priority === displayPriority">
            <div formGroupName="{{ i }}">
                <input formControlName="name" type="text" (blur)="onTouched()" />
            </div>
            <button (click)="onDeleteTask(i)">X</button>
        </div>
    </div>
</div>

Thanks in advance.

0 Answers
Related