'Angular queryParamMap is empty, then populated

I have created a new module in my app so I can separate parts that do not have to communicate and created an new.module.ts with its own routing module and components :

new-routing.module.ts:

const exportRoutes: Routes = [
  {
    path: 'export',
    component: ExportsComponent
  }
]

@NgModule({
  imports: [RouterModule.forRoot(exportRoutes)],
  exports: [RouterModule]
})
export class ExportRoutingModule {}

new.module.ts:

import { Router } from '@angular/router'
import { BrowserModule } from '@angular/platform-browser'

// Routing Module
import { NewRoutingModule } from './new-routing.module'

@NgModule({
  imports: [..., ExportRoutingModule, ...],
  declarations: [ExportsComponent],
  bootstrap: [ExportsComponent]
})

I have a simple index.html:

<body class="app">
  <router-outlet></router-outlet> // outlet is here because I read that I needed it to be able to use activated route, I actually just want the query params
</body>

and finally, where the problem lies, my component :

export class MyComponent implements OnInit {

constructor(private activatedRoute: ActivatedRoute) {}

ngOnInit() {this.activatedRoute.queryParamMap.subscribe(
(params: ParamMap) => console.log(params) 
// Do stuff with params => error)}

When I navigate to http://localhost:4200/export?firstParam=1.00,2.00,3.00 in my console, the params are logued twice, once empty, once populated as such :

ParamsAsMap {params: {…}}
keys:(...) // empty
params:{} // empty

core.js:3565 Angular is running in the development mode. Call enableProdMode() to enable the production mode.

ParamsAsMap {params: {…}}
keys:Array(3)
params:{firstParam: "1.00,2.00,3.00", secondParam: "bla"}

This cause my component to throw error since I need those params to display my component and the first time they are logued they are empty so :

  • Why are they loggued twice ?
  • Why is my code executed before my params observable has a value ?
  • Could I get rid of the router outlet (which I don't really need since I have no routing involved with this module, I just used it because I read that I couldn't use activatedRoute without it; I just want the query params from my url

Thanks for your help



Solution 1:[1]

I think the issue is that you are using the ExportsComponent for bootstrapping:

@NgModule({
  ...
  bootstrap: [ExportsComponent]
})

The component you specify here is used when the application starts, so it's ngOnInit is called early.

What you can do is to have a separate component for bootstrapping and separate ExportComponent, here is an example (see the live version):

import { bootstrap } from '@angular/platform-browser-dynamic';
import { RouterModule, Router, Routes, ActivatedRoute } from '@angular/router';
import { NgModule, Component, OnInit }       from '@angular/core';
import { BrowserModule }  from '@angular/platform-browser';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

@Component({
    selector: 'my-app',
    template: `
    <nav class="rbe-plunk-nav">
      <ul>
        <li> <a [routerLink]="['/']">Homepage</a> </li>
        <li> <a [routerLink]="['/about']">Export</a> </li>
      </ul>
    </nav>

    <main>
      <router-outlet></router-outlet>
      <!-- The router will put the content here, right after the outlet -->
    </main>
  `
})
export class AppComponent { }

@Component({
  selector: 'home',
  template: '<h1>Home: </h1>'
})
export class HomeComponent { }

@Component({
  selector: 'export',
  template: '<h1>Export: </h1>'
})
export class ExportComponent implements OnInit {

  constructor(private route: ActivatedRoute) {}

  ngOnInit() {
     this.route.queryParamMap.subscribe(paramMap => {
         console.log("Export: ");
         console.log(paramMap);
     });
  }
}

export const routes: Routes = [
  { path: '',         component: HomeComponent },
  { path: 'about',    component: ExportComponent }
];

@NgModule({
  imports: [
    BrowserModule,
    RouterModule.forRoot(
      routes,
      { /*enableTracing: true*/ }
    )
  ],
  declarations: [
    AppComponent,
    HomeComponent,
    ExportComponent
  ],
  bootstrap: [ AppComponent ]
})
export class AppModule {
  // Diagnostic only: inspect router configuration
  constructor(router: Router) {
    console.log('Routes: ', JSON.stringify(router.config, undefined, 2));
  }
}

platformBrowserDynamic().bootstrapModule(AppModule);

Solution 2:[2]

Url: http://localhost:4200/licenca-filtro?param=60&param2=50

component.ts

constructor(
    private route: ActivatedRoute,
  ) {

    const param1 = this.route.queryParams['value']['param'];
    const param2 = this.route.queryParams['value']['param2'];
    
    console.log(param1, param2);

   }

OUT: 60 50

Solution 3:[3]

The accepted answer by @Lakston does not take into account the flow when there's no query params and we need to detect it. After struggling a lot I found a real solution for those of us working with SSR (Server Side Rendering, meaning no browser global objects available for us) and in need of detecting the no query param scenario.

import { Location } from '@angular/common';
----------------------------------------------------------
constructor(private location: Location)
----------------------------------------------------------
let questionIndex = this.location.path().indexOf('?');
questionIndex = questionIndex === -1 ? Infinity : questionIndex;
const queryParams = (<any>Object).fromEntries(this.location.path().substr(questionIndex + 1).split('&').filter(str => str).map(el => el.split('=')));

All credits to YugasVasyl, who posted this answer on: https://github.com/angular/angular/issues/12157

I already test it on my project and it works! Blessings...

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Boris Serebrov
Solution 2 Joel Luis PInto
Solution 3 Ramiro G.M.