Nicolas Pierre
Nicolas Pierre

Reputation: 1195

Injected service undefined in function but not ngInit

I'm trying to inject a HttpService into my CoreModule. This works in the ngOnInit function but when I try to access the services in an other function the service becomes undefined.

Component

export class TreeComponent implements OnInit {
  constructor(private store: Store<AppState>,
    private cdRef: ChangeDetectorRef,
    private injector: Injector,
    private locationService: LocationService) { }

async ngOnInit() {
  await this.store.pipe(
  select('auth'),
  map(async (authState) => {
    if (authState.isAuthenticated) {
      console.log(this.locationService);
      this.data.push(await this.injector.get(LocationService).getBaseLocation().toPromise());
      this.cdRef.detectChanges();
      console.log(this.locationService);
    }
  })
  ).toPromise();
}
/* Ommited where not used */

public hasChildren(node: any): boolean {
  console.log(node);
  console.log(this.locationService); <-- undefined
  console.log(this.injector); <-- undefined
  //Check if the parent node has children.
  return true;
}

CoreModule

@NgModule({
  declarations: [ShellComponent, SidebarComponent, TreeComponent],
  imports: [
    CommonModule,
    RouterModule,
    SharedModule,
    TreeViewModule,
    FormsModule
  ],
  exports: [

  ],
  providers: [AuthService, Shell, LocalizationService, LocationService]
})
export class CoreModule {
  constructor(@Optional() @SkipSelf() parentModule: CoreModule) {
    if (parentModule) {
      throw new Error(`${parentModule} has already been loaded. Import Core module in the AppModule only.`);
    }
  }
}

App Module

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    HttpClientModule,
    BrowserModule,
    TranslateModule.forRoot({
      loader: {
        provide: TranslateLoader,
        useFactory: (createCustomTranslateLoader),
        deps: [HttpClient]
      },
      missingTranslationHandler: {
        provide: MissingTranslationHandler,
        useClass: CreateMissingTranslationHandler,
        deps: [HttpClient]
      },
      useDefaultLang: true
    }),
    StoreModule.forRoot(reducers, { metaReducers }),
    !environment.production ? StoreDevtoolsModule.instrument({
      maxAge: 25 //  Retains last 25 states
    }) : [],
    EffectsModule.forRoot([AuthEffects]),
    CoreModule,
    SharedModule,
    ProductionOverviewModule,
    AppRoutingModule,
    BrowserAnimationsModule
  ],
  providers: [
    {
      provide: APP_INITIALIZER,
      useFactory: (initI18n),
      deps: [
        HttpClient,
        TranslateService,
      ],
      multi: true
    },
    { provide: HTTP_INTERCEPTORS, useClass: JwtInterceptor, multi: true }
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

LocationService

const headers = new HttpHeaders({
 'Content-Type': 'application/json'
});

@Injectable()
export class LocationService {
public currentPlant: BehaviorSubject<Location>;

constructor(private http: HttpClient) {
 this.currentPlant = new BehaviorSubject<Location>(null);
}

getBaseLocation(): Observable<Location> {
 return this.http.get<Location>(environment.API_BASE_URI + '/Location/GetBaseLocation', { headers: headers })
  .pipe(map(location => {
    this.currentPlant.next(location);
    location.childeren = [];
    return location;
  }));
}

getChildLocations(locationId: string): Observable<Location[]> {
   return this.http.get<Location[]>(environment.API_BASE_URI + `/Location/GetLocationTree?upperLocationID=${locationId}`, { headers: headers })
  .pipe(map(location => {
    return location;
  }));;
}
}

I tried multiple things like importing the service in the app module, trying to assign the service to a variable,.. but it stays undefined. Hopefully someone can point me in the right direction.

Upvotes: 0

Views: 866

Answers (2)

Emanuele Ghetti
Emanuele Ghetti

Reputation: 194

This is because the "hasChildren" function gets called from kendo-treeview component, it is required you bind the current context to the function before passing it.

Changing

[hasChildren]="hasChildren"

To

[hasChildren]="hasChildren.bind(this)"

Will do the trick

Upvotes: 2

youri
youri

Reputation: 1160

Supposing the hasChildren function is called from the kendo-treeview component in your HTML, you should use the Function.prototype.bind function:

<kendo-treeview
  kendoTreeViewExpandable
  [nodes]="data"
  textField="text"
  [children]="fetchChildren"
  [hasChildren]="hasChildren.bind(this)">
                 ^^^^^^^^^^^^^^^^^^^^^^
</kendo-treeview>

What happens here is when hasChildren is being executed, the scope is different and so the this is not what you expected.

The bind function returns a new function that is bound to the this you defined.

Upvotes: 2

Related Questions