liberlux
liberlux

Reputation: 99

How can I resolve NullInjectionError and CircularInjectionError?

I recently created a service for my component using the Angular CLI:

import { Injectable } from '@angular/core';
import { RestDataSource } from "../models/rest.datasource/rest.datasource.model";
import { Task } from "../models/task/task.model";
import { Name } from "../models/name/name.model";
import { FibSeq } from "../models/fibseq/fibseq.model";
import { Category } from "../models/category/category.model";
import { Effort } from "../models/effort/effort.model";
import { Complexity } from "../models/complexity/complexity.model";
import { Tshirtsize } from "../models/tshirtsize/tshirtsize.model";
import { Epic } from "../models/epic/epic.model";
import { Estimate } from "../models/estimate/estimate.model";
import { Observable } from "rxjs";
import { TshirtEstimate } from "../models/tshirtestimate/tshirtestimate.model";

@Injectable({
  providedIn: 'root'
})
export class ToolboxRepositoryService {
  private tasks: Task[] = [];
  private names: Name[] = [];
  private fibseqvalues: FibSeq[] = [];
  private categories: Category[] = [];
  private effortlevels: Effort[] = [];
  private complexitylevels: Complexity[] = [];
  private tshirtSizes: Tshirtsize[] = [];
  private epics: Epic[] = [];


  constructor(private dataSource: RestDataSource) {
    dataSource.getTasks().subscribe(data => {
      this.tasks = data;
    });
    dataSource.getNames().subscribe(data => {
      this.names = data;
    });

    dataSource.getFibSeqValues().subscribe(data => {
        this.fibseqvalues = data;
    });

    dataSource.getCategories().subscribe(data => {
        this.categories = data;
    });

    dataSource.getEffortLevels().subscribe(data => {
        this.effortlevels = data;
    });

    dataSource.getComplexityLevels().subscribe(data => {
        this.complexitylevels = data;
    });

    dataSource.getEpics().subscribe(data => {
        this.epics = data;
    });

    dataSource.getTshirtSizes().subscribe(data => {
        this.tshirtSizes = data;
    });
  }

  getTasks(): Task[] {
    return this.tasks;
  }

  getNames(): Name[] {
      return this.names;
  }

  getFibSeqValues(): FibSeq[] {
      return this.fibseqvalues;
  }

  getCategories(): Category[] {
      return this.categories;
  }

  getEffortLevels(): Effort[] {
      return this.effortlevels;
  }

  getComplexityLevels(): Complexity[] {
      return this.complexitylevels;
  }

  getTshirtSizes(): Tshirtsize[] {
      return this.tshirtSizes;
  }

  getEpics(): Epic[] {
      return this.epics;
  }

  addTaskToList(task: Task) {
      let max = 0;

      this.tasks.forEach(task => {if (task.id > max) { max = task.id; }});

      this.tasks.push({id: max + 1,
                      category: task.category,
                      taskName: task.taskName,
                      speStoryNumber: task.speStoryNumber,
                      effort: task.effort,
                      complexity: task.complexity,
                      taskPointValue: task.taskPointValue,
                      notes: task.notes});
    }

  removeTaskFromList(taskId: number) {
      let index = this.tasks.findIndex(x => x.id === taskId);

      this.tasks.splice(index, 1);
  }

  clearTaskList() {
      this.tasks = [] as Task[];
  }

  filterTaskNamesByCategory(categoryName: string) {
      let objCategory = this.getCategories().find(c => c.category == categoryName);

      return this.getNames().filter(c => c.categoryId == objCategory.categoryId);
  }

  getCategoryNameById(categoryId: number) {
      return this.getCategories().find(c => c.categoryId == categoryId);
  }

  getTaskNameById(taskNameId: number) {
      return this.getNames().find(c => c.taskNameId == taskNameId);
  }

  getEffortById(effortLevelId: number) {
      return this.getEffortLevels().find(c => c.effortLevelId == effortLevelId);
  }

