I need to team suggestion , I have more than 10 form. I want create common directives or any other option form value change before routing. If form changed we need to get confirmation from notification. In candeactive method, we are implementing each component. I want to maintain single file
2 Answers
If "changes" refer to comparison of the form before routing (current) to its original default/placeholder value,
Native JavaScript
one of the alternatives for the case (without using directives) is to use native JavaScript Window: beforeunload event. This alternative might not looks the prettiest, but might be the least overhead (no need to configure 10 forms manually)
We can do something like this in the parent component:
//Vanilla JavaScript
const beforeUnloadHandler = (event) => {
event.preventDefault();
};
const nameInput = document.querySelector(".nameForm");
const nameInputPlaceholder = document.querySelector(".nameForm").placeholder;
nameInput.addEventListener("input", (event) => {
if (event.target.value !== nameInputPlaceholder) {
window.addEventListener("beforeunload", beforeUnloadHandler);
} else {
window.removeEventListener("beforeunload", beforeUnloadHandler);
}
});
<!--HTML-->
<div>
<form>
<label for="name">Input your name*:</label>
<input type="text" name="name" id="name" class="nameForm" placeholder="please input your entry" />
</form>
</div>
<div>
<form>
<label for="nickname">Input your nickname*:</label>
<input type="text" name="nickname" id="nickname" class="nameForm" placeholder="please input your entry" />
</form>
</div>
Angular
Referring to another thread "subscribe to valueChanges from input FormControl in FormGroup", Touqeer Shafi had a good idea and example with Angular FormGroup, ngModel, and subscription. However, this will require you to set everything up for each one of the 10 forms.
Assuming we already have the mean of navigation/routing detection, we also can do something similar such that:
//Angular
myForm: FormGroup;
name = '';
nickname = '';
ngOnInit() {
this.myForm = this.formBuilder.group({
name: [this.name],
nickname: [this.nickname]
});
this.myForm.controls['name'].valueChanges.subscribe(value => {
console.log(value);
});
this.myForm.controls['nickname'].valueChanges.subscribe(value => {
console.log(value);
});
}
<!--HTML-->
<form [formGroup]="myForm">
<div>
<label for="name">Input your name*:</label>
<input type="text" name="name" id="name" [(ngModel)]="name" class="nameForm" placeholder="please input your entry" />
</div>
<div>
<label for="nickname">Input your nickname*:</label>
<input type="text" name="nickname" id="nickname" [(ngModel)]="nickname" class="nameForm" placeholder="please input your entry" />
</div>
</form>
Comments
In order to do this I use a combination of an abstract class that each of my form components extend. I then use a service and a canDeactivateFn guard on the route. When navigation begins the canDeactivateFn queries the form state via the service, which has been set by the abstract class.
It sounds complex but once setup each form component merely binds its FormGroup to the abstract class and calls super() in the constructor.
This approach assumes you have a single form open at any one time.
A simplified example is given below:
// abstract-form.compoent.ts
export abstract class AbstractFormComponent {
protected form!: FormGroup;
protected dialogService: DialogService = inject(DialogService);
protected formService = inject(FormService);
protected destroyRef = inject(DestroyRef);
constructor(){
this.init();
}
init(): void {
this.form.valueChanges
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe(() => {
this.formService.setDirty(this.form.dirty);
});
}
/**
* only show browser refresh warning if form has changes
*/
@HostListener('window:beforeunload', ['$event'])
beforeUnloadHander(): boolean {
if (this.formService._isDirty.value) {
return false;
} else {
return true;
}
}
}
// form.service.ts
export class FormService {
public _isDirty = new BehaviorSubject<boolean>(false);
public setDirty(isDirty: boolean) {
this._isDirty.next(isDirty);
}
}
// form-deactivate.guard.ts
export const formDeactivateGuard: CanDeactivateFn<any> = async () => {
const formService = inject(FormService);
const dialogService = inject(DialogService);
if (formService._isDirty.getValue()) {
return await dialogService
.openConfirmDialog({
title: 'form.unsaved-changes.title',
message: 'form.unsaved-changes.message',
confirmKey: 'common.leave',
cancelKey: 'common.cancel',
})
.then((result) => {
if (result) {
formService.setDirty(false);
return Promise.resolve(true);
} else {
return Promise.resolve(false);
}
});
} else {
return Promise.resolve(true);
}
};
// any-form.component.ts
export class PersonFormComponent
extends AbstractFormComponent
{
public personForm!: FormGroup<IPersonForm>;
constructor() {
super();
// bind the form to abstract class
this.form = this.personForm;
}
}