canDeactivate Guard服务中的Angular use modal对话框用于未提交的更改(表单脏)

canDeactivate Guard服务中的Angular use modal对话框用于未提交的更改(表单脏),angular,typescript,ngx-bootstrap,confirm-dialog,angular-guards,Angular,Typescript,Ngx Bootstrap,Confirm Dialog,Angular Guards,在Angular 4应用程序中,我有一些带有表单的组件,如下所示: export class MyComponent implements OnInit, FormComponent { form: FormGroup; ngOnInit() { this.form = new FormGroup({...}); } import { CanDeactivate } from '@angular/router'; import { FormGroup } from '@

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

export class MyComponent implements OnInit, FormComponent {

  form: FormGroup;

  ngOnInit() {
    this.form = new FormGroup({...});
  }
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;
  }
}
他们使用警卫服务来防止未提交的更改丢失,因此如果用户在请求确认之前试图更改路由:

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;
  }
}
从'@angular/router'导入{CanDeactivate};
从'@angular/forms'导入{FormGroup};
导出接口FormComponent{
表格:表格组;
}
导出类UnsavedChangesGuardService实现CanDeactivate{
canDeactivate(组件:FormComponent){
if(component.form.dirty){
返回确认(
“表单尚未提交,是否确实要离开页面?”
);
}
返回true;
}
}
这是使用一个简单的
confirm(…)
对话框,它工作正常

然而,我想用一个更奇特的模态对话框来代替这个简单的对话框,例如使用

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

我使用and解决了这个问题

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

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

这是我使用ngx引导对话框在离开某个路由之前获得确认对话框的实现。在服务的帮助下,我有一个名为“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;

  }
}
@Injectable()
导出类AddItemsAuthenticate实现CanDeactivate{
bsModalRef:bsModalRef;
构造函数(专用路由器:路由器、,
专用dataHelper:DataHelperService,
专用modalService:BsModalService){
}
canDeactivate(组件:AddUniformItemComponent,
路由:ActivatedRouteSnapshot,
状态:RouterStateSnapshot,
nextState?:路由器状态快照):布尔值{
if(this.dataHelper.canNavigate===false){
this.bsModalRef=this.modalService.show(ConfirmDialogComponent);
this.bsModalRef.content.title='放弃更改';
this.bsModalRef.content.description=`您有未保存的更改。是否要离开此页并放弃
您的更改或保留在此页面?`;
this.modalService.onHidden.subscribe(
结果=>{
试一试{
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]],
{
槲寄生
});
}
}捕获(例外){
//console.log(异常);
}
},error=>console.log(error));
}
返回this.dataHelper.canNavigate;
}
}

除了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
    );
  }
confirmModal(文本?:字符串):可观察{
常量主题=新主题();
const modal=this.modalService.show(ConfirmLeaveModalComponent);
modal.content.subject=主题;
modal.content.text=文本?文本:“确定吗?”;
const onHideObservable=this.modalService.onHide.map(()=>false);
返回合并(
subject.asObservable(),
非隐蔽可观测
);
}

在我的案例中,我将提到的
onHide
observable映射为false,因为在我的案例中解雇被视为中止(只有单击“是”将对我的确认模式产生积极结果)。

我使用“角度材质”对话框实现了此解决方案:

在ngx引导模式中,材质的模式具有“组件持续性”而不是“内容”:

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;
}
if(component.isDirty()){
常量主题=新主题();
const modal=this.dialog.open(ConfirmationDialogComponent{
panelClass:'my panel',宽度:'400px',高度:'400px',
});
modal.componentInstance.subject=主题;
返回subject.asObservable()
}
返回true;
}

由于我一直在与Ashwin进行交流,所以我决定发布我的解决方案,其中包括角度和材质

这是我的


这是可行的,但我想增加停用页面异步响应的复杂性,就像我在应用程序中使用异步响应一样。这是一个过程,请耐心听我说。

仅扩展mitschmidt提供的关于单击外部/退出按钮的附加信息,这个canDeactivate方法适用于Francesco Borzi的代码。我只是将subscribe to 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;
    }
canDeactivate(组件:FormComponent){
if(component.form.dirty){
常量主题=新主题();
const modal=this.modalService.show(confirmlavecomponent,{'class':'modal dialog primary'});
modal.content.subject=主题;
this.modalService.onHide.subscribe(hide=>{
主题。下一个(假);
返回subject.asObservable();
});
返回subject.asObservable();
}
返回true;
}

您可以在对话框的
关闭后将值传递给可观察到的

//modal.component.html
取消
离开
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;
}
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;
    }
// 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;
  }
}