3

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.

4
  • I updated the code. I believe my component doesnt have any decorator. The component is part of SharedModule, which is imported on AppModule. The selector is placed inside app.component.html Commented Nov 25, 2020 at 22:49
  • Try import ngZone in your service and modify notify(message: Message) { this.ngZone.run(() => { this.subject.next(message); }); }. Commented Nov 26, 2020 at 1:45
  • 1
    Hi I think you are encounter the same issue with this one stackoverflow.com/questions/37793276/… the root cause is that change detection does not go when error occurred Commented Nov 26, 2020 at 3:07
  • Yes, this guy had the same problem as I did. Since the accepted answer there is quite old, I believe @prahbh s approach uses a better class to inject so I will keep my question live due to an updated solution. Commented Nov 26, 2020 at 19:24

1 Answer 1

7

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();
      }
    });
  }
Sign up to request clarification or add additional context in comments.

1 Comment

I had to use the detectChanges() method, and it works! Thanks

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.