2

I'm fairly new to Angular 2+, so please bear with me if I'm way off. My goal is to have a shared notification service to be used throughout the application. For the most part, I got it working fine. It is a lazy module structure, so in order to not have issues with multiple instances (took me a while to figure this one out) I had to add a forRoot() method to my SharedModule class as such:

export class SharedModule {
 static forRoot(): ModuleWithProviders {
  return {
    ngModule: SharedModule,
    providers: [
      NotificationService,
      {
        provide: ErrorHandler,
        useClass: ErrorHandlerService
      }]
  };
}

}

Then, the SharedModule is added to the AppModule calling forRoot():

@NgModule({
 declarations: [...],
 imports: [
  ...
  SharedModule.forRoot()
  ...
 ],
 providers: [...],
 bootstrap: [...]
})
export class AppModule { }

I was happy to see that the notification service worked fine from my lazy modules by simply adding SharedModule to the lazy loaded module and then making notificationService calls from the components.

The problem is with the global error handler (provided in the forRoot() in SharedModule, see above), it looks like it might be creating a second instance of notification service and it doesn't work properly.

I guess first of all, am I structuring this correctly? If so, what am I doing wrong so that the notification service acts weird?

Here's the code for the error handler service:

     import { ErrorHandler, Injectable, Injector } from '@angular/core';
import { Router } from '@angular/router';
import { HttpErrorResponse } from '@angular/common/http';
import { NotificationService } from './notification.service';

@Injectable()
export class ErrorHandlerService implements ErrorHandler {
  constructor(private injector: Injector) { }

  handleError(error: Error) {
    const notificationService = this.injector.get(NotificationService);
    const router = this.injector.get(Router);
    if (error instanceof HttpErrorResponse) {
      if (!navigator.onLine) {

      } else {
        console.log('Http error!');
        notificationService.error('Error!', error.message);
      }
    } else {

    }
    console.error('It happens: ', error);
  }
}

Again, calling notification service from components works fine, the behavior is completely different when calling from the error handler service. The behavior I get is similar to the one I was getting from components BEFORE adding the forRoot() method to the SharedModule module, thus the theory of multiple instances.

Any help appreciated. Thanks

--- EDIT ---

I've created Stackblitz to show the different behavior when calling notification service from a component vs from the error handler service. The show notification button shows the notification as intended, but when making an error http call, notice that the first time you click, the notification doesn't show, and if keep playing with it, it will sometimes go away all of the sudden, or not go away at all, whereas you cannot make the same happen when using the show notification button.

Click here for Stackblitz

Naner
  • 1,292
  • 6
  • 25
  • 43
  • Do you *need* a shared module? Seems that this would be easier/simpler if you just built the shared services and injected them where you need them. – DeborahK Aug 01 '18 at 17:39

2 Answers2

1

After spending a few hours looking at this ... it appears that when you are throwing the http exception, that the UI is not picking up the changes.

You may have to ask another question of someone more familiar with change detection to determine exactly why ... but here is a work around to fix it.

In the notification component:

import { Component, OnInit, ChangeDetectorRef } from '@angular/core';

  constructor(private notificationService: NotificationService, private cd: ChangeDetectorRef) { }

  ngOnInit() {
    this.notificationService.getNotification().subscribe((notification: Notification) => {
      if (notification) {
        this.show = true;
        this.notification = notification;
        this.cd.detectChanges();        // <- Add this line
        // the rest of your code here
      }
    });
  }

Here is another post for more information and with some links to articles on change detection: How to trigger a change event manually - angular2

If you are just starting out, you could also consider using NgRx which helps you manage your state and notifications.

DeborahK
  • 57,520
  • 12
  • 104
  • 129
  • 1
    You're the best, that worked, thank you! Also, I've fixed the issue with the subscribe being called multiple times, it was a rookie mistake: accidentally used interval instead of timeout. Btw, I think I might have been to one of your talks at a conference a couple of years ago, AngularJS back then! Anyway, thanks again, this was driving me crazy :) – Naner Aug 02 '18 at 19:02
0

If you build a simple notification service using the providedIn property of the Injectable decorator, you will only get one instance. You don't need to create a sharedModule.

You could then inject the notification service into the ErrorHandler service.

@Injectable({
  providedIn: 'root'
})
export class ErrorHandlerService {
   // ...
   constructor(private notificationService: NotificationService) {}
}

But based on the code you posted, I assume I'm missing some key point as to the purpose of the added complexity of that code?

DeborahK
  • 57,520
  • 12
  • 104
  • 129
  • Thank you Deborah. Here's what I tried, I added the providedIn to the service and removed it completely from the SharedModule, including the NotificationComponent. I added both NotificationService and NotificationComponent directly to the AppModule, but unfortunately I'm still getting the same behavior :( – Naner Aug 01 '18 at 19:06
  • Can you build a stackblitz that demonstrates the issue so we can better help you? – DeborahK Aug 01 '18 at 19:07
  • Oh my, I can try. It's a large project , but yeah, that's a good idea, I'll try to have the basics and see if I can reproduce. If you or anyone else has any other ideas, by all means, please let me know. :) – Naner Aug 01 '18 at 19:12
  • We definitely don't want the large project ... just the two services (notification and error handling) along with a simple component to see them in action (and hopefully illustrate your issue). – DeborahK Aug 01 '18 at 19:14
  • Deborah, thank you again for the help. I've created a stackblitz to show the behavior I'm getting. I've updated the original question to include the link. Notice that clicking show notification one or many times works fine, but when clicking the error http call button, the notification acts strangely – Naner Aug 02 '18 at 12:53
  • If I add a console.log to the component's subscribe call ... it is displaying that log message many, many times. Do you have something else somewhere that is calling your notifications. (Regrettably, stackblitz doesn't have a search feature.) – DeborahK Aug 02 '18 at 18:21