'@Input property is undefined in angular 12

The problem is simple to understand and tricky to find the solution. I've 2 components as below:

  1. CustomerComponent (Parent)
  2. InvoiceComponent (Child)

Now, I'm passing customer details as <admin-invoice-form [customer]="customer"></admin-invoice-form> from parent component to child component. But, when I see the input property of the child component in the constructor( ) and ngOnInit( ), the result is undefined.

Look at the code below to understand this better,

1. admin.customer.components.ts file

import { Component} from '@angular/core';
import { Customer } from 'src/app/model/customer';
import { CustomerService } from 'src/app/services/customer.service';

@Component({
  selector: 'app-admin-customers',
  templateUrl: './admin-customers.component.html',
  styleUrls: ['./admin-customers.component.css']
})
export class AdminCustomersComponent {
  customerArray: Customer[] = [];
  customer: Customer;

  constructor(private customerService: CustomerService) {
    this.customerService.getAll()
       .subscribe((customer: Customer[]) => {
           this.customerArray = customer;
      });
  }

  setCustomer(customer: Customer) {
    this.customer = customer;
  }
}

2. admin-customers.component.html file

<div class="container">
     <table class="table table-striped table-hover border" datatable [dtOptions]="dtOptions" [dtTrigger]="dtTrigger">
        <thead>
            <tr>
                <th class="text-center">First Name</th>
                <th class="text-center">Last Name</th>
                <th class="text-center">Mobile No</th>
                <th class="text-center">City</th>
                <th></th>
            </tr>
        </thead>

        <tbody>
             <tr *ngFor="let customer of customerArray">
                    <td class="text-center">{{ customer.firstName}} </td>
                    <td class="text-center">{{ customer.lastName }}</td>
                    <td class="text-center">{{ customer.mobileNo }}</td>
                    <td class="text-center">{{ customer.city }}</td>
                   
                    <td class="text-center">
                        <button type="button" class="btn btn-success" (click)="setCustomer(customer)" data-bs-toggle="modal" data-bs-target="#custInvoiceModal">
                            Generate Invoice
                        </button>
                    </td>
                </tr>          
        </tbody>
    </table>
    

    <!-- Invoice Modal: Starts -->
    <div id="printThis"> 
        <div class="modal fade" id="custInvoiceModal" tabindex="-1" aria-labelledby="custInvoiceLabel" aria-hidden="true">
            <!-- Child Component: Starts -->
            <admin-invoice-form [customer]="customer"></admin-invoice-form>
        </div>
    </div>
</div>

3. admin-invoice-form.component.ts file

import { Component, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core';
import { Customer } from 'src/app/model/customer';

@Component({
  selector: 'admin-invoice-form',
  templateUrl: './admin-invoice-form.component.html',
  styleUrls: ['./admin-invoice-form.component.css']
})
export class AdminInvoiceFormComponent implements OnInit, OnChanges{
  @Input('customer') customer: Customer; // this will be provided from it's consumer i.e. admin-customers.component.html
  
  
  constructor() { 
    console.log('inside constructor()');
    console.log(this.customer); // Output: undefined
  }
  
  ngOnInit(): void {
    console.log('inside ngOnInit()');
    console.log(this.customer); // Output: undefined   
  }
  
  ngOnChanges(changes: SimpleChanges): void {
    if(changes['customer']){
      console.log('inside ngOnChanges()');
      console.log(this.customer); // Output: undefined
    }
      
  }
  
}

This is how I'm getting an error after adding *ngIf="customer" on the child component.

child-component-error



Solution 1:[1]

i don't see where do you call setCustomer() ??

but anyway i think you can solve your problem like this: <admin-invoice-form *ngIf="customer" [customer]="customer"></admin-invoice-form>

so the component will be created only when customer is not null or undefined

Solution 2:[2]

The solution is to change the detection strategy to onpush in your child

@Component({
  selector: 'admin-invoice-form',
  templateUrl: './admin-invoice-form.component.html',
  styleUrls: ['./admin-invoice-form.component.css'],
  changeDetection: ChangeDetectionStrategy.OnPush
})

Solution 3:[3]

The below solution has solved my problem,

1. Instantiating customer to empty object inside the parent class before calling setCustomer(customer) method.

import { Component} from '@angular/core';
import { Customer } from 'src/app/model/customer';
import { CustomerService } from 'src/app/services/customer.service';

@Component({
  selector: 'app-admin-customers',
  templateUrl: './admin-customers.component.html',
  styleUrls: ['./admin-customers.component.css']
})
export class AdminCustomersComponent {
  customerArray: Customer[] = [];
  customer: Customer = new Customer(); // instantiate to empty object initially

  constructor(private customerService: CustomerService) {
    this.customerService.getAll()
       .subscribe((customer: Customer[]) => {
           this.customerArray = customer;
      });
  }

  setCustomer(customer: Customer) {
    this.customer = customer;
  }
}

2. Create ngOnChange() method and do the rest of the stuff with updated @Input('customer') customer;

import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';
import { Customer } from 'src/app/model/customer';
import { Invoice } from 'src/app/model/invoice';
import { Item } from 'src/app/model/item';

@Component({
  selector: 'admin-invoice-form',
  templateUrl: './admin-invoice-form.component.html',
  styleUrls: ['./admin-invoice-form.component.css']
})
export class AdminInvoiceFormComponent implements OnChanges{
  @Input('customer') customer: Customer; // this will be provided from it's conumer i.e. admin-customers.component.html
  invoice: Invoice; // for invoice details
  
  ngOnChanges(changes: SimpleChanges): void {
    if(changes['customer']){
      this.invoice = new Invoice(this.customer);
    }
  }
  
}

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 Zerotwelve
Solution 2 Souhail HARRATI
Solution 3 Rupesh Bharuka