3

I'm developing a simple app to practice with Angular and I'm hitting a wall regarding routing.

I'm not using standalone component.

I have a app.routing.module as follow:

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { MonthDetailComponent } from './containers/month-detail/month-detail.component';
import { BudgetEntryComponent } from './components/budget-entry/budget-entry.component';

const routes: Routes = [
  {
    path: 'month/:id',
    component: MonthDetailComponent
  },
  {
    path: 'entry/:id',
    component: BudgetEntryComponent
  },
  {
    path: '',
    pathMatch: 'full',
    redirectTo: 'month/1'
  }
];

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

Here is my app.module:

import { AppComponent } from './app.component';
import { MonthListComponent } from './containers/month-list/month-list.component';
import { MonthDetailComponent } from './containers/month-detail/month-detail.component';
import { BudgetEntryComponent } from './components/budget-entry/budget-entry.component';
import { MatTableModule } from '@angular/material/table';

@NgModule({
  declarations: [
    AppComponent,
    MonthListComponent,
    MonthDetailComponent,
    BudgetEntryComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    MatTableModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

I declared the router-outlet in the app.component.html:

<budget-month-list class="menu"></budget-month-list>

<router-outlet class="content"></router-outlet>

And here is how the routerlink is used:

<div>
    <ul>
        <ng-container *ngIf="months?.length; then items; else nothing"></ng-container>      
        <ng-template #items>
            <li *ngFor="let entry of months">
                <a
                    [routerLink]="['/month', entry.id]">{{entry.name}}</a>
            </li>
        </ng-template>
        <ng-template #nothing>
            <p>No month...</p>
        </ng-template>
    </ul>
</div>

When the app starts, I arrived on the correct default value ("month/1").But when I click on one of the <a> element, I do see the URL is changed but nothing happens. If I go manually in the URL and press enter, then the routing is fired and my page loads.

For example, the app starts and I'm at "/month/1" and see the informations regarding id 1. I click on the <a> element for the id 3. I see the URL turned to "/month/3" but nothing is loaded. If I go in the URL bar and hit enter, then I'll see the information for "/month/3".

I have googled and try multiple thing on my end, but nothing works. Any idea what I may be missing?

EDIT:

Here is the MonthDetailComponent:

import { Component, OnInit } from '@angular/core';
import { BudgetEntry } from '../../models/budget-entry.model';
import { ActivatedRoute } from '@angular/router';
import { MonthService } from '../../services/month.service';

@Component({
  selector: 'budget-month-detail',
  standalone: false,
  templateUrl: './month-detail.component.html',
  styleUrl: './month-detail.component.scss'
})
export class MonthDetailComponent implements OnInit {
  columnsToDisplay = ['date', 'amount', 'description']
  budgetEntries?: BudgetEntry[];

  constructor(private route: ActivatedRoute,
              private monthService: MonthService) {
    
  }

  ngOnInit(): void {
    const id = Number(this.route.snapshot.paramMap.get('id')!)

    this.budgetEntries = this.monthService.GetMonthEntries(id);
  }
}

When I click on an anchor tag, I don't reach the ngOnInit and nothing prints in the console.

3
  • The component that's reading the id route parameter needs to be set up to react to changes if route reuse is enabled. How are you reading the id route parameter and reacting to changes to it? Commented May 12 at 0:41
  • Can you share the code for MonthDetailComponent? We need to see how you are getting the route params. Commented May 12 at 1:40
  • Oh you are right! I forgot to include it! I edited the question @YongShun :) Commented May 12 at 23:49

2 Answers 2

2

From the ActivatedRouteSnapshot,

Contains the information about a route associated with a component loaded in an outlet at a particular moment in time.

Hence, as you mention, even though the parameter in the URL changes, the id is still remained, and you only get the latest id from the URL when the component is destroyed and re-created.

You should subscribe to the route.params for listening to URL (params) changes.

this.route.params.subscribe({
  next: (params) => {
    const id = Number(params['id']);

    this.budgetEntries = this.monthService.GetMonthEntries(id);      
  },
});

Demo @ StackBlitz

Sign up to request clarification or add additional context in comments.

Comments

0

Since you are using Angular 19, you can use withComponentInputBinding or bindToComponentInputs (for modular initialization) along with signals to create a reactive component with few lines of code.

@NgModule({
  imports: [RouterModule.forRoot(routes, { bindToComponentInputs: true })],
  exports: [RouterModule]
})
export class AppRoutingModule { }

Now your signal has access to the URL params.

So in your component you can create reactivity by using input signal to access the URL param.

@Component ({ ... })
export class MonthDetailComponent {
  id = input.required();

Now we can use this id input along with httpResource to fetch the data you need reactively.

Seamless data fetching with httpResource

@Component ({ ... })
export class MonthDetailComponent {
  id = input.required();
  
  monthData = httpResource(() => `https://some-api.com/path-to-month-api/${this.id()}`)

Then on the HTML access the data like below. We have signals provided by this resource, which you can use:

status -> this signal contains the status of the data fetch, it will give a number corresponding to ResourceStatus

error -> this signal contains the details of the error that occourred.

isLoading -> this signal contains a boolean related to the loading status of the resource.

value -> this signal contains the details of the actual data fetched.

So we can combine these signals and use them in HTML like:

@if(response.error()) {
  {{response.error() | json}}
} @else if (response.isLoading()) {
  Loading...
} @else {
  {{response.value() | json }}
}

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.