I am new to Angular so I wouldnt be surprised that I am missing basic things. I tried Angular docs and googling, but no clue so far of why my component just updates the UI after a click?
My scenario is, I want to have a shared component called NotificationComponent with a shared service called NotificationService. Since I can have more then 1 error happening, my NotificationComponent should display all of these messages . On top of that I have an http error interceptor and a custom error handling.
What is happening is, I get 3 https errors, interceptor is getting all 3, custom error handling is handling all 3, notification service is creating all 3 errors but notification component is rendering only 1 (sometimes 2) errors automatically. Once I click anywhere in the UI, the reminding msgs shows up. Why is that happening?
Hierarchy: AppModule
imports SharedModule
[contains notification service and component]. Notification component selector is inside app.component.html
Component:
@Component({
selector: 'app-notification',
templateUrl: './notification.component.html',
styleUrls: ['./notification.component.scss']
})
export class NotificationComponent implements OnInit, OnDestroy {
messages: Message[] = [];
notificationSubscription: Subscription;
constructor(private notificationService: NotificationService) { }
ngOnInit() {
this.notificationSubscription = this.notificationService.onNotify()
.subscribe(msg => {
this.messages.push(msg);
});
}
ngOnDestroy() {
// unsubscribe to avoid memory leaks
this.notificationSubscription.unsubscribe();
}
}
HTML:
<div *ngFor = "let msg of messages" role="alert">
{{msg.detail}}
</div>
Service:
@Injectable({
providedIn: 'root'
})
export class NotificationService {
messages: Message[];
private subject = new Subject<Message>();
constructor() {
this.messages = [];
}
onNotify(): Observable<Message> {
return this.subject.asObservable();
}
error(title: string, detail: string): void {
this.notify({type: 'error', title: title, detail: detail});
}
notify(message: Message) {
this.subject.next(message);
}
}
Custom Error Handler:
@Injectable()
export class CustomErrorHandler implements ErrorHandler {
constructor(@Inject(NotificationService)
private notificationService: NotificationService) {
}
handleError(error: any): void {
this.notificationService.error(error.title, error.detail);
}
}
PS: I am not restricted to any particular implementation, so I can change the approach if needed.
In your NotificationComponent
class, inject ChangeDetectorRef
in constructor
and explicitly update messages
array so angular can infer that property changed and therefore update UI
constructor(
private readonly changeDetector: ChangeDetectorRef,
private readonly notificationService: NotificationService
) {}
public ngOnInit(): void {
this.notificationSubscription = this.notificationService
.onNotify()
.subscribe({
next: (msg) => {
// Don't mutate existing array, Angular can't infer if it should update UI
this.messages = [...this.messages, msg];
// explicitly update UI if the above approach doesn't work
this.changeDetector.detectChanges();
}
});
}