10

My requirement is to load some data by calling Two Rest Api's before app component loads.If API gives any error display the message in Toaster (angular2-toaster).

Before loading app component the below AppLoadService executes

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import 'rxjs/add/operator/toPromise';
import { APP_SETTINGS } from 'app/app-settings/settings';

@Injectable()
export class AppLoadService {

constructor(private httpClient: HttpClient) { }

loadLabs(): Promise<any> {
    return new Promise((resolve, reject) => {
        this.httpClient.get(`/api/v1/api/lab`)
            .toPromise()
            .then((res: any) => {
                APP_SETTINGS.labs = res;
                resolve();
            })
            .catch((err: any) => {
                reject(err);
            });
    });
}
/////////////////******************////////////////////////////
getSettings(): Promise<any> {
    return new Promise((resolve, reject) => {
        this.httpClient.get(`assets/settings/config.json`)
            .toPromise()
            .then((config: any) => {
                APP_SETTINGS.loginURL = config["login"];
                console.log(`config.json loaded:: `, APP_SETTINGS);
                resolve();
            })
            .catch((err: any) => {
                reject(err);
            });
    });
 }

My App module file is like below

    export function createTranslateLoader(http: Http) {
        return new TranslateStaticLoader(http, './assets/i18n', '.json');
    }


    @NgModule({
    declarations: [
        AppComponent, CustomDateRangePickerComponent
    ],
    imports:  [
            // coreModules: //
            BrowserModule,
            BrowserAnimationsModule,
            ToasterModule,
            HttpClientModule,
            FormsModule,
            CommonModule, //<====added

            //thirdPartyModules:
            // ToastyModule,
            BootstrapModalModule,
            //appModules: //
            AppRoutingModule,
            FooterModule,
            ErrorModule,
            AccessDeniedModule,
            NotFoundModule,
            AppLoadModule, //Startupdata before APP loaded
            RouterModule.forRoot([]),
            TranslateModule.forRoot({ provide: TranslateLoader, useFactory: (createTranslateLoader), deps: [Http] })
    ],
    providers: [
        ToasterService,
        StartupService,
        ResponseStatusesService,
        LocalStorageService,
        ApplicationSettingsService,
        LabSelectionService,
        AccountService,
        AuthService,
        AlertService,
        AuthGuard,
        RolesGuard,
        FeaturebasedGuard,
        ErrorLogService,
        {
            provide: ErrorHandler,
            useClass: GlobalErrorsHandler
        },
        {
            provide: HTTP_INTERCEPTORS,
            useClass: AppHttpInterceptor,
            multi: true
        },
        {
            provide: LocationStrategy,
            useClass: HashLocationStrategy
        },
    ],
    exports: [TranslateModule],
    bootstrap: [AppComponent]
    })

    export class AppModule { }

GlobalErrorHandler.ts

@Injectable()
export class GlobalErrorsHandler extends ErrorHandler {
constructor(
    private injector: Injector,
    private errorLogService: ErrorLogService

) {
    super();
    alert('GlobalErrorsHandler');

}
handleError(error: Error | HttpErrorResponse) {
    debugger;
    let toaster = this.injector.get(ToasterService);
    toaster.pop("head", "body");
}
}

AppComponent.html

<toaster-container [toasterconfig]="ang2toasterconfig"></toaster-container>

<router-outlet></router-outlet>

Same issue with interceptors as well

            import { Injectable, Injector } from '@angular/core';
            import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest, HttpResponse, HttpErrorResponse } from '@angular/common/http';
            import { Observable } from 'rxjs/Observable';
            import 'rxjs/add/operator/do';
            import 'rxjs/add/operator/catch';
            import 'rxjs/add/observable/throw';
            import { ToasterService } from 'angular2-toaster';
            import { AuthService } from 'app/blocks/auth/auth.service';
            import { TranslateService } from 'ng2-translate';
            import { AlertService } from '../../core/services/common';


            @Injectable()
            export class AppHttpInterceptor implements HttpInterceptor {

                constructor(private injector: Injector) { }

                intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
                    debugger;
                    console.log(req);
                    if (!window.navigator.onLine) { // check to see if there's internet
                        // if there is no internet, throw a HttpErrorResponse error
                        // since an error is thrown, the function will terminate here
                        return Observable.throw(new HttpErrorResponse({ error: 'NO_INTERNET' }));
                    } else {
                        // else return the normal request
                        return this.handleResponse(next, req);
                    }


                }
                handleResponse(next, req) {
                    return next.handle(req)
                        .do((ev: HttpEvent<any>) => {
                            if (ev instanceof HttpResponse) {
                            }
                        })
                        .catch((response: any) => {
                            if (response instanceof HttpErrorResponse) {
                                console.log('response in the catch: ', response);
                                this.handleReponseExceptions(response);
                            }
                            return Observable.throw(response);
                        });
                }
                handleReponseExceptions(exception) {
                    let toaster = this.injector.get(ToasterService);
                    let translate = this.injector.get(TranslateService);

                    // TOASTER NOT WORKING AND TRANSLATE NOT WORKING
                    toaster.pop("test","test");

                    this.translate.get(["test", "test"]).subscribe(res => {
                        //NOT FETCHING FROM en.json
                    });

                }

            }

