Angular: Keeping Parent Updated with Child Variable Changes

My parent component is a form that consists of two child components:

Experiment Create (Parent)

  • Creation Dataset (Child)
  • Creation Metadata (Child)

I am using an Angular component, mat-accordion, to navigate between the two child components. By using @Input, I can access the data filled in by the children components within the parent. The form submission should only be allowed if a file is chosen for both components. To achieve this, I have set a variable (datasetList[i].fileValid) to track whether a file has been selected. This variable controls the button's disabled state based on whether a file has been updated or not. Two functions are called to disable the button:

  • isDatasetFilesValid()
  • isMetadataFilesValid()

However, when the variable changes in the second child component, it does not update the disabled button. It only works after pressing "previous" and "next". It seems like I need to reload or refresh the parent component, possibly due to the lifecycle.

Parent Component:

export class ExperimentCreateComponent implements OnInit {
  data: any = {};
  datasetList: any = [{ fileValid: false }];
  metadataList: any = [{ fileValid: false }];

  // Functions to navigate through the expansion panels
  setStep(index: number) {
    this.step = index;
  }

  nextStep() {
    this.step++;
  }

  prevStep() {
    this.step--;
  }

  isDatasetFilesValid() {
    return this.datasetList.findIndex(function(item, i) {
      return item.fileValid == false;
    });
  }

  isMetadataFilesValid() {
    return this.metadataList.findIndex(function(item, i) {
      return item.fileValid == false;
    });
  }
}

Parent HTML:

<div class="jumbotron">
  <div class="container">
    <div class="row">
      <div class="col-sm-8 offset-sm-2">

        <form name="form" (ngSubmit)="f.form.valid" #f="ngForm" novalidate>

          <mat-accordion class="headers-align">

            <mat-expansion-panel id="datasetUpload" [expanded]="step === 0" (opened)="setStep(1)" hideToggle="true">

              <app-creation-dataset [datasetList]="datasetList"></app-creation-dataset>

              <mat-action-row>
                <button mat-button color="warn" (click)="prevStep()">Previous</button>
                <button mat-button color="primary" (click)="nextStep()">Next</button>
              </mat-action-row>
            </mat-expansion-panel>

            <mat-expansion-panel id="metadataUpload" [expanded]="step === 1" (opened)="setStep(2)" hideToggle="true">

              <app-creation-metadata [metadataList]="metadataList"></app-creation-metadata>

              <mat-action-row>
                <button mat-button color="warn" (click)="prevStep()">Previous</button>
                <button mat-button color="primary" type="submit" [disabled]="(isMetadataFilesValid() != -1) && (isDatasetFilesValid() != -1)" (click)="createExperiment()">End</button>
              </mat-action-row>
            </mat-expansion-panel>

          </mat-accordion>

        </form>
      </div>
    </div>
  </div>
</div>

Child Component:

export class CreationDatasetComponent implements OnInit {
  @Input() datasetList: any = [{ fileValid: false }];
  fileSelected: File;


  constructor(private papa: Papa, private cd: ChangeDetectorRef) {}

  ngOnInit() {}

  onChange(files: FileList, index: number, dom: any) {
    // Option to parse the file with papaparse
    let options = {
      header: true,
      error: (err, file) => {
        this.datasetList[index].fileValid = false;
        alert(
          "Unable to parse CSV file, please verify the file can be accessed and try again. Error reason was: " +
            err.code
        );
        return;
      },
      complete: (results, file) => {
        console.log("Parsed:", results, file);
        let filename = file.name;

        // Add the dataset to the datasetList
        this.datasetList[index].headers = results.meta.fields;
        this.datasetList[index].values = results.data;
        this.datasetList[index].filename = filename;
        this.datasetList[index].is_metadata = false;
        this.datasetList[index].fileValid = true;
        this.cd.detectChanges();
      }
    };
    this.fileSelected = files[0]; // Get the file
    // Call the function to parse the file, option is the callback
    this.papa.parse(this.fileSelected, options);
  }

  // Add a dataset form
  addDataset() {
    this.datasetList.push({ fileValid: false });
  }

  // Remove a dataset form
  removeDataset(index: number) {
    this.datasetList.splice(index, 1);
  }
}

Child HTML:

<div *ngFor="let dataset of datasetList; let index = index">
  <div id="datasetFiles">
    <h6>Select the type of dataset and browse the files:</h6>
    <div class="container">
      <div class="row justify-content-between">
        <div class="col-6 d-flex align-items-center">
          <input id="file" #file (change)="onChange(file.files, index, $event.currentTarget)" type="file">
        </div>
      </div>
    </div>
  </div>
</div>
<div>
  <button mat-icon-button color="primary" (click)="addDataset()">
    <mat-icon>add_box</mat-icon>
  </button>
</div>

Answer №1

If you need further clarification on this answer, be sure to check the comments on the original question.

Below is an example showcasing the use of @Output:

In the CHILD.COMPONENT.TS file:

@Component({
  selector: 'children',
  templateUrl: './children.component.html',
  styleUrls: ['./children.component.scss'],
  providers: [{...
  })
})

export class ChildrenComponent {

  @Output() editedEmitter = new EventEmitter<number>();
  
  private variableToPass = 10;
  
  constructor() {}
  
