85

I have created a user photo component which takes an @Input() value, this value is the userId. If the value is passed in then I retrieve information from Firebase linking to this userId, if it doesn't then I do something else.

My user-photo component

import { Component, OnInit, OnDestroy, Input } from '@angular/core';

@Component({
    selector: 'user-photos',
    templateUrl: './user-photos.component.html',
    styleUrls: ['./user-photos.component.css']
})

export class UserPhotoComponent implements OnInit {

    @Input() userId: string;

    constructor() { 

        console.log('userId is:',this.userId);

    }


    ngOnInit() {


    }

    ngOnDestroy() {

    }
}

As you can see I have declared the userId as @Input(), now on my edit-profile component I have the following:

<user-photos [userId]="TestingInput"></user-photos>

Now the User Photo component gets rendered as I see the h1 tag appearing, however the userId is always undefined ?

No errors are appearing in the developer console, so I'm not entirely sure what I've done wrong.

Code Ratchet
  • 5,758
  • 18
  • 77
  • 141

10 Answers10

188

It will be initialized in ngOnInit, not the constructor. (Please also review the Angular Life Cycle Hooks documentation.)

ngOnInit() {
  console.log('userId is:',this.userId);
}

Also if you want to pass a literal like a string and use brackets [] you have to pass it as a string by enclosing the value with single ticks.

<user-photos [userId]="'TestingInput'"></user-photos>

The reason for that is the containing expression is evaluated in the context of the containing component so without it it will try to retrieve and pass a property/field named TestingInput which will be undefined (unless you also happen have a field by that name).

Igor
  • 60,821
  • 10
  • 100
  • 175
  • 1
    Can confirm it works, perfect thanks for your help and the additional information. Can't accept answer at the moment have to wait three minutes. Will do it asap. – Code Ratchet Feb 08 '17 at 20:52
19

In my case i had to use *ngIf = "isLoaded" on the parent's template first.

On Parent Component

    <div *ngIf = "isLoaded">
       <app-child-component [dataToChild]="dataFromParent"> </app-child-component>
    </div>

On Child Component

      @Input() dataToChild: any;
        constructor() { }
        ngOnInit(): void {
             console.log(this.dataToChild);
         }
David Njuguna
  • 803
  • 7
  • 8
17

If your input comes delayed (e.g. a slow webservice), the value will at first be undefined until the response arrives from the webservice.

In order to debug this, you could use ngOnChanges() hook, which will fire every time, an input value changes:

ngOnChanges() {
  console.log('data', this.data);
}

which will output something like:

data undefined <- OnInit, value did not arrive yet

data here it is! <- value arrives delayed

Johannes Hinkov
  • 761
  • 8
  • 9
10

In my case, I was passing in undefined as an input and I was assuming that Angular will treat the case of undefined the same as the default scenario. This assumption was wrong even if it is not clearly outlined in Angular's Component Interaction documentation. Here is the use case scenario:

parent.component.html:

...
[mVal]="undefined"
...

child.component.ts:

...
@input('mVal')
mVal: boolean = true
...

The value of mVal will be undefined and not true as you might have expected.

Angular will make the value true (default case) only if it is not defined on the parent component; otherwise, it will pass-in the value as it is (even if it is undefined).

You can solve this by checking if the value is undefined on OnInit or even in the component's constructor.

Menelaos Kotsollaris
  • 5,776
  • 9
  • 54
  • 68
4

In case TestingInput is a variable and not value is better add *ngIf="TestingInput"

<user-photos [userId]="TestingInput" *ngIf="TestingInput "></user-photos>

if we are calling some api, the delay of the response may cause to pass an empty value

Ruslan López
  • 4,433
  • 2
  • 26
  • 37
Adel Balbisi
  • 106
  • 3
4

I spent a lot of time for this stupidest @Input mistake. It should be:

<user-photos
  [userId]="TestingInput">
</user-photos>

and not

<user-photos>
  [userId]="TestingInput"
</user-photos>

Maybe it will help if someone has made a syntax mistake like I did

SemperFi
  • 2,358
  • 6
  • 31
  • 51
2

Use single ticks if you want to use a hardcoded string as parameter

<app-mycomponent [inputProperty]="'test'"></app-mycomponent>
Enrico
  • 2,734
  • 1
  • 27
  • 40
1

Answering your question

@Input() value is always undefined

The reason you're getting undefined in ngOnInit is because at the point where the component is initialised you haven't actually passed in userId.

<user-photos [userId]="TestingInput"></user-photos>

In order to get @Input value in the OnInit() function you can do something like :

My user-photo component

import { Component, OnInit, OnDestroy, Input } from '@angular/core';

@Component({
    selector: 'user-photos',
    templateUrl: './user-photos.component.html',
    styleUrls: ['./user-photos.component.css']
})

export class UserPhotoComponent implements OnInit {

    @Input() userId: string;

    constructor() {  }

    ngOnInit() {
     setTimeout(() => {
         console.log('userId is:',this.userId);  // You will get the @Input value
     });
    }

    ngOnDestroy() {

    }
}
Vishal Hasnani
  • 720
  • 7
  • 21
1

any of these answers is not working for me, after some research I have found belove solution,

in HTML.

<app-opportunityTable [tableNAme]="'OPPORTUNITY'"></app-opportunityTable>

in child component

@Component( {
    selector: 'app-opportunityTable',
    templateUrl: './opportunityTable.component.html',
    styleUrls: ['./opportunityTable.component.css'],
    inputs: ['tableNAme']
} )

export class opportunityTable implements OnInit {
         ngOnInit() {
        console.log( "this is it" + this.tableNAme )
         }
}
Shafeeq Mohammed
  • 1,193
  • 17
  • 25
0

It can also be undefined if you have multiple inputs, in my version of Angular this is the case:

export class Foo {
    @Input() bar: string; // fine
    @Input() baz: string; // does not exist (console.log(this) does not show it)
}

Does not matter whether you pass it in as well. It seems that earlier versions of Angular do not allow this. If you need multiple inputs pass in one object with multiple fields.

Asad-ullah Khan
  • 1,573
  • 18
  • 22
  • This isn't accurate. You can add any number of inputs to a component, so something else is going on with your code if baz doesn't exist. – andyrue Dec 06 '22 at 01:36
  • As i stated in the answer, in my *specific version* of angular this did not work. I know it works on newer versions. See this: https://stackoverflow.com/a/42454816/4021308. I edited my answer to make it more clear – Asad-ullah Khan Dec 06 '22 at 23:59