1

Inside constructor of component A I use this code:

public visitors: any[] = [];
 this.eventsService.view(this.activatedRoute.snapshot.params['id']).subscribe(response => {
      this.event = response.data.event;
      this.visitors = response.data.event.visitors;
});

Component A template is:

<app-visitor-component [visitors]="visitors"></app-visitor-component>

Why I variable visitors in app-visitor-component component is empty, despite on data exists:

@Input() visitors: IVisitor[];
 constructor() {
     console.log(this.visitors); // returns []
 }
POV
  • 11,293
  • 34
  • 107
  • 201
  • 1
    Just use `*ngIf` to only render `app-visitor-component` only once `visitors` has data/length if you are needing it to be populated at the time of `ngOnInit()`. It is always coming out to `[]` in `contructor`/`ngOnInit()` because the asynchronous call to populate `visitors` only completes **after** `contructor`/`ngOnInit()` of the child component is executed. Otherwise there are other lifecycle methods like OnChanges, where you can capture updates to that @Input. Try even at minimum to visualize this put some console.log() statements inside the subscribe as well as in `contructor`/`ngOnInit`. – Alexander Staroselsky Mar 04 '19 at 19:40

2 Answers2

3

From the ComponentA template the visitor the easiest thing to do if you expect a value for the binding of visitor in child components is to add a *ngIf check.

<app-visitor-component *ngIf="visitors.length" [visitors]="visitors"></app-visitor-component>

That does a truthy check on the value of visitors.length which will evaluate to false until the service call returns a value.

Also I would recommend you move the service call to eventsService.view to the ngOnInit of ComponentA and out of the constructor. This also makes ComponentA easier to test.


Then in the child component for app-visitor

From a previous answer @Input() value is always undefined

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

Your code using OnInit

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

@Component({
    selector: 'app-visitor-component',
    templateUrl: ''
})
export class ComponentAppVisitor implements OnInit {

    @Input() visitors: IVisitor[];
    constructor() {
    }

    ngOnInit() {
        console.log(this.visitors);
    }
}
Igor
  • 60,821
  • 10
  • 100
  • 175
  • No, I tried ` ngOnInit() { console.log(this.visitors); } ` it is empty [] – POV Mar 04 '19 at 19:37
  • @OPV - but it isn't. You are writing your code in the `constructor`. The data will always be undefined because the binding has not yet occurred. – Igor Mar 04 '19 at 19:42
  • @OPV - the other reason that nothing is being passed is that there is no value until your service call returns with a value. See the updated template code, you can add an `*ngIf` check to ensure that the `app-visitor` component does not get created until after the parent component has a value. – Igor Mar 04 '19 at 19:45
  • `*ngIf="visitors"` is not enough, you would need to at minimum check length as they are defaulting to empty array `[]`. – Alexander Staroselsky Mar 04 '19 at 19:45
  • @AlexanderStaroselsky - you are right, I overlooked the initial value assignment. – Igor Mar 04 '19 at 19:46
  • I tried: `` does not work – POV Mar 04 '19 at 19:48
  • @OPV - see the update from 2 min. ago. That should be `*ngIf="visitors.length"` – Igor Mar 04 '19 at 19:48
  • Upper this line I have data `{{visitors | json }}`, but inside component it is always [] – POV Mar 04 '19 at 19:48
  • @Igor when data biding will be changing the code is not running since it's in ngOnInit not in ngOnchanges – Reza Mar 04 '19 at 20:03
1

Angular doesn't make the @Input data available as soon as the component is created. I.e. it's not available during the constructor().

If you want to key off when the @Input is actually sent (and re-sent) use NgOnChanges:

import { Component, Input, OnChanges } from @angular/core';
@Component({
    selector: 'app-foo',
})
export class FooComponent implements OnChanges {
    @Input() visitors: IVisitor[];
    ngOnChanges() {
        if (this.visitors) {
            console.log(this.visitors)
            // do something with this.visitors
        }
    }
}

Alternatively you can use getter/setters to trigger something when it's sent by Angular:

    private _visitors: IVisitor[];
    @Input() public set visitors(val: IVisitor[]) {
        this._visitors = val;
    }
    // Also, guarantee to always return an array
    public get visitors(): IVisitor[] {
        if (this._visitors) {
            return this._visitors;
        }
        return [];
    }
amphetamachine
  • 27,620
  • 12
  • 60
  • 72