When incorporating a custom Http provider that redirects, the use of APP_INITIALIZER can result in the error message "Cannot instantiate cyclic dependency! ApplicationRef_"

Implementing a custom Http provider in Angular to manage API authentication errors has been successful. The CustomHttp is able to redirect users to the login page upon receiving a 401 status error from the API.

app.module.ts

export function loadCustomHttp(backend: XHRBackend, defaultOptions: AppRequestOptions,
  router: Router, dataHelper: DataHelperService) {
  return new CustomHttp(backend, defaultOptions, router, dataHelper);
}

@NgModule({
// declarations and imports...
providers: [
// some services ...
 {
      provide: Http,
      useFactory: loadCustomHttp,
      deps: [XHRBackend, RequestOptions, Router, DataHelperService] 
    }
});

custom-http.ts

import { Injectable } from '@angular/core';
import { Http, RequestOptions, RequestOptionsArgs, ConnectionBackend, Request, Response } from '@angular/http';
import { Router } from '@angular/router';
import { Observable } from 'rxjs/Observable';

import { DataHelperService } from '../helpers/data-helper.service';
import { AuthStorage } from '../services/auth/auth-storage';

import 'rxjs/add/observable/throw';
import 'rxjs/add/observable/empty';

@Injectable()
export class CustomHttp extends Http {
  constructor(backend: ConnectionBackend, defaultOptions: RequestOptions,
    private router: Router, private dataHelper: DataHelperService) {
    super(backend, defaultOptions);
  }

  // request methods...

  intercept(observable: Observable<Response>): Observable<Response> {
    return observable.catch((err, source) => {
      let token = AuthStorage.getToken();

      if (err.status === 401 && token && AuthStorage.isTokenExpired())    { 
        AuthStorage.clearAll();
        this.router.navigate(['auth/login']);
      }
      return Observable.throw(err);
    });
  }
}

Attempting to utilize the APP_INITIALIZER opaque token to initialize the app settings leads to an error.

app.module.ts

@NgModule({
// declarations and imports...
providers: [
// some services ...
    ConfigService,
    { 
      provide: APP_INITIALIZER, 
      useFactory: (config: ConfigService) => () => config.load(), 
      deps:[ConfigService, Http],
      multi: true
    }
});

config.service.ts

import { Injectable } from '@angular/core';
import { Http, Response } from '@angular/http';
import { AppSettings } from '../../environments/app-settings';
import { Observable } from 'rxjs/Observable';

import 'rxjs/add/operator/toPromise';

@Injectable()
export class ConfigService {

  public settings: AppSettings;

  constructor(private http: Http) { }

  load(): Promise<AppSettings> {
    let url = '/settings/';

    var observable= this.http.get(url)
            .map(res => res.json());

    observable.subscribe(config => this.settings = config);
    return observable.toPromise();
  }

}

An error is generated:

Uncaught Error: Provider parse errors:
Cannot instantiate cyclic dependency! ApplicationRef_: in NgModule AppModuleNgModuleProviderAnalyzer.parse @ provider_analyzer.js:291...  

The presence of the router dependency in the Http provider declaration seems to be causing the cyclic dependency error. Removing it resolves the issue but triggers the ConfigService.load() function to run twice.

Seeking insights on why the router dependency may lead to this error and how to prevent double execution of the ConfigService.load() function.

For reference, a repository demonstrating the error can be accessed here: https://github.com/haia212/AngularErrorTestProject

Answer №1

The issue at hand is that the Router has the ability to asynchronously load certain routes, requiring it to utilize the Http service. However, there exists a circular dependency between Router and Http, making it impossible for the Angular injector to instantiate either of these services.

I encountered a similar challenge, and one approach to resolving it involves injecting the Injector instead of directly accessing the service, retrieving the service subsequently.

Code:

@Injectable()
export class CustomHttp extends Http {
  constructor(backend: ConnectionBackend, defaultOptions: RequestOptions,
    private injector: Injector, private dataHelper: DataHelperService) {
    super(backend, defaultOptions);
  }

  public get router(): Router { //this creates router property on your service.
     return this.injector.get(Router);
  }
  ...

In essence, you do not need to rely on Router to obtain an instance of the Http service. The injection occurs when accessing the router property, specifically when directing user navigation. This property remains isolated from other sections of the code.

If this solution does not address the issue, you can implement a similar method for other injected services (excluding those related to calling super).

Answer №2

My solution was quite straightforward - I just took out the Router from the deps declarations :

{
      provide: Http,
      useFactory: loadCustomHttp,
      deps: [XHRBackend, RequestOptions, DataHelperService]
    }

Remarkably, everything else remained unchanged. It almost feels like a bit of magic, but it definitely gets the job done.

Answer №3

Here's a method that might be beneficial; I managed to tackle this by redefining the approach for the CustomHttp class using composition instead.

My implementation of CustomHttp looks something like this:

@Injectable()
export class CustomHttp {

    constructor(private http: Http) {}

By doing this, I no longer require Router or any other service to be injected into my custom Http service.

In the configuration loader (config.service.ts), I implemented the following changes:

import { Injectable } from '@angular/core';
import { Http, Response } from '@angular/http';
import { AppSettings } from '../../environments/app-settings';
import { Observable } from 'rxjs/Observable';

import 'rxjs/add/operator/toPromise';

@Injectable()
export class ConfigService {

  public settings:AppSettings;

  constructor() { }

  load(http: Http): Promise<AppSettings> {
    let url = '/settings/';

    var observable= http.get(url)
            .map(res => res.json());

    observable.subscribe(config => this.settings = config);
    return observable.toPromise();
  }

}

I eliminated the necessity to inject the dependency on the Http service and integrated it into the load(http: Http) method instead.

Within my app.module.ts, I include the following:

providers: [
    {
        provide: Http,
        useFactory: (backend, options) => new CustomHttp(new Http(backend, options)),
        deps: [XHRBackend, RequestOptions]
    },
    ConfigService,
    {
        provide: APP_INITIALIZER,
        useFactory: (config, http) => () => config.load(http),
        deps: [ConfigService, Http],
        multi: true
    },

This is the current setup I have in place for my app. It may not be a universal solution, but I hope it offers some guidance.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

The utilization of angular2-materialize is not possible due to an error message indicating that jQuery.easing is undefined

Greetings to all who are taking the time to read this. I am encountering an issue while trying to run ng serve. Here is the error message I am receiving: TypeError: jQuery.easing is undefined Here is a breakdown of what I have done so far: ng new X cd X ...

Unable to activate the 'resolve' or 'guard' functionalities on child routes within a parameterized route path in Angular 2

I have come across a similar issue to the one mentioned here, however, I haven't been able to find a solution for it especially when the URL is parameterized. There was also a related problem on GitHub which has been fixed but doesn't address my ...

Issue with rest operator behavior in TypeScript when targeting es2018

This specific code snippet functions properly in the TypeScript Playground... class Foo { constructor(...args: any[]) { } static make(...args: any[]): Foo { return new Foo(...args); } } Example However, when trying to incorpora ...

First, download a npm package and integrate it into TSX files

Hello all, I am currently working on my very first project using React, Typescript, and ASP.NET Core. As a beginner in this technology stack, I seek your patience and understanding as I encounter challenges along the way. Right now, I'm facing an issu ...

The Angular directive ng-if does not function properly when trying to evaluate if array[0] is equal to the string value 'Value'

In my code, I want to ensure that the icon is only visible if the value at array index 0 is equal to 'Value': HTML <ion-icon *ngIf="allFamily[0] === 'Value'" class="checkas" name="checkmark"></ion-icon> TS allFamily = [ ...

Bringing in libraries/modules in Angular

When working with Angular import { Observable } from "rxjs"; what is the exact purpose? From my understanding, it seems like it should reference a file named Observable.ts, but I cannot locate such a file within "node_modules/rxjs". This same question a ...

Electron does not have the capability to utilize Google's Speech to Text engine

I am trying to connect my microphone with the Google Speech to Text engine. I came across this page and copied the code into my renderer.ts file, uncommented the lines with const, but when I run it, I encounter an error at line 7 (const client = new speech ...

Using ngFor and ngModel: What is the best way to add values to the beginning of the array I am looping through?

I am facing an issue with my array of elements in which users can edit, add, and delete complete array elements. Everything works smoothly until I try to add a value to the beginning of the array using the unshift method. Below is a test that showcases th ...

Oops! We encountered a TypeError at line 57 in the RegisterPage.html file. It seems that there is an issue with reading the property 'valid' of an undefined

An issue is arising while attempting to access a registration page... The error occurs when trying to navigate from the login page to the registration page Error: RegisterPage.html:57 ERROR TypeError: Cannot read property 'valid' of undefined C ...

The dragToZoom property on Google line Chart is malfunctioning with version 45

When utilizing the current version of Google line charts, I have noticed that the drag to zoom property in options is not functioning properly. google.charts.load('current', { packages: ['corechart'] }) However, when I sw ...

Clicking on the button will retrieve all values instead of just the index

Recently, I started working with Angular and encountered an issue with button functionality in my table. The problem is that when I click a button on one row, all the buttons on the entire page disappear instead of just hiding the specific button clicked. ...

Ensure that the hashmap data type is correctly defined when passing it as an argument

import React from 'react'; import styled from 'styled-components'; const IconWrapper = styled.Text` font-family: MyFont; `; const glyphs = { 'logo': '\ue94e', 'minus': '\ue900', ...

The element 'loginToken' is not found within the type '{ loginToken: string; } | { error: Error; } | { username: string; password: string; }'

I'm currently working on creating a reducer using Typescript and Redux, but I keep running into this error: Property 'loginToken' is not recognized in type '{ loginToken: string; } | { error: Error; } | { username: string; password: str ...

Mastering the Art of Promises in RXJS Observables

After thoroughly researching SO, I stumbled upon numerous questions and answers similar to mine. However, I suspect that there might be gaps in my fundamental understanding of how to effectively work with this technology stack. Currently, I am deeply enga ...

What is the best way to ensure that TypeScript can work seamlessly with values that are subject to change

I am looking to create a scalable model for setting modals by using different body names. Currently, I have two types set: 'preference' and 'split'. The SetModal function takes a parameter of type body which should be one of these two v ...

Tips for importing a .geojson document in TypeScript using webpack?

I am trying to extract data from a .geojson file but faced some challenges while attempting two different methods: const geojson = require('../../assets/mygeojson.geojson'); The first method resulted in an error message stating: Module parse f ...

Using Cypress with Typescript: Extracting values from aliases in cy.origin

I'm faced with a unique challenge where I need to extract data from an external source and incorporate it into my base URL. How can I remove the aliases that are causing errors whenever I try to call them? https://i.sstatic.net/gBmBW.png Below is the ...

How can a custom event bus from a separate account be incorporated into an event rule located in a different account within the CDK framework?

In account A, I have set up an event rule. In account B, I have a custom event bus that needs to act as the target for the event rule in account A. I found a helpful guide on Stack Overflow, but it was specific to CloudFormation. I am providing another a ...

What is the best way to handle .jsx files in my library bundling process with Rollup?

Currently, I am in the process of developing a component library using storybook and rollup. Here is the configuration file rollup.config.mjs /* eslint-disable import/no-extraneous-dependencies */ import peerDepsExternal from 'rollup-plugin-peer-deps- ...

Node Express and Typescript app are failing to execute new endpoints

Hello, I'm currently diving into Node express and working on a simple CRUD app for pets. So far, I've successfully created 2 endpoints that are functioning properly. However, whenever I try to add a new endpoint, Postman fails to recognize it, g ...