What is the correct way to use Observables in Angular to send an array from a Parent component to a Child

Initially, the task was to send JSON data from the parent component to the child component. However, loading the data through an HTTP request in the ngOnInit event posed a challenge as the data wasn't being transmitted to the child component. Here is the initial approach:

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);
    }

}

In the parent HTML, the data was sent as follows:

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

However, when checking for the value of salesOrderDetailsInput in the child component.ts using console.log, it returned 'undefined'.

Realizing that the issue might be due to the parent loading the data too late, I attempted another approach by utilizing ngOnChanges event:

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

This enabled me to receive the data passed from the parent. Yet, after researching on Stack Overflow, I learned about the necessity of async functions and observables when passing values from the parent to the child without the need for ngOnChanges. Hence, I adjusted my code accordingly:

In the parent component:

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

Unfortunately, this modification resulted in an error during compilation and in IntelliJ Idea IDE, stating:

Argument type TransSalesOrderDetail[] is not assignable to parameter type Observable<unknown> | Promise<unknown>
.

Below is the updated child component.ts code snippet:

@Input() salesOrderDetailsInput: Observable<any>;

What could be missing in the implementation?

Answer №1

Asynchronously, the member variable this.data is given a value and contains an empty object when the binding

[salesOrderDetailsInput]="data.transSalesOrderDetail"
is triggered. This results in sending the current state of transSalesOrderDetail, which is undefined.

I believe that there is no necessity for using async in this scenario. Here are a couple of options to consider:

Option 1: *ngIf

Use conditional rendering with *ngIf for the child component tag.

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

In this case, the child component will only be rendered if the transSalesOrderDetail property is defined. Refer to here for more information on the double bang !! operator.

Option 2: safe navigation operator ?.

Utilize the safe navigation operator ?. when accessing properties to ensure they are defined before attempting to access them.

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

In this scenario, the child component may still be rendered, but without being bound to transSalesOrderDetail.


Furthermore, I would like to offer some additional suggestions:

When utilizing the ngOnChanges hook to monitor changes to the @Input properties, it is advisable to check if the property is defined before trying to access it. While this might not pose an issue in a component with a single @Input property, it could become problematic when dealing with multiple @Inputs where some properties are defined and others are not.

@Input() salesOrderDetailsInput: any;

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

Answer №2

In order to achieve this, you need to convert the transSalesOrderDetail into an observable within your parent component. After that, you can subscribe to this observable in your child component.

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

Once you have done that in your child component,

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

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

If preferred, you can also utilize an async pipe which removes the necessity of subscribing - however, in that scenario, you will need to use the onChange event to receive the most recent data.

Answer №3

Using the async pipe is similar to creating a subscription for an observable when fetching data asynchronously, such as from an API. Remember that the left side of the | operator should always be an observable. For 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);
}

Remember to centralize your API calls in a service file. The provided example illustrates the correct usage.

See Live Demo

Answer №4

In my opinion, the reason why Input is not working is because data.transSalesOrderDetail will cause an error during its initial evaluation. To resolve this issue, consider using the Elvis operator. Once the service in ngOnInit() is executed, the data will be properly loaded as expected.

Another approach would be to define a variable and use it for input binding.

In the component.ts:

transSalesOrderDetail: TransSalesOrderDetail;

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

In the HTML:

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


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

It's also advisable to specify the type for Input:

@Input() salesOrderDetailsInput: TransSalesOrderDetail;

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Toggle switch with active state

I'm currently using Ionic 2 alongside Angular 2 beta 11. Is there a way to turn off the toggle switch? The Ionic documentation suggests using the checked attribute, but I've tried that and also attempted ng-checked with no luck. Any advice on t ...

Overtaking a property in a node_module interface: a guide

I am facing a situation where I need to modify an existing interface property in my custom type declaration file, rather than creating a new one. Despite trying various approaches, such as attempting to omit the property, I have not been successful in ach ...

Issues arise when attempting to construct an angular docker image

After attempting to create an angular docker image, I encountered the following error message following the execution of npm run build --prod: executor failed running [/bin/sh -c npm run build --prod]: exit code: 1 I am seeking clarification on this iss ...

Encountering a Circular JSON stringify error on Nest.js without a useful stack trace

