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