0

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

1
  • Did you try using the function "canDeactivateFn" as replacement of candeactivate ? Is available from v17 of angular. So only declare once and go on. Commented Oct 24, 2024 at 18:18

2 Answers 2

0

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>

Sign up to request clarification or add additional context in comments.

Comments

0

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;
    }
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.