  getComplexityById(complexityLevelId: number) {
      return this.getComplexityLevels().find(c => c.complexityLevelId == complexityLevelId);
  }

  addEpicToList(epic: Epic) {
      let max = 0;

      this.epics.forEach(epic => {if (epic.id > max) { max = epic.id; }});

      this.epics.push({id: max + 1,
                      parentEpicNumber: epic.parentEpicNumber,
                      tseEpicNumber: epic.tseEpicNumber,
                      epicDescription: epic.epicDescription,
                      tshirtSize: epic.tshirtSize,
                      lowValue: epic.lowValue,
                      highValue: epic.highValue,
                      opportunity: epic.opportunity,
                      dependency: epic.dependency,
                      risk: epic.risk});
  }

  removeEpicFromList(epicId: number) {
      let index = this.epics.findIndex(x => x.id === epicId);

      this.epics.splice(index, 1);
  }

  getTshirtSizeById(tshirtLevelId: number) {
      return this.getTshirtSizes().find(c => c.tshirtLevelId == tshirtLevelId);
  }

  createStoryEstimate(storyEstimate: Estimate): Observable<any> {
      return this.dataSource.createStoryEstimate(storyEstimate);
  }

  createTask(taskRecord: Task): Observable<any> {
      return this.dataSource.createTask(taskRecord);
  }

  getStoryEstimate(storyNumber: string) {
      return this.dataSource.getStoryEstimate(storyNumber);
  }

  getStoryTasks(storyNumber: string) {
      return this.dataSource.getStoryTasks(storyNumber);
  }

  deleteStoryEstimateInfo(storyNumber: string) {
      return this.dataSource.deleteStoryEstimateInfo(storyNumber);
  }

  deleteStoryEstimateTasks(storyNumber: string) {
      return this.dataSource.deleteStoryEstimateTasks(storyNumber);
  }

  updateStoryEstimateInfo(estimateRecord: Estimate) {
      return this.dataSource.updateStoryEstimateInfo(estimateRecord);
  }

  getTshirtEstimate(epicNumber: string) {
      return this.dataSource.getTshirtEstimate(epicNumber);
  }

  createTshirtEstimate(tshirtEstimate: TshirtEstimate): Observable<any> {
      return this.dataSource.createTshirtEstimate(tshirtEstimate);
  }

  createEpic(epicRecord: Epic): Observable<any> {
      return this.dataSource.createEpic(epicRecord);
  }

  clearEpicList() {
      this.epics = [] as Epic[];
  }

  getTshirtEpics(epicNumber: string) {
      return this.dataSource.getTshirtEpics(epicNumber);
  }

  deleteEpicEstimateList(parentNumber: string) {
      return this.dataSource.deleteEpicEstimateList(parentNumber);
  }

  updateTshirtEstimateInfo(estimateRecord: TshirtEstimate) {
      return this.dataSource.updateTshirtEstimateInfo(estimateRecord);
  }
}

The rest.datasource.model is a class that performs API calls to a server.

import { Injectable } from "@angular/core";
import { catchError, mergeMap } from "rxjs/operators";
import { HttpClient, HttpHeaders, HttpErrorResponse } from "@angular/common/http";
import { from, Observable, throwError } from "rxjs";
import { map } from "rxjs/operators";

import { Task } from "../task/task.model";
import { Name } from "../name/name.model";
import { FibSeq } from "../fibseq/fibseq.model";
import { Category } from "../category/category.model";
import { Effort } from "../effort/effort.model";
import { Complexity } from "../complexity/complexity.model";
import { Tshirtsize } from "../tshirtsize/tshirtsize.model";
import { Epic } from "../epic/epic.model";
import { Estimate } from "../estimate/estimate.model";
import { TshirtEstimate } from "../tshirtestimate/tshirtestimate.model";

const PROTOCOL = "http";
const PORT = 3500;
const headers = { "content-type": "application/json" };

@Injectable()
export class RestDataSource {
    baseUrl: string;
    storyEstimate: any = [];
    taskList: any = [];

