'@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:
- CustomerComponent (Parent)
- 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.
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 |

