Sending a parameter through a route to a child component as an input in Angular 2

My parent component receives an id value from a route parameter and I need to pass this value to a child component using the Input() decorator. The issue I'm facing is that I can't seem to get the route param value to be passed to the child component dynamically. When I hard-code a value and pass it, everything works fine which leads me to believe that the binding and child component logic are correct; the problem seems to lie in how or where I'm setting the value dynamically.

Below is the complete parent component with some explanatory comments:

import { Component }                                                from '@angular/core';
import { HTTP_PROVIDERS }                                           from '@angular/http';
import { provide }                                                  from '@angular/core';
import { Routes, Router, RouterUrlSerializer, ROUTER_DIRECTIVES }   from '@angular/router';
import { Location }                                                 from '@angular/common';
import { XHRBackend }                                               from '@angular/http';

import { ContentNode }                                              from './content-node';
import { ContentTreeComponent }                                     from './content-tree.component';
import { ContentDashboardComponent }                                from './content-dashboard.component';
import { ContentEditComponent }                                     from './content-edit.component';

import { ContentService }                                           from '../services/content.service';
import { InitService }                                              from '../services/init.service';
import { RouteNames }                                               from '../services/route-names.service';

@Component({
    selector: 'my-dashboard',
    template: `
        <div class="tree-panel-container">
            <div class="tree-panel-content">
                <content-tree [startNodeId]="startNodeIdContent" [currentNodeId]="currentNodeId"></content-tree>
            </div>
        </div>
        <router-outlet></router-outlet>
    `,
    directives: [
        ContentTreeComponent, 
        ContentDashboardComponent, 
        ROUTER_DIRECTIVES
    ],
    providers: [
        HTTP_PROVIDERS
    ]
})
@Routes([
    { path:'/',    component: ContentDashboardComponent },
    { path:'/:id', component: ContentEditComponent }
])
export class ContentComponent {

    _currentNodeId: number;

    constructor(private router:Router, private routeSerializer:RouterUrlSerializer, private location:Location) {
        router.changes.first().subscribe(() => {
            let urlTree = this.routeSerializer.parse(location.path());
            let urlSegment = urlTree.children(urlTree.children(urlTree.root)[0])[0];
            if(urlSegment != undefined){
                let id = urlSegment.segment;
                this._currentNodeId = id;
                console.log('_currentNodeId', this._currentNodeId); // This works - it logs the correct id from the route param
            }
        });
    }

    startNodeIdContent = InitService.startNodeIdContent;
    currentNodeId = this._currentNodeId; // This doesn't work - it just results in 'undefined' in the child component

    // The following line works; it passes 123 to the child component, so I know the binding and the child input is set up correctly:
    // currentNodeId = 123;
}

...and here's the child component:

import { Component, Input, OnInit }         from '@angular/core';
import { Router, RouteSegment, RouteTree }  from '@angular/router';

import { ContentNode }                      from './content-node';
import { ContentService }                   from '../services/content.service';


@Component({
    selector: 'content-tree',
    directives: [ContentTreeComponent],
    template: `
        <ol class="tree">
            <li *ngFor="let contentNode of contentNodes" class="tree__branch" [ngClass]="{'tree__branch--has-children': contentNode.HasChildren}">
                <a *ngIf="contentNode.HasChildren" (click)="contentNode.toggle=!contentNode.toggle" class="tree__branch__toggle">
                    {{ !!contentNode.toggle ? '-' : '+' }}
                </a> 
                <a class="tree__branch__link" (click)="onSelect(contentNode)">{{ contentNode.Name }}</a>
                <content-tree *ngIf="contentNode.toggle" [startNodeId]="contentNode.Id"></content-tree>
            </li>
        </ol>
        <div class="error" *ngIf="errorMessage">{{errorMessage}}</div>
        <p>{{test}}</p>
    `
})
export class ContentTreeComponent implements OnInit {

