Created
December 31, 2017 03:36
-
-
Save kazuma1989/1e1f8182af19b7bd571ed593d6b65629 to your computer and use it in GitHub Desktop.
Angular の ErrorHandler からエラー表示コンポーネントを制御する
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Injectable, ErrorHandler, NgZone } from '@angular/core'; | |
import { MessageService } from '../../service/message/message.service'; | |
@Injectable() | |
export class ErrorHandlerService implements ErrorHandler { | |
constructor( | |
private message: MessageService, | |
private zone: NgZone, | |
) { } | |
handleError(error: any) { | |
console.error('ERROR', error); | |
// handleError() はボタン操作などの UI イベントとは関係なく呼ばれるため、コンポーネントの変化が検知できず、画面が変化しないことがある。 | |
// それを防止するため、NgZone#run() 内でサービスを呼び出している。 | |
this.zone.run(() => { | |
this.message.showError(error.message); | |
}); | |
} | |
} | |
export const ErrorHandlerServiceProvider = { | |
provide: ErrorHandler, | |
useClass: ErrorHandlerService | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Component, OnInit } from '@angular/core'; | |
import { MessageService, Message } from '../../service/message/message.service'; | |
import { ChangeDetectorRef } from '@angular/core'; | |
@Component({ | |
selector: 'app-message-box', | |
template: ` | |
<ng-container *ngFor="let message of messageList" [ngSwitch]="message.type"> | |
<div *ngSwitchCase="'info'" class="msg-box" (click)="message.remove()"> | |
{{message.content}} | |
</div> | |
<div *ngSwitchCase="'error'" class="msg-box--error" (click)="message.remove()"> | |
<i class="icon-error"></i> | |
{{message.content}} | |
</div> | |
</ng-container> | |
`, | |
styleUrls: ['./message-box.component.scss'] | |
}) | |
export class MessageBoxComponent implements OnInit { | |
messageList: any[]; | |
constructor( | |
private messenger: MessageService, | |
// private cd: ChangeDetectorRef | |
) { } | |
ngOnInit() { | |
this.messageList = []; | |
this.messenger.currentMessage.subscribe(message => { | |
this.add(message); | |
// ChangeDetectorRef#detectChanges() で変更をビューに伝えてもよい。 | |
// しかし、この呼び出しが必要なのは MessageService#showError() を ErrorHandler が呼んだ場合であって、 | |
// コンポーネントや Resolve#resolve() が呼んだ場合には必要ない。 | |
// 呼び出し元の ErrorHandler に責任があると考え、NgZone#run() で対応している。 | |
// this.cd.detectChanges(); | |
}); | |
} | |
private add(message: Message) { | |
const { type, content } = message; | |
const list = this.messageList; | |
// 新しいメッセージが上に表示されるように、push() ではなく unshift() を使う | |
this.messageList.unshift({ | |
type, | |
content, | |
remove() { | |
list.splice(list.indexOf(this), 1); | |
} | |
}); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Injectable } from '@angular/core'; | |
import { Subject } from 'rxjs/Subject'; | |
import { Observable } from 'rxjs/Observable'; | |
export interface Message { | |
type: 'info' | 'error'; | |
content: string; | |
} | |
@Injectable() | |
export class MessageService { | |
currentMessage: Observable<Message>; | |
private messenger: Subject<Message>; | |
constructor() { | |
// Subject を公開し、サービスを使う側に subscribe() させることもできるが、 | |
// Subject#next() を呼んで値を配信することもできてしまうので、Observable のみを公開プロパティとし、 | |
// そちらに subscribe() させる。 | |
this.messenger = new Subject<Message>(); | |
this.currentMessage = this.messenger.asObservable(); | |
} | |
showInfo(content: string) { | |
this.messenger.next({ | |
type: 'info', | |
content | |
}); | |
} | |
showError(content: string) { | |
this.messenger.next({ | |
type: 'error', | |
content | |
}); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Angular Guard の動作確認と、ローディング中表示の実装 では Resolve とコンポーネントを連携させた。
ErrorHandler とコンポーネントを連携させる際には、追加で NgZone#run() が必要だったので、備忘録として残す。
ErrorHandler は通常のサービスと異なり、コンポーネントに inject できない。
そのため MessageService を介してコンポーネントの表示を制御している。
ただし、handleError() の呼び出しはビュー変更イベントから漏れるらしく、明示的に NgZone#run() 内でサービスを呼び出さないと、コンポーネントが subscribe() 内でプロパティを変化させてもビューに反映されなかった。
コンポーネントの状態をビューに反映する方法として、もう一つ ChangeDetectorRef#detectChanges() を使う方法もあるが、今回の場合は呼び出し元の責任なので、NgZone を使うのが妥当だろう。