0

First, I need to send some json data from parent to child component, but I load the data from http request in ngOnInit event and when I submit the data in normal way, the data is not sent to my child component, here is my first try :

data: TransSalesOrder = new TransSalesOrder();

   ngOnInit(): void {

    if (this.dataId > 0) {
      const data: SelectItem = new SelectItem();
      data.id = this.dataId;
      const sb = this.transSalesOrderService.view(data).subscribe(response => {
        this.transSalesOrderForm.patchValue(response);
        this.data = response;

        if (this.transSalesOrderForm.get('isDeleted').value) {
          this.editButton = false;
          this.deleteButton = false;
          this.isDeleted = true;
        }
      }, err => {
        this.openSnackBar(err);
      });
      this.subscriptions.push(sb);
    }

}

Then in my parent html, I sent the data like this :

<app-trans-sales-order-detail-list [salesOrderDetailsInput]="data.transSalesOrderDetail"></app-trans-sales-order-detail-list>

Then when I console.log in the child component.ts :

@Input() salesOrderDetailsInput: any;

  constructor() { }

  ngOnInit(): void {
    console.log(this.salesOrderDetailsInput);
  }

It's print out 'undefined'

Then I think the parent is too late to load the data, and here is my second try, I need ngOnChanges event :

  ngOnChanges(changes: SimpleChanges) {
    console.log(changes.salesOrderDetailsInput.currentValue);
  }

Then I can get my data that sent from parent, but I read somewhere in stackoverflow, some said there is async function needed to add from the parent value and the @Input in child component should retrieve as Observable, then I assume if I can do this, I will no longer need ngOnChanges, here is how I try :

In my parent code :

<app-trans-sales-order-detail-list [salesOrderDetailsInput]="data.transSalesOrderDetail | async"></app-trans-sales-order-detail-list>

But it give me error ERROR Error: InvalidPipeArgument: '' for pipe 'AsyncPipe' when compile and error Argument type TransSalesOrderDetail[] is not assignable to parameter type Observable<unknown> | Promise<unknown> in my Intellij Idea ide

Here is my child component.ts :

@Input() salesOrderDetailsInput: Observable<any>;

What did I miss here?

Charlie
  • 22,886
  • 11
  • 59
  • 90
Ke Vin
  • 3,478
  • 11
  • 60
  • 91
  • Is there a type for `transSalesOrderDetail`? You should use this as type for the child component's input - something like this: `@Input() salesOrderDetailsInput: TransSalesOrderDetail;` – Tino Jun 21 '21 at 06:34

4 Answers4

1

The member variable this.data is assigned a value asynchronously and holds an empty object by the time the binding [salesOrderDetailsInput]="data.transSalesOrderDetail" is executed. So it sends the current state of transSalesOrderDetail which is undefined.

IMO there is no need for async use here. You could try the following

Option 1: *ngIf

Conditionally render the child component tag using *ngIf

<app-trans-sales-order-detail-list 
  *ngIf="!!data && !!data.transSalesOrderDetail"
  [salesOrderDetailsInput]="data.transSalesOrderDetail"
></app-trans-sales-order-detail-list>

Here the child component will not be rendered when the transSalesOrderDetail property isn't defined. See here for more info on double bang !! operator.

Option 2: safe navigation operator ?.

Use safe navigation operator ?. when accessing properties. It'll check if the property is defined in an object before trying to access it.

<app-trans-sales-order-detail-list 
  [salesOrderDetailsInput]="data?.transSalesOrderDetail"
></app-trans-sales-order-detail-list>

Here the child component might be rendered but without binding to transSalesOrderDetail.


Having said that, I could provide some additional suggestions

When using ngOnChanges hook to read the changes to the @Input properties, it is better to check if the property is defined before trying to access it. It might not be an issue in a component with a single @Input property. But when there are multiple @Inputs, it might happen some are defined and some aren't.

@Input() salesOrderDetailsInput: any;

ngOnChanges(changes: SimpleChanges) {
  if (!!changes && 
      !!changes.salesOrderDetailsInput && 
      !!changes.salesOrderDetailsInput.currentValue
  ) {
    console.log(changes.salesOrderDetailsInput.currentValue);
  }
}
ruth
  • 29,535
  • 4
  • 30
  • 57
0

The async pipe actually works like a subscription made to an observable when you call an API or something to get data asynchronously. When you use an async pipe the left side of | should be an observable. Example:

parent.component.html

<ChildComponent [inputData] = "_data | async"></ChildComponent>

parent.component.ts

public data = new BehaviorSubject(null);
public _data = this.selectedSubmission.asObservable();


ngOnInit(): {
  this.http.get(url).subscribe((respData:any)=>{
    this.data.next(respData);
  })
}

child.component.ts

@Input() inputData: any;

ngOnChanges(changes: SimpleChanges) {
  console.log(this.inputData);
}

Note: You should use a service file to write all the API calls. The above example demonstrates how it should be done.

Live Demo

Anglesvar
  • 1,132
  • 8
  • 15
0

I think the reason for Input not working is, data.transSalesOrderDetail will be lead to an error on it's first evaluation, so try to add Elvis operator. Once service in ngOnInit() is executed data will loaded and will properly as expected

Another way is, define variable and add it for input binding..

In component.ts

transSalesOrderDetail: TransSalesOrderDetail;

...
this.transSalesOrderForm.patchValue(response);
this.data = response;
this.transSalesOrderDetail = response.transSalesOrderDetail;
...

In HTML:

<app-trans-sales-order-detail-list [salesOrderDetailsInput]="data?.transSalesOrderDetail"></app-trans-sales-order-detail-list>


 // or with new variable <app-trans-sales-order-detail-list [salesOrderDetailsInput]="transSalesOrderDetail"></app-trans-sales-order-detail-list>

Better to add type for Input as well

@Input() salesOrderDetailsInput: TransSalesOrderDetail;
Ganesh
  • 5,808
  • 2
  • 21
  • 41
0

You have to make the transSalesOrderDetail an observable in your parent. Then subscribe to this observable in your child.

<app-trans-sales-order-detail-list [salesOrderDetailsInput]="data.transSalesOrderDetail"></app-trans-sales-order-detail-list> 

Then in your child,

@Input() salesOrderDetailsInput: any;
...
...

ngOnInit(): void {
  salesOrderDetailsInput.subscribe(data => console.log(data));
}

Optionally, you can use an async pipe which would remove the necessity of subscribing - but there you will have to use the onChange event to get the latest data.,

Charlie
  • 22,886
  • 11
  • 59
  • 90