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

Typescript typings for child model/collection structures

I have encountered an issue while trying to implement a Model/Collection pattern with various typings. Both the Model and Collection classes have a method called serialize(). When this method is called on the Collection, it serializes all the Model(s) with ...

When I utilize a component to create forms, the React component does not refresh itself

One of the components I am working with is a form handling component: import React, { useState } from "react"; export const useForm = (callback: any, initialState = {}) => { const [values, setValues] = useState(initialState); const onCha ...

Managing the activation and deactivation of a form based on value modifications

I have a formgroup with interconnected formcontrols where each control is enabled only if the previous one is filled. Additionally, if a control is cleared, all subsequent controls should also be cleared and disabled. To illustrate this use case, I create ...

Excluding files in Angular by adding them to the .gitignore list

Interested in mastering the integration of Twilio with your Angular app? The first step is to configure your Twilio credentials and make sure to add that file to the .gitignore. However, upon reviewing this example Angular-Twilio project on GitHub, I notic ...

The utilization of a Typescript Generic for basic addition is not producing the desired results

This issue feels incredibly insignificant, but for some reason I can't seem to figure out why it's not functioning correctly. It seems like the return type should match since I am simply concatenating or adding based on whether it's a number ...

Understanding the process of retrieving form data files sent from Angular to TYPO3/PHP via a Post request

I've been working on uploading an image from Angular to the TYPO3 backend, but I'm struggling to read the request body in the Controller. Here's the code snippet from my Angular side: HTML: <input multiple type="file" (change ...

Experiencing difficulty retrieving individual :id information from a list in MEAN stack

**I'm experiencing issues retrieving a single :id from a list as the data returned is not what I expected... ** GET /article/5b0be8829f734a4e580a43c5 401 3.845 ms - 99 ===> response from my get request my api ===> var express = require(&apos ...

The error message "Error: cannot read property ‘setDirtyAttribute’ of null" may be encountered when attempting to use the method YourModel.create({...}) in ember-typescript-cli

Encountering the error cannot read property 'setDirtyAttribute' of null even when using YourModel.create({...}) in ember-typescript-cli to instantiate an EmberObject. Model: import DS from 'ember-data'; import {computed} from "@ember/ ...

Using Angular's dependency injection in a project that has been transpiled with Babel

I am currently attempting to transpile my Angular 6 project, which is written in TypeScript, using the new Babel 7. However, I am facing challenges with getting dependency injection to function properly. Every time I try to launch the project in Chrome, I ...

Angular Firebase Count of Items in List

My current dilemma revolves around obtaining a count of items within a firebase list. Firebase details: foods randompushid title: apple, comboQuery: apple23523526(userid) Angular Code snippet: this.foods= this.firebaseDB.list(& ...

Using Angular 6 pipes can simplify observable operations by eliminating the need for explicit typing

Recently, I upgraded my application from Angular5 to 6. Upon completing the update, I proceeded to migrate to rxjs6, which resulted in a change in my code where I was utilizing the takeWhile method. As a result, in order to subscribe to a service, my code ...

Angular2 - Incorporating a New Attribute

I am working with the following Angular2 code: <ngx-datatable-column prop="id" name="ID"> <template ngx-datatable-cell-template let-row="row" let-value="value"> <a [routerLink]="['/devicedtls',r ...

The property 'setParentKeyForNodeData' is not found in the 'Model' type. Perhaps you meant to use 'setCategoryForNodeData'?

As I work with gojs and utilize an organizational chart, I have encountered a problem with one of the context menus. Specifically, when selecting "Remove Role", I receive an error message stating Property 'setParentKeyForNodeData' does not exist ...

Uploading files to an FTP server using Angular 2

Hello everyone, I'm diving into the world of technology and currently exploring how to upload a file to an FTP server using Angular 2. Does anyone have any code snippets or tutorial pages that could guide me through this process? I've been searc ...

Is it possible to prevent casting to any by improving type definitions?

My query can be best elucidated with the following code snippet. The comments within the code aim to provide clarity on the nature of the question. type MediaFormats = "video" | "audio"; type IMediaContent<TType extends MediaFormats, TValue> = { ...

Creating a multipart/form-data POST request in Angular2 and performing validation on the input type File

I am working on sending an image (base64) via a POST request and waiting for the response. The POST request should have a Content-Type of multipart/form-data, and the image itself should be of type image/jpg. This is what the POST request should look like ...

Angular 13: Issue with displaying lazy loaded module containing multiple outlets in a component

Angular version ^13.3.9 Challenge Encountering an issue when utilizing multiple outlets and attempting to render them in a lazy module with the Angular router. The routes are being mapped correctly, but the outlet itself is not being displayed. Sequence ...

Handling HTTP Errors in Angular Components with NGRX

I have successfully integrated the store into my angular project. I am able to handle and process the successSelector, but I am facing difficulty in capturing any data with the errorSelector when an HTTP error occurs from the backend. The error is being c ...

Beneath the Surface: Exploring Visual Studio with NPM and Typescript

Can you explain how Visual Studio (2015) interacts with external tools such as NPM and the Typescript compiler (tsc.exe)? I imagine that during the building of a solution or project, MSBuild is prompted to execute these additional tools. I'm curious a ...

Getting the current page name within the app.component.ts file in Ionic 3 - a simple guide

Is it possible to retrieve the current active page name within the app.component.ts file in Ionic without having to add code to any other pages? ...