【问题标题】:Angular use modal dialog in canDeactivate Guard service for unsubmitted changes (Form dirty)Angular 在 canDeactivate Guard 服务中使用模态对话框进行未提交的更改(表单脏)
【发布时间】:2018-03-08 01:48:22
【问题描述】:

在我的 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

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

【问题讨论】:

  • 我使用了几种不同的模式并且总是发现最好的方法是使用带有组件的服务。很简单的东西。你不清楚哪一部分?
  • 关于canDeactivate根据用户的输入返回一个值
  • 所以canDeactivate的返回类型需要是Observable|Promise|boolean。所以,我会寻找一个结果返回其中一个的模式。看看这个...github.com/dougludlow/ng2-bs3-modal
  • @jbrown 模态需要 jQuery,我会避免包含...

标签: angular typescript ngx-bootstrap confirm-dialog angular-guards


【解决方案1】:

我使用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,
  ]
})

【讨论】:

  • 这个例子也适用于 Angular Material,遵循 Subject 实现!
  • 我很想知道为什么将 Observable 返回到 CanDeactivate() 方法有效?
  • @AndrewLobban canDeactivate 中的 UnsavedChangeGuardService 方法覆盖了 CanDeactivate 接口的方法,该方法的返回类型设置为 Observable|Promise|boolean,因此我们可以传递具有 Observable 的值canDeactivate 方法中的返回类型
  • @khush 感谢您的解释。我认为这比这要复杂得多。
  • @KushanRandima 发生这种情况是因为检查是 if (component.form.dirty),您可以更改它并在那里实现您自己的逻辑,以便比较旧表单值和新表单值。据我所知,Angular 中没有这样的内置功能。
【解决方案2】:

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

我通过订阅 bsModalService onHide 属性并将其与动作主题合并在一起实现了这一点:

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

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

【讨论】:

    【解决方案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;
        }
    

    【讨论】:

      【解决方案4】:

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

      这是我的StackBlitz

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

      【讨论】:

      • 感谢您对这个问题如此关注。我能够实现您在 StackBlitz 中提供的相同功能。但是,我不希望这样。如果您注意到,当显示对话框时,计数器会不断增加。当存在传统的 window.confirm 警报时它不会增加,并且在单击取消时它会从该点恢复。请看这个 - stackblitz.com/edit/angular-custom-confirmation-bx66hh 以了解我在说什么。我认为如果没有 window.confirm,这种行为是无法实现的。
      【解决方案5】:

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

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

      【讨论】:

        【解决方案6】:

        您可以将值传递给对话框的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;
          }
        }
        

        【讨论】:

          【解决方案7】:

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

          【讨论】:

            【解决方案8】:

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

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

            【讨论】:

              猜你喜欢
              • 2018-05-14
              • 1970-01-01
              • 2011-06-12
              • 2011-10-17
              • 2020-04-23
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2021-01-02
              相关资源
              最近更新 更多