    constructor(
        private _contentService: ContentService,
        private _router: Router,
        private _currSegment: RouteSegment
    ) { }

    errorMessage: string;

    @Input('startNodeId')
    private _startNodeId: number;

    @Input('currentNodeId')
    private _currentNodeId: number;

    contentNodes: ContentNode[];

    ngOnInit() { 
        this.getContentNodes();

        console.log('_startNodeId = ' + this._startNodeId);
        console.log('_currentNodeId = ' + this._currentNodeId);
    }

    onSelect(contentNode: ContentNode) {
        this._router.navigate([`./${contentNode.Id}`], this._currSegment);
    }

    getContentNodes() {
        this._contentService.getContentNodes(this._startNodeId)
            .subscribe(
                contentNodes => this.contentNodes = contentNodes,
                error =>  this.errorMessage = <any>error
            );
    }
}

Answer №2

Encountering a similar issue when passing values to child components during routing, I discovered that the problem stemmed from the router adding ViewContainerRef.createComponent to these child components. As a result, both @Input and @Output were ineffective. To circumvent this, I created a SharedService within the parent component and then injected this service wherever it was required. Hopefully, this approach proves helpful for you as well.

Answer №3

A custom library has been developed, currently compatible only with Angular 9. There is a possibility to add support for older versions with additional code from your end.

npm install --save ngx-route-params-input

To see the library in action, visit: https://stackblitz.com/edit/angular-v8hdug?embed=1&file=src/app/user/user-routing.module.ts

The functionality of the library works as follows:

// import component (NgxRouteParamsInputModule must be provided 
//to angular module imports as well)
import { NgxRouteParamsInputComponent } from "ngx-route-params-input";

const routes: Routes = [
  {
    path: ":userId",
    // Change YourComponent to NgxRouteParamsInputComponent:
    component: NgxRouteParamsInputComponent,
    data: {
      // Provide YourComponent in route data
      component: YourComponent,
      // Specify parameters to pass from URL to component
      routeParams: {
        userId: "userId"
      },
      // Option to provide query params
      queryParams: {
        content: "content"
      }
    }
  }
];

If you encounter any issues or have feedback on this package, please feel free to leave comments/feature requests on GitHub (accessible through npm site along with documentation).

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

What is the best way to manage the 'content' attribute in TSX?

I'm currently developing an application that utilizes schema.org. In the code snippet below, you can see how I've implemented it: <span itemProp="priceCurrency" content="EUR">€</span> According to schema.org do ...

Unable to execute a join operation in TypeScript