My application is being plagued by this critical error in production: /usr/src/app/node_modules/@nestjs/common/services/console-logger.service.js:137 ? `${this.colorize('Object:', logLevel)}\n${JSON.stringify(message, (key, value ...

Is there a way to dynamically create a property and assign a value to it on the fly?

When retrieving data from my API, I receive two arrays - one comprising column names and the other containing corresponding data. In order to utilize ag-grid effectively, it is necessary to map these columns to properties of a class. For instance, if ther ...

Trouble with using the date pipe in Angular specifically for the KHMER language

<span>{{ value | date: 'E, dd/MM/yyyy':undefined:languageCode }}</span> I am facing a challenge where I need to identify the specific locale code for the KHMER language used in Cambodia. Despite trying various cod ...

How to use TypeScript to print a blob (PDF) in Internet Explorer or Microsoft Edge?

I need to print a pdf blob using typescript. I've tried the following code, which works in Chrome but not in Edge. Code 1 (works in Chrome but prints blank in Edge) - const fileURL = URL.createObjectURL(blob); const iframe = document.createE ...

Utilize annotations for both request and response filters to enable client-side routing in Quarkus for forwarding purposes

Currently, I am utilizing Quarkus version 3.4.3 to serve as the backend for my REST endpoints and also host my Angular SPA which acts as a simple interface for interacting with those rest endpoints. The issue I am facing is quite common - I need client-si ...

What is the best way to make multiple network requests with varying parameters using rxjs in an Angular application?

I am facing a challenge with my mat-table and mat-paginator elements. The table is populated with data from an API, which usually returns 10 elements per request. However, I need to display 25 elements in the table, so I have to make 3 separate API calls t ...

Utilize TypeScript to access scope within a directive

Is there a way to access the controller's scope properties using my custom TypeScript directive? For example, in this snippet below, I am trying to retrieve and log scope.message: /// <reference path="typings/angularjs/angular.d.ts" ...

Incorporating interactive elements within an NgFor loop

My goal is to develop a component that takes another component as a parameter and generates a collection of instances of this component, with the ultimate objective being the creation of a versatile backoffice. Here's an example of how it could look: ...

The error message "Declaration file for module 'mime' not found" was issued when trying to pnpm firebase app

Currently, I am in the process of transitioning from yarn to pnpm within my turborepo monorepo setup. However, I have run into an issue while executing lint or build commands: ../../node_modules/.pnpm/@<a href="/cdn-cgi/l/email-protection" class="__cf_e ...

Guide on generating a video thumbnail using JavaScript Application

Searching for a way to easily create a thumbnail from a video for uploading alongside the video itself to a server? I've been looking for JavaScript libraries to simplify the process without much luck. The scenario involves the user selecting a video ...

An issue occurred during the compilation of an Angular6 project

I am embarking on my first Angular project and may not grasp every detail perfectly. In my search for guidance, I came across the command "ng build --prod" on Google and decided to give it a try. Everything seemed to be going smoothly at first until an err ...

Guide to importing simulated data from a JSON file in Angular 2 Karma Jasmine tests

Currently, I am working on creating karma jasmine test case for angular 2. We have encountered a requirement to mock data in a separate JSON file due to the large amount of data (to maintain code cleanliness). After extensive research, we were unable to f ...

Ensure the proper sequence of field initialization within a TypeScript class constructor

Is there a way to ensure the proper initialization order of class fields in TypeScript (4.0) constructors? In this example (run), this.x is accessed in the method initY before it's initialized in the constructor: class A { readonly x: number rea ...

Is a custom test required for PartiallyRequired<SomeType>?

Is there a way to create a function that validates specific fields as required on a particular type? The IFinancingModel includes the property statusDetails, which could potentially be undefined in a valid financing scenario, making the use of Required< ...

"Define a TypeScript function type that can return either an object or a string depending on whether it succeeds or fails

I encountered an issue with a function that either returns a Promise on success or a string on error. async create(createDebtorDto: CreateDebtorDto): Promise<Debtor> { console.log("createDebtorDto", createDebtorDto) try{ const createdD ...

What is the best way to put into practice a Calendar system?

What is the best way to integrate a Calendar in Angular2 using typescript? Are there any specific directives that need to be imported? ...

How to apply conditional styling to text using Angular2

I need to display a list of objects that contain names and numbers. The color of the name should change based on the number associated with it - red for 1, blue for 2, green for 3, and orange for 4. My template currently looks like this: <p *ngFor="l ...