    constructor(private http: HttpClient) {
        //this.baseUrl = `${PROTOCOL}://${location.hostname}:${PORT}/`;
        this.baseUrl = "http://localhost:8080/";  // use this to connect to local rest services
        // this.baseUrl = "http://172.18.138.37:8080/";  // use this to connect to dev rest services
    }

    getTasks(): Observable<Task[]> {
      return this.http.get<Task[]>(this.baseUrl + "taskList");
    }

    getNames(): Observable<Name[]> {
      return this.http.get<Name[]>(this.baseUrl + "spetaskname/list");
    }

    getFibSeqValues(): Observable<FibSeq[]> {
      return this.http.get<FibSeq[]>(this.baseUrl + "spefibsequence/list");
    }

    getCategories(): Observable<Category[]> {
      return this.http.get<Category[]>(this.baseUrl + "specategory/list");
    }

    getEffortLevels(): Observable<Effort[]> {
      return this.http.get<Effort[]>(this.baseUrl + "speeffortlevel/list");
    }

    getComplexityLevels(): Observable<Complexity[]> {
      return this.http.get<Complexity[]>(this.baseUrl + "specomplexitylevel/list");
    }

    getTshirtSizes(): Observable<Tshirtsize[]> {
      return this.http.get<Tshirtsize[]>(this.baseUrl + "tsetshirtlevel/list");
    }

    getEpics(): Observable<Epic[]> {
      return this.http.get<Epic[]>(this.baseUrl + "epicList");
    }

    createStoryEstimate(storyEstimate: Estimate): Observable<any> {
      return from([this.createStoryEstimate(storyEstimate)]);
    }

    createTask(taskRecord: Task): Observable<any> {
      return from([this.createTask(taskRecord)]);
  }

    getStoryEstimate(storyNumber: string) {
      return this.http.get<Estimate>(this.baseUrl + "spestoryestimate/story/" + storyNumber)
        .pipe(catchError(this.handleError));
    }

    getStoryTasks(storyNumber: string) {
       return this.http.get<Task[]>(this.baseUrl + "spetaskrecord/story/" + storyNumber);
    }

    deleteStoryEstimateInfo(storyNumber: string) {
      return this.http.delete(this.baseUrl + "spestoryestimate/story/" + storyNumber)
        .subscribe(() => console.log("Story deleted"));
    }

    deleteStoryEstimateTasks(storyNumber: string) {
      return this.http.delete(this.baseUrl + "spetaskrecord/story/" + storyNumber)
        .subscribe(() => console.log("Story tasks deleted"));
    }

    updateStoryEstimateInfo(estimateRecord: Estimate) {
      let jsonEstimate = JSON.stringify(estimateRecord);

      return this.http.put(this.baseUrl + "spestoryestimate/story/" + estimateRecord.storyNumber, jsonEstimate, {"headers": headers})
        .subscribe(() => console.log("Story info updated"));
    }

    getTshirtEstimate(parentNumber: string) {
      return this.http.get<TshirtEstimate>(this.baseUrl + "tsetshirtestimate/epic/" + parentNumber)
        .pipe(catchError(this.handleError));
    }

  createTshirtEstimate(tshirtEstimate: TshirtEstimate): Observable<any> {
    return from([this.createTshirtEstimate(tshirtEstimate)]);
  }

  createEpic(epicRecord: Epic): Observable<any> {
    return from([this.createEpic(epicRecord)]);
  }

    getTshirtEpics(parentNumber: string) {
      return this.http.get<Epic[]>(this.baseUrl + "tseepicrecord/epic/" + parentNumber);
    }

    deleteEpicEstimateList(parentNumber: string) {
      return this.http.delete(this.baseUrl + "tseepicrecord/epic/" + parentNumber)
        .subscribe(() => console.log("Tshirt Estimate stories deleted"));
    }

