18

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

export class MyComponent implements OnInit, FormComponent {

  form: FormGroup;

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

他们使用 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;
  }
}

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

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

我怎样才能使用模态来实现相同的结果?

4

8 回答 8

31

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

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

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

这是模板:

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

然后我用一个主题修改了我的守卫,现在它看起来像这样:

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

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

@NgModule({
  entryComponents: [
    ConfirmLeaveComponent,
  ]
})
于 2017-09-27T14:53:05.160 回答
3

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

我通过订阅 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
    );
  }

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

于 2018-02-15T15:02:19.943 回答
3

只是扩展了 mitschmidt 提供的关于点击外部/退出按钮的附加信息,这个 canDeactivate 方法适用于 Francesco Borzi 的代码。我只是在函数中将订阅添加到 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;
    }
于 2019-08-06T23:05:43.713 回答
2

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

这是我的StackBlitz

这可行,但我想从停用页面添加异步响应的复杂性,就像我在应用程序中的方式一样。这是一个过程,所以请多多包涵。

于 2019-07-17T20:44:32.823 回答
1

这是我在使用 ngx-bootstrap 对话框离开某个路线之前获得确认对话框的实现。在服务的帮助下,我有一个名为“canNavigate”的全局变量。这个变量将保存一个布尔值,如果它是真或假,看看是否可以导航。该值最初为 true,但如果我对组件进行更改,我会将其设为 false,因此“canNavigate”将为 false。如果它是假的,我将打开对话框,如果用户放弃更改,它将通过 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;

  }
}
于 2018-01-25T04:53:33.840 回答
1

您可以将值传递给afterClosed对话框的 Observable:

// modal.component.html
<mat-dialog-actions>
  <button mat-button mat-dialog-close>Cancel</button>
  <button mat-button [mat-dialog-close]="true">Leave</button>
</mat-dialog-actions>
// unsaved-changes.service.ts
@Injectable({ providedIn: 'root' })
export class UnsavedChangesGuardService
  implements CanDeactivate<FormComponent> {
  constructor(private _dialog: MatDialog) {}

  canDeactivate(component: FormComponent) {
    if (component.form.dirty) {
      const dialogRef = this._dialog.open(UnsavedChangesDialogComponent);
      return dialogRef.afterClosed();
    }

    return true;
  }
}
于 2020-12-03T15:04:22.803 回答
0

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

Material 的模态在 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;
}
于 2019-07-15T06:52:44.177 回答
0

这是一个没有主题的工作解决方案,您可以添加一个布尔属性确认来区分用户单击了取消或确认

import { Component, OnInit } from '@angular/core';
import { BsModalRef } from 'ngx-bootstrap/modal';

@Component({
  selector: 'app-leave-form-confirmation',
  templateUrl: './leave-form-confirmation.component.html',
  styleUrls: ['./leave-form-confirmation.component.scss']
})
export class LeaveFormConfirmationComponent implements OnInit {
  confirmed = false;
  constructor(public bsModalRef: BsModalRef) { }

  ngOnInit(): void {
  }
  confirm = () => {
    this.confirmed= true;
    this.bsModalRef.hide()
  }
}

这是html

<div class="modal-header">
  <h4 class="modal-title pull-left">Confirmation</h4>
</div>
<div class="modal-body">
  <h2>Data will be lost, Are you sure to leave the form?</h2>
</div>-*
<div class="modal-footer">
  <button type="button" class="btn btn-default" (click)="confirm()">confirm</button>
  <button type="button" class="btn btn-default" (click)="bsModalRef.hide()">cancel</button>
</div>

这是你的 canDeactivate 方法

 canDeactivate(
    component: DataStatus,
    currentRoute: ActivatedRouteSnapshot,
    currentState: RouterStateSnapshot,
    nextState?: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
    if(!component.isDataSaved()) {
      const modalRef = this.modalService.show(LeaveFormConfirmationComponent,  {
        backdrop: false,
        ignoreBackdropClick: true
      })
      return modalRef.onHidden.pipe(map(_ => modalRef.content.confirmed));
    }
    return of(true);
  }
于 2021-07-18T22:16:29.810 回答