Creating custom Router class in Angular leads to exception

59 views Asked by At

I want to override the function navigate(commands: any[], extras?: NavigationExtras): Promise<boolean> of Router to always set queryParamsHandling to true in the NavigationExtras.

Therefore I implemented following class:

@Injectable({
    providedIn: 'root'
})
export class MmosRouter extends Router {

    constructor(
        rootComponentType: Type<any>,
        urlSerializer: UrlSerializer,
        contexts: ChildrenOutletContexts,
        location: Location_2,
        injector: Injector,
        compiler: Compiler,
        @Inject(ROUTES) config: Route[][]
    ) {
        super(rootComponentType, urlSerializer, contexts, location, injector, compiler, flatten(config));
    }


    navigate(commands: any[], extras?: NavigationExtras): Promise<boolean> {
        extras = { ...extras, queryParamsHandling: 'preserve' };
        return super.navigate(commands, extras);
    }
}

Because I want to inject the class through a components constructor I have to add the class to providers in app.module.ts.

@NgModule({
  declarations: [
    ...
  ],
  imports: [
    ...
  ],
  providers: [
    ...
  {
    provide: Router,
    useFactory: (rootComponentType: Type<any>, urlSerializer: UrlSerializer, contexts: ChildrenOutletContexts, location: Location_2, injector: Injector, config: Route[][]) => {
      return new MmosRouter(rootComponentType, urlSerializer, contexts, location, injector, config);
    },
    deps: [ApplicationRef, UrlSerializer, ChildrenOutletContexts, Location_2, Injector, Compiler, ROUTES]
  },
    ...
  ],
  bootstrap: [...],
  exports: [
    ...
  ],
})
export class AppModule {
  constructor(service: ApmService) {}
}

For my information, that should be everything to get this to work.

However, whenever I start the application, I get following NullInjectionError:

core.mjs:9171 ERROR NullInjectorError: R3InjectorError(AppModule)[MmosRouter -> Function -> Function -> Function]: 
NullInjectorError: No provider for Function!
  at NullInjector.get (core.mjs:8191:27)
  at R3Injector.get (core.mjs:8618:33)
  at R3Injector.get (core.mjs:8618:33)
  at R3Injector.get (core.mjs:8618:33)
  at injectInjectorOnly (core.mjs:4782:33)
  at Module.ɵɵinject (core.mjs:4786:12)
  at Object.MmosRouter_Factory [as factory] (mmosRouter.ts:17:24)
  at R3Injector.hydrate (core.mjs:8719:35)
  at R3Injector.get (core.mjs:8607:33)
  at ChainedInjector.get (core.mjs:13811:36)

(line 17, mentioned in the error, is the class signature of MmosRouting)

3

There are 3 answers

0
basti394 On BEST ANSWER

The problem was Type<any> in the constructor of MmosRouter.

Because Type is a FunctionConstructor and that interface has some properties with type Function, I think angular had problems to provide them when I wanted to inject my custom router.

So the Solution is removing the parameter rootComponentType: Type<any> from the constructor and pass AppComponent into the super(...) in the body. And then you also don't need them in the providers in app.module.ts.

So consider changing:

Custom Router:

@Injectable({
  providedIn: 'root'
})
export class MmosRouter extends Router {

  constructor(
    //remove rootComponentType: Type<any>
    ...
  ) {
    super(AppComponent, ...);
  }


  navigate(commands: any[], extras?: NavigationExtras): Promise<boolean> {
    ...
  }
}
2
Andrei On

it seems you are trying to inject MmosRouter somewhere, and there is nothing that is provided by that token, and angular tries to resolve tokens from constructor parameters.

try

{
    provide: MmosRouter,
    useFactory: (rootComponentType: Type<any>, urlSerializer: UrlSerializer, contexts: ChildrenOutletContexts, location: Location_2, injector: Injector, config: Route[][]) => {
      return new MmosRouter(rootComponentType, urlSerializer, contexts, location, injector, config);
    },
    deps: [ApplicationRef, UrlSerializer, ChildrenOutletContexts, Location_2, Injector, Compiler, ROUTES]
  },
  {
    provide: Router,
    useExisting: MmosRouter
   }

this way your app would know how to construct MmosRouter and will provide same instance in place of Router

0
Naren Murali On

There is no need for the below code block!

So app.module.ts will not contain the providers with the useFactory object!

@NgModule({
  declarations: [
    ...
  ],
  imports: [
    ...
  ],
  providers: [
    ...
  ],
  bootstrap: [...],
  exports: [
    ...
  ],
})
export class AppModule {
  constructor(service: ApmService) {}
}

You can simplify you service to the below code, where you import router and call the router method, just with the extra params, this will make your code less complex and more maintainable!

@Injectable({
    providedIn: 'root'
})
export class MmosRouter {

    constructor(private router: Router) { }


    navigate(commands: any[], extras?: NavigationExtras): Promise<boolean> {
        extras = { ...extras, queryParamsHandling: 'preserve' };
        return this.navigate(commands, extras);
    }
}

The above code will do the same thing!