-
-
Notifications
You must be signed in to change notification settings - Fork 14
Open
Description
First of all, thanks for the lib!
But I use it with NGRX and with Containers/Presentational (I call them Meat and Skin) components, and things get complicated. I wonder if there is an easier way, or do I use it right?
Here's the code:
const routes: Routes = [
{
path: "",
component: ComponentSidenav,
children: [
...
{
path: "edit/:id",
component: CompanyEditComponent,
canDeactivate: [FormDirtyGuard],
resolve: {
company: CompanyResolver
}
}
]
}
];
Container:
export class CompanyEditComponent implements DirtyComponent {
...
readonly savedSuccessfully$ = this.stateSvc.saveSuccessful$;
isDirty$: Observable<boolean> | boolean | (() => boolean);
constructor(private stateSvc: CompaniesStateService) {
this.isDirty$ = this.savedSuccessfully$.pipe(
withLatestFrom(this.frmIsDirty.asObservable()),
map(([saved, dirty]) => {
// when data is saved then we don't care if form is dirty or not
return saved ? !saved : dirty;
})
);
}
setFormDirty(value: boolean) {
this.frmIsDirty.next(value);
}
private frmIsDirty = new BehaviorSubject<boolean>(false);
}
Presentation:
@Component({
selector: "ilg-company-edit",
templateUrl: "./company-edit-form.component.html",
styleUrls: ["./company-edit-form.component.scss"],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class CompanyEditFormComponent extends IlgDirtyPresentationBase<object, MmlCompany> implements OnInit, OnDestroy {
@Input() company?: MmlCompany;
get form(): FormGroup<any> {
return this.frm;
}
get sourceEntity(): MmlCompany | undefined {
return this.company;
}
frm = this.fb.group<CompanyForm>({...});
constructor(private fb: NonNullableFormBuilder, private route: ActivatedRoute) {
super();
}
override ngOnInit(): void {
super.ngOnInit();
...
// this will have a value 100% because of the data resolver
if (this.company && this.company.id) {
this.frm.patchValue(this.company);
}
}
protected createEntityFromFormValue(frmVal: any): MmlCompany | undefined {
/*
*
* CAREFUL WITH THIS FUNCTION => if any error occurred YOU WON'T SEE IT!
* Use try {} catch {}
*
*/
if (areAllPropsFalsy(frmVal)) {
// if all props are falsy ==> return undefined
return undefined;
}
return new MmlCompany(
frmVal.name ?? "",
frmVal.shortName ?? "",
frmVal.phone ?? "",
frmVal.email ?? "",
this.company ? this.company.printingMml : true,
frmVal.licenseNumber,
frmVal.licenseExpDate,
this.company ? this.company.id : undefined,
frmVal.comment
);
}
}
And here's the common class to implement dirty checking for any of Presentational form component:
export abstract class IlgDirtyPresentationBase<T, R> implements OnInit, OnDestroy {
@Output() formIsDirty = new EventEmitter<boolean>();
abstract form: FormGroup;
abstract sourceEntity: R | undefined;
protected subscriptions = new Subscription();
isDirty$?: Observable<boolean>;
protected abstract createEntityFromFormValue(frmVal: T): R | undefined;
ngOnDestroy() {
this.subscriptions.unsubscribe();
}
ngOnInit(): void {
this.isDirty$ = this.form.valueChanges.pipe(this.checkIsDirty(this.sourceEntity, this.createEntityFromFormValue.bind(this))); // bind() IS IMPORTANT HERE!
this.subscriptions.add(
//
// ===> this subscription MUST BE in ngOnInit !!!
//
this.isDirty$.subscribe((val) => this.formIsDirty.emit(val))
);
}
private checkIsDirty<T, R>(sourceEntity: R | undefined, createEntityFromFormValue: (frmVal: T) => R): OperatorFunction<T, boolean> {
function wrapUserFunction(frmVal: T) {
try {
return createEntityFromFormValue(frmVal);
} catch (e) {
console.error(`createCompanyFromValue exception: ${e}`);
// return {} as R;
throw e;
}
}
return pipe(
debounceTime(400),
distinctUntilChanged(),
map(wrapUserFunction),
map((frmEntity) => !deepEqualRelaxed(sourceEntity, frmEntity)),
startWith(false),
shareReplay()
);
}
I did not find a way to adopt easily dirty-check-forms lib for actual checking. Store shouldn't be present in Presentational components.
Metadata
Metadata
Assignees
Labels
No labels