web-dev-qa-db-fra.com

Angular Désactiver Guard pour les modifications non soumises (formulaire modifié)

Dans mon Angular 4, j'ai quelques composants avec une forme, comme ceci:

export class MyComponent implements OnInit, FormComponent {

  form: FormGroup;

  ngOnInit() {
    this.form = new FormGroup({...});
  }

ils utilisent un service Guard pour empêcher la perte de modifications non soumises. Par conséquent, si l'utilisateur tente de modifier l'itinéraire avant de demander une confirmation:

import { CanDeactivate } from '@angular/router';
import { FormGroup } from '@angular/forms';

export interface FormComponent {
  form: FormGroup;
}

export class UnsavedChangesGuardService implements CanDeactivate<FormComponent> {
  canDeactivate(component: FormComponent) {
    if (component.form.dirty) {
      return confirm(
        'The form has not been submitted yet, do you really want to leave page?'
      );
    }

    return true;
  }
}

Ceci utilise une simple boîte de dialogue confirm(...) et cela fonctionne très bien.

Cependant, je voudrais remplacer ce dialogue simple par un dialogue modal plus sophistiqué, par exemple en utilisant le ngx-bootstrap Modal .

Comment puis-je obtenir le même résultat en utilisant un modal à la place?

6
Francesco Borzi

Je l'ai résolu en utilisant ngx-bootstrap Modals et RxJs Subjects .

Tout d'abord, j'ai créé un composant modal:

import { Component } from '@angular/core';
import { Subject } from 'rxjs/Subject';
import { BsModalRef } from 'ngx-bootstrap';

@Component({
  selector: 'app-confirm-leave',
  templateUrl: './confirm-leave.component.html',
  styleUrls: ['./confirm-leave.component.scss']
})
export class ConfirmLeaveComponent {

  subject: Subject<boolean>;

  constructor(public bsModalRef: BsModalRef) { }

  action(value: boolean) {
    this.bsModalRef.hide();
    this.subject.next(value);
    this.subject.complete();
  }
}

voici le modèle:

<div class="modal-header modal-block-primary">
  <button type="button" class="close" (click)="bsModalRef.hide()">
    <span aria-hidden="true">&times;</span><span class="sr-only">Close</span>
  </button>
  <h4 class="modal-title">Are you sure?</h4>
</div>
<div class="modal-body clearfix">
  <div class="modal-icon">
    <i class="fa fa-question-circle"></i>
  </div>
  <div class="modal-text">
    <p>The form has not been submitted yet, do you really want to leave page?</p>
  </div>
</div>
<div class="modal-footer">
  <button class="btn btn-default" (click)="action(false)">No</button>
  <button class="btn btn-primary right" (click)="action(true)">Yes</button>
</div>

Puis j'ai modifié ma garde en utilisant un sujet, maintenant ça ressemble à ça:

import { CanDeactivate } from '@angular/router';
import { FormGroup } from '@angular/forms';
import { Injectable } from '@angular/core';
import { Subject } from 'rxjs/Subject';
import { BsModalService } from 'ngx-bootstrap';

import { ConfirmLeaveComponent } from '.....';

export interface FormComponent {
  form: FormGroup;
}

@Injectable()
export class UnsavedChangesGuardService implements CanDeactivate<FormComponent> {

  constructor(private modalService: BsModalService) {}

  canDeactivate(component: FormComponent) {
    if (component.form.dirty) {
      const subject = new Subject<boolean>();

      const modal = this.modalService.show(ConfirmLeaveComponent, {'class': 'modal-dialog-primary'});
      modal.content.subject = subject;

      return subject.asObservable();
    }

    return true;
  }
}

Dans le fichier app.module.ts, accédez à la section @NgModule et ajoutez le composant ConfirmLeaveComponent à entryComponents.

@NgModule({
  entryComponents: [
    ConfirmLeaveComponent,
  ]
})
14
Francesco Borzi

En plus de la bonne solution de ShinDarth, il semble utile de mentionner que vous devrez également couvrir le renvoi du modal, car la méthode action () risque de ne pas être déclenchée (par exemple, si vous autorisez le bouton échap ou cliquez en dehors du modal) . Dans ce cas, l'observable ne se termine jamais et votre application risque de rester bloquée si vous l'utilisez pour le routage.

J'ai atteint cet objectif en m'abonnant à la propriété bsModalService onHide et en fusionnant le sujet de l'action:

confirmModal(text?: string): Observable<boolean> {
    const subject = new Subject<boolean>();
    const modal = this.modalService.show(ConfirmLeaveModalComponent);
    modal.content.subject = subject;
    modal.content.text = text ? text : 'Are you sure?';
    const onHideObservable = this.modalService.onHide.map(() => false);
    return merge(
      subject.asObservable(),
      onHideObservable
    );
  }

Dans mon cas, je mappe la variable onHide mentionnée à fausse car un renvoi est considéré comme un avortement dans mon cas ( uniquement un clic sur «oui» produira un résultat positif pour mon modal de confirmation).

2
mitschmidt

Ceci est mon implémentation pour obtenir une boîte de dialogue de confirmation avant de quitter un certain itinéraire en utilisant la boîte de dialogue ngx-bootstrap. J'ai une variable globale appelée 'canNavigate' à l'aide d'un service. Cette variable contiendra une valeur booléenne si elle est vraie ou fausse pour voir si la navigation est possible. Cette valeur est initialement vraie, mais si je modifie mon composant, je le rendrai faux. Par conséquent, 'canNavigate' sera faux. Si c'est faux, j'ouvre la boîte de dialogue et si l'utilisateur ignore les modifications, il ira à la route souhaitée en prenant les queryParams également, sinon il ne routera pas.

@Injectable()
export class AddItemsAuthenticate implements CanDeactivate<AddUniformItemComponent> {

  bsModalRef: BsModalRef;
  constructor(private router: Router,
              private dataHelper: DataHelperService,
              private modalService: BsModalService) {
  }

  canDeactivate(component: AddUniformItemComponent,
                route: ActivatedRouteSnapshot,
                state: RouterStateSnapshot,
                nextState?: RouterStateSnapshot): boolean {
    if (this.dataHelper.canNavigate === false ) {
      this.bsModalRef = this.modalService.show(ConfirmDialogComponent);
      this.bsModalRef.content.title = 'Discard Changes';
      this.bsModalRef.content.description = `You have unsaved changes. Do you want to leave this page and discard
                                            your changes or stay on this page?`;

      this.modalService.onHidden.subscribe(
        result => {
          try {
            if (this.bsModalRef && this.bsModalRef.content.confirmation) {
              this.dataHelper.canNavigate = true;
              this.dataHelper.reset();;
              const queryParams = nextState.root.queryParams;
              this.router.navigate([nextState.url.split('?')[0]],
                {
                  queryParams
                });
            }
          }catch (exception) {
            // console.log(exception);
          }
        }, error => console.log(error));
    }

    return this.dataHelper.canNavigate;

  }
}
0
Dilshan Liyanage