As per my knowledge, the toaster.pop('','') method is called before toaster-container loaded. How to solve this issue. Root component is the App component, where I placed the toaster container. Please suggest me the architecture to solve this issue.

One more, where I need to handle different errors... In Local Errorhandler (component level) or in Global error handler or in Interceptor?

Example errors: 400,404,500+ ...etc

Update1: As per the David comment changed code like below, but still No Container ..... message is coming in console and no toaster is visible

Using "angular2-toaster": "^6.1.0"

enter image description here

enter image description here

These are the API calls which will be fired before app component enter image description here

enter image description here

Tony
  • 141
  • 4
  • 21
  • Are you getting an error message in your console, "No Toaster Containers have been initialized to receive toasts" when you pop a toast? If not, the container is rendered, but the UI isn't picking up the zone change. – David L Jul 20 '18 at 21:44
  • Yes, I am getting the error message in console – Tony Jul 20 '18 at 22:31
  • So it seems like you want to pop a toast before your app has even rendered. How do you expect that to be possible? – David L Jul 21 '18 at 12:53
  • As per your view its not possible... One more question, When I call first API from APP, if authentication failed, then 401 will come as response. Now I want to handle this scenario, like, Display a toaster saying "authorization failure, Redirecting to login page". Where should I write the code , in my view i should write in globalErrHandler or Interceptor. But, in both cases above error message is thrown from toast service. Same error is coming in Interceptors also. – Tony Jul 21 '18 at 19:01

5 Answers5

10

This is documented in angular2-toaster's README due to the number of times this occurs and the confusion it causes.

This is caused by how Angular handles UI dispatching (or rather, the lack thereof) in error handlers.

The handleError function is executed outsize of an Angular zone. You need to explicitly tell Angular to run the pop call within the context of a zone.

export class AppErrorHandler implements ErrorHandler {
    constructor(
        private toasterService: ToasterService,
        private ngZone : NgZone) { }

    handleError(error: any): void {
        this.ngZone.run(() => {
            this.toasterService.pop('error', "Error", error);
        });  
    }
}
David L
  • 32,885
  • 8
  • 62
  • 93
  • 1
    This worked for me trying to handle http errors in an interceptor. Using MDBootstrap toast module the toast was coming up white, no text or formatting. I put it in a zone like the above example and it is showing perfectly. Thank you. – digthewells Sep 07 '18 at 18:43
  • This helped me to get the Toast component provided by PrimeNG to work within a custom error handler. Thanks! – Magnus Wallström Mar 06 '19 at 10:09
2

I added Java Script Toaster not angular and shown the Text.

Tony
  • 141
  • 4
  • 21
1

This happens because handleError() method is executed outside of the Angular zone. This causes toasts not to behave correctly since change detection doesn't run on it. Turn on onActivateTick in the error handler to ensure that the toast is running inside Angular's zone:

Working Example

@Injectable()
export class GlobalErrorHandler extends ErrorHandler {

    constructor(@Inject(Injector) private readonly injector: Injector) {}

    handleError(error) {
        console.log("Handling error: " + error);
        this.toastrService.error("testing", null, { onActivateTick: true })
    }

    /**
     * Need to get ToastrService from injector rather than constructor injection to avoid cyclic dependency error
     * @returns {} 
     */
    private get toastrService(): ToastrService {
        return this.injector.get(ToastrService);
    }

}

Git Issue

Romil Patel
  • 12,879
  • 7
  • 47
  • 76
0

Customer error handler will create before the tosterservice as the But while creating error handler the roster service is not available

To resolve the issue use Injector in constructor in custom error handler and in handleerror method get tosterservice using injector

0

This is example works in Angular 6

app.component.html

<toaster-container [toasterconfig]="toasterconfig"></toaster-container>
<router-outlet></router-outlet>

app.component.ts

import { ToasterConfig } from 'angular2-toaster';
import { CustomToasterService} from '../app/core/_services/custom-toaster.service';

@Component({
    selector: 'app-root',
    templateUrl: './app.component.html',
    styleUrls: ['./app.component.scss'],
    providers: [CustomToasterService],
})
export class AppComponent implements OnInit {
    toasterConfig: any;
    toasterconfig: ToasterConfig = new ToasterConfig({
        positionClass: 'toast-bottom-right',
        showCloseButton: true,
        tapToDismiss: false,
        timeout: 5000
    });
    constructor(public settings: SettingsService) {
    }

custom-toaster.service.ts

import { Injectable } from '@angular/core';
import { ToasterService } from 'angular2-toaster';

@Injectable()
export class TooasterService {
    constructor(
        private toasterService: ToasterService,
    ) { }


    public showError(error) {
        this.toasterService.pop('error', error.name, error.Message); 
    }
}
unknow27
  • 81
  • 1
  • 2