web-dev-qa-db-ja.com

Angular送信されていない変更(フォームダーティ)に対してcanDeactivate Guardサービスのモーダルダイアログを使用する)

私の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 を使用して、このシンプルなダイアログをより豪華なモーダルダイアログに置き換えたいと思います。

代わりにモーダルを使用して同じ結果を得るにはどうすればよいですか?

12
Francesco Borzi

ngx-bootstrap ModalsRxJs Subject を使用して解決しました。

まず、モーダルコンポーネントを作成しました。

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,
  ]
})
14
Francesco Borzi

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にマッピングします。私の場合、却下は中止と見なされるためです(only「はい」をクリックすると、確認モーダルに肯定的な結果が得られます)。 。

2
mitschmidt

私はアシュウィンと行ったり来たりしていたので、Angular and Materialを使って自分が持っている解決策を投稿することにしました。

これが私の StackBlitz です

これは機能しますが、アプリケーションでの使用方法のように、非アクティブ化ページからの非同期応答の複雑さを追加したかったのです。これは少しプロセスですので、ご容赦ください。

1
Andrew Lobban

これは、ngx-bootstrapダイアログボックスを使用して特定のルートを離れる前に確認ダイアログを取得するための私の実装です。サービスを利用して 'canNavigate'というグローバル変数を持っています。この変数は、ナビゲーションが可能かどうかを確認するためにtrueまたはfalseの場合、ブール値を保持します。この値は最初はtrueですが、コンポーネントで変更を行う場合はfalseにするため、「canNavigate」はfalseになります。 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;

  }
}
1

私はこのソリューションをAngular Material Dialogで実装しました:

マテリアルのモーダルには、ngx-bootstrapモーダルの「コンテンツ」ではなく「componentInstance」があります。

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;
}
0
Or Zohar

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;
    }
0
Cameron Forward