typescript canDeactivate Guard 服务中的 Angular 使用模式对话框未提交的更改(表单脏)

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/46433195/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-21 04:55:50  来源:igfitidea点击:

Angular use modal dialog in canDeactivate Guard service for unsubmitted changes (Form dirty)

angulartypescriptngx-bootstrapconfirm-dialogangular-guards

提问by Francesco Borzi

In my Angular 4 application I have some components with a form, like this:

在我的 Angular 4 应用程序中,我有一些带有表单的组件,如下所示:

export class MyComponent implements OnInit, FormComponent {

  form: FormGroup;

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

they use a Guard service to prevent unsubmitted changes to get lost, so if the user tries to change route before it will ask for a confirmation:

他们使用 Guard 服务来防止未提交的更改丢失,因此如果用户在请求确认之前尝试更改路由:

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

This is using a simple confirm(...)dialog and it works just fine.

这是使用一个简单的confirm(...)对话框,它工作得很好。

However I would like to replace this simple dialog with a more fancy modal dialog, for example using the ngx-bootstrap Modal.

但是,我想用更奇特的模态对话框替换这个简单的对话框,例如使用ngx-bootstrap Modal

How can I achieve the same result using a modal instead?

如何使用模态来实现相同的结果?

回答by Francesco Borzi

I solved it using ngx-bootstrap Modalsand RxJs Subjects.

我使用ngx-bootstrap ModalsRxJs Subjects解决了它。

First of all I created a Modal Component:

首先我创建了一个模态组件:

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

here's the template:

这是模板:

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

Then I modified my guard using a Subject, now it look like this:

然后我使用 Subject 修改了我的守卫,现在它看起来像这样:

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

In app.module.ts file go to the @NgModule section and add the ConfirmLeaveComponent component to entryComponents.

在 app.module.ts 文件中,转到 @NgModule 部分并将 ConfirmLeaveComponent 组件添加到 entryComponents。

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

回答by mitschmidt

In addition to ShinDarth's good solution, it seems worth mentioning that you will have to cover a dismissal of the modal as well, because the action() method might not be fired (e.g. if you allow the esc button or click outside of the modal). In that case the observable never completes and your app might get stuck if you use it for routing.

除了 ShinDarth 的好解决方案之外,似乎值得一提的是,您还必须涵盖对模态的解除,因为可能不会触发 action() 方法(例如,如果您允许 esc 按钮或在模态外单击) . 在这种情况下,observable 永远不会完成,如果您将其用于路由,您的应用程序可能会卡住。

I achieved that by subscribing to the bsModalService onHideproperty and merging this and the action subject together:

我通过订阅 bsModalServiceonHide属性并将它和动作主题合并在一起来实现这一点:

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

In my case I map the mentioned onHideobservable to false because a dismissal is considered an abort in my case (onlya 'yes' click will yield a positive outcome for my confirmation modal).

在我的情况下,我将提到的onHideobservable映射到 false 因为在我的情况下解雇被认为是中止(只有“是”点击才会为我的确认模式产生积极的结果)。

回答by Dilshan Liyanage

This is my implementation to get a confirmation dialog before leaving a certain route using ngx-bootstrap dialog box. I am having a global variable called 'canNavigate' with the help of a service. This variable will hold a Boolean value if it is true or false to see if navigation is possible. This value is initially true but if I do a change in my component I will make it false therefore 'canNavigate' will be false. If it is false I will open the dialog box and if the user discards the changes it will go to the desired route by taking the queryParams as well, else it will not route.

这是我在使用 ngx-bootstrap 对话框离开特定路线之前获得确认对话框的实现。在服务的帮助下,我有一个名为“canNavigate”的全局变量。这个变量将保存一个布尔值,如果它是真或假,看看是否可以导航。该值最初为真,但如果我对组件进行更改,我会将其设为假,因此“canNavigate”将为假。如果它是假的,我将打开对话框,如果用户放弃更改,它也会通过使用 queryParams 转到所需的路线,否则不会路由。

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

  }
}

回答by Andrew Lobban

Since I have been going back and forth with a Ashwin, I decided to post my solution that i have with Angular and Material.

由于我一直在使用 Ashwin,因此我决定发布我使用 Angular 和 Material 的解决方案。

Here is my StackBlitz

这是我的StackBlitz

This works, but I wanted add the complexity of an asynchronous response from the Deactivating page like how I have it in my application. This is bit of a process so bear with me please.

这有效,但我想从停用页面添加异步响应的复杂性,就像我在我的应用程序中拥有它一样。这是一个过程,所以请耐心等待。

回答by Cameron Forward

Just expanding on the additional info provided by mitschmidt regarding click outside / escape button, this canDeactivate method works with Francesco Borzi's code. I just add the subscribe to onHide() inline in the function:

只是扩展了 mitschmidt 提供的关于点击外部/退出按钮的附加信息,这个 canDeactivate 方法适用于 Francesco Borzi 的代码。我只是在函数中内联添加 subscribe 到 onHide() :

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;

            this.modalService.onHide.subscribe(hide => {
                subject.next(false);
                return subject.asObservable();
            });

            return subject.asObservable();
        }

        return true;
    }

回答by Or Zohar

I implemented this solution with Angular Material Dialog:

我用 Angular Material Dialog 实现了这个解决方案:

Material's modal has "componentInstance" instead of "content" in ngx-bootstrap Modals:

材料的模态在 ngx-bootstrap 模态中有“componentInstance”而不是“content”:

if (component.isDirty()) {
  const subject = new Subject<boolean>();
  const modal = this.dialog.open(ConfirmationDialogComponent, {
    panelClass: 'my-panel', width: '400px', height: '400px',
  });

  modal.componentInstance.subject = subject;
  return subject.asObservable()
}
  return true;
}