Skip to content

Instantly share code, notes, and snippets.

@cbossi
Created December 4, 2022 18:25
Show Gist options
  • Save cbossi/0979c7dcdc2e215e28772a11ccc602ab to your computer and use it in GitHub Desktop.
Save cbossi/0979c7dcdc2e215e28772a11ccc602ab to your computer and use it in GitHub Desktop.
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());
}
}
/**
* 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