私の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 を使用して、このシンプルなダイアログをより豪華なモーダルダイアログに置き換えたいと思います。
代わりにモーダルを使用して同じ結果を得るにはどうすればよいですか?
ngx-bootstrap Modals と RxJs 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">×</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,
]
})
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「はい」をクリックすると、確認モーダルに肯定的な結果が得られます)。 。
私はアシュウィンと行ったり来たりしていたので、Angular and Materialを使って自分が持っている解決策を投稿することにしました。
これが私の StackBlitz です
これは機能しますが、アプリケーションでの使用方法のように、非アクティブ化ページからの非同期応答の複雑さを追加したかったのです。これは少しプロセスですので、ご容赦ください。
これは、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;
}
}
私はこのソリューションを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;
}
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;
}