Created
December 4, 2022 18:25
-
-
Save cbossi/0979c7dcdc2e215e28772a11ccc602ab to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import {Directive, OnDestroy, OnInit} from '@angular/core'; | |
import {AbstractControl, ControlValueAccessor, FormGroup, NgControl} from '@angular/forms'; | |
import {Subscription} from 'rxjs'; | |
export abstract class AbstractControlComponent implements ControlValueAccessor { | |
private onTouchedFn: () => void; | |
protected constructor(public readonly controlDir: NgControl | null) { | |
if (controlDir) { | |
controlDir.valueAccessor = this; | |
} | |
} | |
public registerOnTouched(onTouchedFn: () => void): void { | |
this.onTouchedFn = onTouchedFn; | |
} | |
public onTouched(): void { | |
if (this.onTouchedFn) { | |
this.onTouchedFn(); | |
} | |
} | |
public abstract registerOnChange(onChangeFn: (value: unknown) => void): void; | |
public abstract writeValue(obj: unknown): void; | |
protected get control(): AbstractControl | null | undefined { | |
return this.controlDir?.control; | |
} | |
} | |
@Directive() | |
export abstract class FormGroupComponent extends AbstractControlComponent implements OnInit, OnDestroy { | |
public abstract readonly form: FormGroup; | |
private readonly subscriptions: Subscription[] = []; | |
protected constructor(controlDir: NgControl | null) { | |
super(controlDir); | |
} | |
public ngOnInit(): void { | |
this.propagateFormStateToParentForm(); | |
setTimeout(() => { | |
// in the parent form, the value of the sub-form initially is 'null'. After the sub form has been initialized, we manually have to trigger an update. | |
this.form.updateValueAndValidity(); | |
}); | |
} | |
/** | |
* For some reason angular does not propagate the status of a nested form to its parent form (https://github.com/angular/angular/issues/10530). | |
* In the parent form, the nested form is just a FormControl that has no validator and hence is always valid. | |
* This method manually propagates the validation status of the sub form to its respective FormControl in the parent form. | |
*/ | |
private propagateFormStateToParentForm(): void { | |
if (this.control) { | |
this.control.setValidators(() => this.form.valid ? null : {}); | |
this.subscriptions.push(this.form.statusChanges.subscribe(() => { | |
this.control?.updateValueAndValidity(); | |
this.markAsPristine(); | |
this.markAsDirty(); | |
})); | |
} | |
} | |
private markAsPristine(): void { | |
if (this.form.pristine) { | |
this.control?.markAsPristine(); | |
} | |
} | |
private markAsDirty(): void { | |
if (this.form.dirty) { | |
this.control?.markAsDirty(); | |
} | |
} | |
public registerOnChange(onChangeFn: (value: unknown) => void): void { | |
this.subscriptions.push(this.form.valueChanges.subscribe(onChangeFn)); | |
} | |
public writeValue(value: {[key: string]: unknown}): void { | |
if (value) { | |
this.form.setValue(value, {emitEvent: false}); | |
} | |
} | |
public setDisabledState(isDisabled: boolean): void { | |
if (isDisabled) { | |
this.form.disable(); | |
} else { | |
this.form.enable(); | |
} | |
} | |
public ngOnDestroy(): void { | |
this.subscriptions.forEach(subsription => subsription.unsubscribe()); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Example usage of above base class. | |
*/ | |
@Component({ | |
selector: 'my-form', | |
templateUrl: 'my-form.component.html', | |
}) | |
export class MyFormComponent extends FormGroupComponent { | |
public readonly form: FormGroup; | |
constructor(@Optional() @Self() controlDir: NgControl, | |
private readonly formBuilder: FormBuilder) { | |
super(controlDir); | |
this.form = this.initForm(); | |
} | |
private initForm(): FormGroup { | |
return this.formBuilder.group({ | |
// create form | |
}); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment