Why Angular component is just updating after click?

2.7k Views Asked by At

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.

1

There are 1 best solutions below

1
On BEST ANSWER

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