  functionToCall() {
    this.editedEmitter.emit(20);
  }

In the PARENT.COMPONENT.HTML file:

<section>
      <children (editedEmitter)="updateValue($event)"></children>
</section>

<!-- in the component you'll do 
  updateValue(val: number) {
    this.variableToUpdate = val;
  }
-->

Answer №2

[disabled] necessitates a condition that is either true or false. The code you have entered:

isMetadataFilesValid() != -1 isDatasetFilesValid() != -1
is not a valid condition as there are two separate statements. If you intend for both conditions to be true, you should use the && operator.

[disabled]="(isMetadataFilesValid() != -1) && (isDatasetFilesValid() != -1)"

Alternatively, I recommend incorporating the condition within the functions themselves so they return a boolean value.

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

What causes an ObjectUnsubscribedError to be triggered when removing and then re-adding a child component in Angular?

Within a parent component, there is a list: items: SomeType; The values of this list are obtained from a service: this.someService.items$.subscribe(items => { this.items = items; }); At some point, the list is updated with new criteria: this.some ...

What is the best way to clear the textureCache in A-Frame's latest release?

Currently working on a project involving multi-camera streaming, I've noticed a continuous increase in memory usage after switching between cameras. I've managed to clean up on the hls.js side of things, but I haven't come across any methods ...

Refreshing an iFrame in NextJS from a different component

I am working on a NextJS application that includes a specific page structure: Page: import Layout from "@/components/app/Layout"; import Sidebar from "@/components/app/Sidebar"; export default function SiteIndex() { return ( < ...

Encountering a "Module not found" error while trying to run npm start in a Create React App

I'm attempting to initiate a React project using create-react-app. Encountered an error when running npm start: Failed to compile. multi ./node_modules/react-scripts/config/polyfills.js ./node_modules/react-dev-utils/webpackHotDevClient.js ./src/i ...

Angular Tutorial: Retrieve values in array that are over 100

I'm having trouble showing the length of items in my array that exceed 100 by using a function call. I keep encountering this error message "ERROR TypeError: Cannot read property 'filter' of undefined". Can someone please assist me ...

A loop that incorporates a jQuery JavaScript dropdown menu along with some calculations

My goal is to have multiple dropdown lists populated from a PHP while loop. Each select dropdown has a corresponding textbox that should update its value when a selection is made. However, the current script only works for a single dropdown outside of the ...

What are some ways to enhance the design of Material Input Text boxes and make them more compact?

I have been developing an Angular application using Material UI v7, but I am finding that the input controls are not as compact as I would like them to be. I have attempted to customize them without success. Can someone provide guidance on how to achieve m ...

Displaying Grunt Command-Line Output in Browser

Is there a straightforward method to display the command-line output of a grunt task in a browser? Essentially, I want to showcase the input and/or output of a command line process within a browser window. While I am aware that I could develop a custom app ...

The interpolation error in Angular is causing an undefined result to occur

I am currently facing an interpolation issue that involves working with data from an API in the following format: {columna1=6.022, columna2=0.0, columna3=3.14.....,columnaN=5.55 } There are instances where there is only one 'columna', while in s ...

Utilizing aliases for CSS/SASS file imports with webpack

Can we use aliases to import .scss / .css files? // webpack.config.js resolve: { alias: { styles: path.resolve(__dirname, "src/styles/"), } } In the main.js file: // main.js import "styles/main.scss"; ...

The array is producing accurate results, however it is displaying as undefined in the HTML output

I have encountered a problem while working on my project. I am utilizing an API to fetch an array of platforms where a video game is available. Although the array is returning the correct data, I am facing difficulty in displaying these values in my HTML a ...

Tips on sending template variables to JavaScript

Greetings, I am currently facing an issue with passing a template variable to javascript in order to create a chart. Unfortunately, Django treats all js files as static files which means that dynamic template variables are not accessible within javascript. ...

Incorporate an onclick event to the elements within the array

I'm currently working on iterating over an array and adding an onclick event to each element. My goal is to be able to click on each div and have them log their values in the console. However, I am facing a challenge in applying the onclick event to e ...

Web Development using HTML and JavaScript

Here is the code for a search bar engine that I am working on. <form id="frmSearch" class="search1" method="get" action="default.html" /> <input class="search" id="txtSearch" type="text" name="search_bar" size="31" maxlength="255" value=" ...

Is there a way to determine the bounding rectangle of a specific word within a paragraph when you only have its index?

I am currently developing a text-to-speech functionality for a react application that can emphasize the word being spoken by highlighting it with a background color. This feature closely resembles the layout of the Firefox reader view. However, my curren ...

The epoch time indicates a 12-hour difference

I keep encountering an error with the time showing as 12:00 P.M. When I receive a response in epoch time format 1454092200000, it corresponds to 1/30/2016, 12:00:00 AM GMT+5:30 $scope.shipmentDate = moment(1454092200000).format("YYYY/MM/DD hh:mm"); The ...

What are the best practices for securely storing a distinctive client identifier for websockets?

In order to differentiate and identify the specific client sending a request through web-sockets, I require a unique client identifier. However, I'm uncertain about the optimal method for storing this identifier so that it can be included with every s ...

I encountered an error when trying to install npm -g @angular/cli

Command to check Node version: v9.2.1 Command to check npm version: 5.5.1 Error occurred after running command npm install -g @angular cli The error message received was .ESOCKETTIMEDOUT Suggestion: If github.com is inaccessible in your area, try set ...

Leverage the `dispatch` hook within a useEffect function

When it comes to triggering an action upon the React component unmounting, I faced a challenge due to hooks not allowing the use of componentWillUnmount. In order to address this, I turned to the useEffect hook: const dispatch = useDispatch(); useEffect(( ...

Implement a getter function within a specific Firestore section using Vuefire

I am currently seeking a solution to set a property from getters to the firestore section using vue. I have implemented vuefire, but encountered the following error: vue.runtime.esm.js?2b0e:1888 TypeError: Cannot read property 'getToUid' of und ...