I have an array of objects listed below var exampleArray = [{ "isAvailable": true, "receipent": [{ "id": "a6aedf0c34", "receipentName": "ABC" }, { "id": "a6aedbc34" ...

Leveraging several unique Angular custom builders

For a while now, I've utilized the ng-cli-hooks custom builder to incorporate CSP into the index.html file and create a unique webpack config. Currently, I'm exploring the features of @ngx-env/builder to enable environment variables injection du ...

The issue I encountered with Angular's Mat-Select component is that the openedChange

When a user opens the mat-select, I am attempting to set CSS style using custom code. However, I am encountering an undefined error. Upon checking in the console, I noticed that the panel value is returning undefined. Can someone please advise me on how to ...

Following the update, Angular no longer requires any node dependencies

Recently upgraded from Angular 5 to 9 and encountered an error in the browser's devtools: Uncaught ReferenceError: global is not defined After researching, I found a helpful post that discusses the issue: Upgrading to angular-6.x gives "Unca ...

Determine the maximum and minimum numbers by inputting a number and utilizing jQuery

<script type="text/javascript"> function findLargestNumber() { var number1, number2; number1 = Number(document.getElementById("N").value); number2 = Number(document.getElementById("M").value); if (number1 > numb ...

The functionality of 'ngbPopover' in Ng-bootstrap may be affected if the parent container contains a 'transform' css property

Struggling to implement Ng-bootstrap's 'ngbPopover' functionality, I encountered a frustrating issue where the popover would not display after clicking the button. After numerous hours of troubleshooting, I was relieved to discover the root ...

Using type values in TypeScript

I am trying to assign interfaces as values within a config object: export interface RouterConfig { startEvents?: typeof RouterEvent[]; completeEvents?: typeof RouterEvent[]; } The intended usage is as follows: private config: RouterConfig = { star ...

An in-depth guide on integrating lint-staged with jest and utilizing --collectCoverageFrom

I have incorporated lint-staged along with Jest testing framework to solely test the files that have been altered since the last commit, following the instructions outlined in this blog. Here is my current configuration: "src/**/*.{ts}": [ "prettier -- ...

Is it possible to assign an object literal to a typed variable in TypeScript? Can you also specify the typeof object literal?

Consider a scenario where you have the following type definition: type MyType = { A: number | string } If you try to assign a value like this, TypeScript will correctly flag it as an error: const myValue1: MyType = { A: 123, B: "Oh!", // This wil ...

Encountering the "Unrecognized teardown 1" error when subscribing to an Observable in Typescript and Angular2

Having trouble with using an Observable in my Angular2.rc.4 Typescript app. Check out the plunker for it here: https://embed.plnkr.co/UjcdCmN6hSkdKt27ezyI/ The issue revolves around a service that contains this code: private messageSender : Observable< ...

Guide on identifying modifications in the reactive form input utilizing valuechange in Angular 8

To track changes made in the quantity field of a reactive form, I am attempting to use valueChanges to detect them. However, I encountered an error stating: Cannot read property 'valueChanges' of undefined. ngOnInit() { this.cartItems = t ...

Allowing the OPTIONS method in CORS when sending a REST request from AJAX to a WCF Service

After spending 7 hours scratching my head, I am still unable to figure this out. Despite my extensive search on the web, no luck has come my way. My Angular App is sending requests to a WCF command-line hosted service application. To bypass CORS, I utilize ...

Is it possible for an Angular App to function as an authenticated user for a real-time database?

Just a question, no code included. I want to restrict access to reading from RTDB only to authenticated users. However, I don't want every user to have to sign up individually. Instead, I would like to have one login tied to the angular app that auto ...

Tips for customizing the matTreeNodePadding style in Material Design

In the world of Material Attributes, there exists a unique property known as matTreeNodePadding which helps in setting the padding-left for a specific DOM element: <div matTreeNodePadding></div> Upon rendering, the block transforms into: ...

Transmitting images from ASP.NET Web API to Angular client

My latest update involves implementing a photo upload feature. I've successfully managed to save the images in a folder after uploading and store their paths in the database. Now, my challenge is figuring out how to display these photos in my Angular ...

Discover the power of Angular 2 RC5 with WebPack and ngRx for seamless Hot

In the past, I successfully implemented HMR with RC4 by following the method detailed here to preserve ngRX state while using Webpack. However, with the introduction of RC5 and ngModules, the bootstrapping process has undergone changes that have left me u ...

Can you explain the distinction between any[] and [] in TypeScript?

Here is an example that successfully works: protected createGroups(sortedItems: Array<TbpeItem>): any[] { let groups: any[] = []; return groups; } However, the second example encounters a TypeScript error: type any[] not assignable to ...

Having trouble with the Bootstrap 4 toggle functionality while converting a Bootstrap theme to an Angular 9 project?

I am currently in the process of converting a Bootstrap theme to an Angular 9 project. I have successfully configured all the necessary JS and CSS files in the angular.json file, but unfortunately, the toggle function is not working. Please take a look at ...

simulating the emission of two observables occurring in succession

I have a component with two observable properties. @Component({ }) export class MyComponent { observable1$; observable1$; ngOnInit() { Observable1$ = this.service.getObservable1(); Observable2$ = this.service.getObservable2(); } } During ...