18

Why is my service (this.loggerService) undefined in my DataHandlerService when it has been injected? I thought dependency injection took care of this. My loggerService works in other services. Please help me realize where I am going wrong. My DataHandlerService code below:

import { Injectable } from '@angular/core';
import { Http, Response } from '@angular/http';
import { Observable } from 'rxjs/Observable';
import { LoggerService } from './logger.service';

@Injectable()

export class DataHandlerService 
{
constructor(private loggerService: LoggerService)
{

}

extractData(res: Response)
{
    let body = res.json();
    return body || {};
}


handleHttpError(error: any)
{
    let errMsg = (error.message) ? error.message : error.status ? `${error.status} - ${error.statusText}` : 'Server error';

    if (errMsg && this.loggerService)  //Why is this.loggerService always undefined?
    {
        this.loggerService.error(errMsg);
    }

    return Observable.throw(errMsg);
}
}

My LoggerService code below:

import { Injectable } from '@angular/core';
import { Http, Headers, Response } from '@angular/http';
import { Observable } from 'rxjs/Observable';
import { ConfigurationService } from './configuration.service';

@Injectable()

export class LoggerService 
{
constructor(private http: Http, private configurationService: ConfigurationService)
{

}

public fatal(msg: string)
{
    if (msg)
    {
        this.log(msg, "Fatal");
    }
}

public debug(msg: string)
{
    if (msg)
    {
        this.log(msg, "Debug");
    }
}

public info(msg: string)
{
    if (msg)
    {
        this.log(msg, "Info");
    }
}

public warn(msg: string)
{
    if (msg)
    {
        this.log(msg, "Warn");
    }
}

public error(msg: string)
{
    if (msg)
    {
        this.log(msg, "Error");
    }
}

private log(msg: string, logLevel: string)
{
    if (msg && logLevel && this.configurationService && this.configurationService.coreSettings)
    {
        let headers = new Headers();
        headers.append('Content-Type', 'application/json');
        headers.append('Accept', 'application/json');

        let loggingInfo: LoggingInfo = { "message": msg, "logLevel": logLevel, "sourceName": "CoreIV", "logDirectory": this.configurationService.coreSettings.logDirectory };

        this.http.post(this.configurationService.coreSettings.logbookUrl + "/rest/LogMessage", { loggingInfo }, { headers: headers })
            .toPromise()
            .then(res => this.extractData(res))
            .catch(err => this.handleHttpError(err));
    }
}

private extractData(res: Response)
{
    let body = res.json();
    return body || {};
}

private handleHttpError(error: any)
{
    let errMsg = (error.message) ? error.message :
        error.status ? `${error.status} - ${error.statusText}` : 'Server error';
    return Observable.throw(errMsg);
}



}

export interface LoggingInfo
{
    message: string,
    logLevel: string,
    sourceName: string,
    logDirectory: string

}

My App.Module code below:

import { NgModule, APP_INITIALIZER, ErrorHandler } from '@angular/core';
import { LocationStrategy, HashLocationStrategy } from '@angular/common';
import { HttpModule, JsonpModule, Jsonp } from '@angular/http';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule, FormGroup, FormControl, ReactiveFormsModule }   from '@angular/forms';
import { routing, appRoutingProviders } from './app.routing';
import { AppConfig } from './app.config';

import { AppComponent } from './app.component';
import { HomeComponent } from './home/home.component';
import { AppErrorHandler, LOGGING_ERROR_HANDLER_OPTIONS,     LOGGING_ERROR_HANDLER_PROVIDERS } from './app.error-handler';

import { AppService } from './app.service';
import { ConfigurationService } from './shared/services/configuration.service';
import { DataHandlerService } from './shared/services/data-handler.service';
import { LoggerService } from './shared/services/logger.service';
import { AuthGuard } from './auth-guard.service';

export function init_app(appConfig: AppConfig, configurationService: ConfigurationService, loggerService: LoggerService)
{
// Do initiating of services that are required before app loads
// NOTE: this factory needs to return a function (that then returns a promise)

return appConfig.load()
    .then((res) =>        
    {
        configurationService.coreSettings = appConfig.config;
    })
    .catch((err) =>
    {
        loggerService.error(err);
    });
}

@NgModule({
imports: [
    BrowserModule,
    FormsModule,
    routing,
    HttpModule,
    JsonpModule
],
exports: [

],
declarations: [
    HomeComponent,
    AppComponent
],
providers: [
    HttpModule,
    ConfigurationService,
    LoggerService,
    { provide: LocationStrategy, useClass: HashLocationStrategy },
    LOGGING_ERROR_HANDLER_PROVIDERS,
    {
        provide: LOGGING_ERROR_HANDLER_OPTIONS,
        useValue: {
            rethrowError: false,
            unwrapError: true
        }
    },       
    appRoutingProviders,
    AuthGuard,    
    DataHandlerService,
    AppConfig,     
    {
        provide: APP_INITIALIZER,
        useFactory: init_app,
        deps: [AppConfig, ConfigurationService, LoggerService],
        multi: false
    }
    ],
    bootstrap: [AppComponent, appRoutingProviders]
   })


export class AppModule
{
constructor(private httpModule: HttpModule)
{

}
}
LanceM
  • 1,888
  • 4
  • 23
  • 41

3 Answers3

43

I found out the solution to this issue. The answer in the post angular 2 - Injected service in http error handler pointed me in the right direction. I was using the following:

        .map(this.dataHandler.extractData)
        .catch(this.dataHandler.handleHttpError);

but should use:

        .map(res => this.dataHandler.extractData(res))
        .catch(err => this.dataHandler.handleHttpError(err));

For some reason the lambdas are needed.

Community
  • 1
  • 1
LanceM
  • 1,888
  • 4
  • 23
  • 41
  • 8
    Saved me a lot of grief! – Bill Apr 22 '17 at 02:42
  • 2
    if you don't use the lambda, in ´extractData´ and ´handleHttpError´ the ´this´ context will be point to other places. In order to make it work, you can bind the current context: .map(this.dataHandler.extractData.bind(this).catch(this.dataHandler.handleHttpError.bind(this)); – Alejandro Silva Oct 25 '17 at 02:45
  • Still a solution in Angular 5. – Salil Junior May 09 '18 at 08:15
  • The first call disregards the class instance, whereas the second does not. It makes sense why this works the way it does. – Tony U. Feb 15 '20 at 21:19
5

Even though the answer is correct and accepted, wanted to write down the reason why this was failing and another possible solution.

Instead of writing

.map(this.dataHandler.extractData)
.catch(this.dataHandler.handleHttpError);

You can use

.map(this.dataHandler.extractData.bind(this))
.catch(this.dataHandler.handleHttpError.bind(this));

The main reason why that failed is due to this takes it's value based on execution context and if the service was called in a setTimeout or setInterval as in my case the value of this was undefined (due to strict mode - otherwise it would have been the window object). By using .bind you explicitly providing the value of this (component class object).

DrNio
  • 1,936
  • 1
  • 19
  • 25
3

This happens to me when I fail to import the service in the main app module and list it in the providers section

// in app.module.ts     
import { LoggerService } from './logger.service';
...
@NgModule({
...
  providers: [
    LoggerService,
Robert Tamlyn
  • 333
  • 3
  • 7