    updateTshirtEstimateInfo(estimateRecord: TshirtEstimate) {
      let jsonEstimate = JSON.stringify(estimateRecord);

      return this.http.put(this.baseUrl + "tsetshirtestimate/epic/" + estimateRecord.parentEpicNumber, jsonEstimate, {"headers": headers})
        .subscribe(() => console.log("Tshirt Estimate info updated"));
    }

    private handleError(error: HttpErrorResponse): any {
      if (error.error instanceof ErrorEvent) {
        console.error('An error occurred:', error.error.message);
      } else {
        console.error(
          `Backend returned code ${error.status}, ` +
          `body was: ${error.error}`);
      }
      return throwError(
        'Something bad happened; please try again later.');
    }
}

I have added my service to the list of providers within app.module.ts:

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RouterModule, Routes } from '@angular/router';
import { FormsModule} from '@angular/forms'

import { AppComponent } from './app.component';
import { StoryPointEstimatorComponent } from './components/story-point-estimator/story-point-estimator.component';
import { TShirtEstimatorComponent } from './components/t-shirt-estimator/t-shirt-estimator.component';
import { HomeComponent } from './components/home/home.component';
import { SpInputComponent } from './components/story-point-estimator/sp-input/sp-input.component';
import { SpScaleComponent } from './components/story-point-estimator/sp-scale/sp-scale.component';
import { SpOutputComponent } from './components/story-point-estimator/sp-output/sp-output.component';
import { TsInputComponent } from './components/t-shirt-estimator/ts-input/ts-input.component';
import { TsOutputComponent } from './components/t-shirt-estimator/ts-output/ts-output.component';
import { TsScaleComponent } from './components/t-shirt-estimator/ts-scale/ts-scale.component';
import { HeaderComponent } from './components/header/header.component';
import { ToolboxRepositoryService } from './services/toolbox-repository.service';

import { HttpClientModule } from '@angular/common/http';
import { DatePipe } from '@angular/common';

const appRoutes: Routes  = [
  { path: 'home', component: HomeComponent  },
  { path: '', redirectTo: '/home', pathMatch: 'full'},
  { path: 'spe', component: StoryPointEstimatorComponent },
  { path: 'tse', component: TShirtEstimatorComponent }
];

@NgModule({
  declarations: [
    AppComponent,
    StoryPointEstimatorComponent,
    TShirtEstimatorComponent,
    HomeComponent,
    SpInputComponent,
    SpScaleComponent,
    SpOutputComponent,
    TsInputComponent,
    TsOutputComponent,
    TsScaleComponent,
    HeaderComponent
  ],
  imports: [
    BrowserModule,
    RouterModule.forRoot(appRoutes),
    FormsModule,
    HttpClientModule

  ],
  providers: [ ],
  bootstrap: [AppComponent]
})
export class AppModule { }

I want to inject this service into one of my components as below.

constructor(private repository: ToolboxRepositoryService ) {}

However, doing so only results in a NullInjectorError whenever I wish to navigate to this component.

Furthermore, if I navigate away from the component and try to access it again I get a Error stating Circular dependency in DI detected for ToolboxRepositoryService.

Here are my angular settings:

Angular CLI: 13.3.0       
Node: 14.15.4
Package Manager: npm 8.6.0
OS: win32 x64

Angular: 13.3.0
... animations, cli, common, compiler, compiler-cli, core, forms

Package                         Version
---------------------------------------------------------
@angular-devkit/architect       0.1303.0
@angular-devkit/build-angular   13.3.0
@angular-devkit/core            13.3.0
@angular-devkit/schematics      13.3.0
@schematics/angular             13.3.0
rxjs                            7.5.5
typescript                      4.6.2

Screenshots are below console errors

Upvotes: 0

Views: 78

Answers (1)

user17740968
user17740968

Reputation:

First of all, if you have this

@Injectable({
  providedIn: 'root'
})

You do not need to inject the service in the providers array of your module. This means the service is injected automatically into appModule.

Following that, please provide the exact error you're getting, or even better, a reproduction of the error on stackblitz, because it looks like it comes from a totally different issue (meaning, if your code is that simple, it should work).

Upvotes: 1

Related Questions