Guide to successfully submitting an Angular form that includes a nested component

I have developed a custom dateTime component for my application. I am currently facing an issue where I need to integrate this component within a formGroup in a separate component. Despite several attempts, I am unable to display the data from the child form within the parentForm. Is there any way to set this as a property or value of the parent form?

Child DateTime Picker HTML:

<mat-form-field>
  <input matInput [ngxMatDatetimePicker]="picker" placeholder="{{ name }}" [formControl]="dateControl" required="true">
  <mat-datepicker-toggle matSuffix [for]="picker"></mat-datepicker-toggle>
  <ngx-mat-datetime-picker #picker startView="year"></ngx-mat-datetime-picker>
</mat-form-field>

Child Typescript:

@Input() name: string;
@Input() displayTime: boolean;
@Input() allowDateInPast: boolean;

public dateControl = new FormControl();

constructor() { }

ngOnInit() {

}

Parent HTML/form:

<form [formGroup]="formGroup">
<mat-label>Name</mat-label>
    <input type="textfield" formControlName="reportName" matInput required="true" placeholder="Report Name" name="reportName">
</mat-form-field>

<div class="col-sm">
    <app-datetime-picker [name]="'Start Date'" [displayTime]="true" [allowDateInPast]="true"></app-datetime-picker>
</div>

<button class="float-right" [disabled]="formGroup.invalid" (click)="createReport()" mat-raised-button color="primary">Save</button>
  </div>
</form>

Parent Typescript:

formGroup: FormGroup = new FormGroup({

reportName: new FormControl("", Validators.required),
// ?? something here
});

Is it feasible to achieve this? Do I need to utilize @Output() in some way?

Appreciate any assistance.
Travis W-

Answer №1

My preferred approach is to pass the FormControl down as an input. In the child component, set up an input like this:

@Input() dateControl: FormControl;

In the parent html file, simply pass down the FormControl like so:

<app-datetime-picker [dateControl]="formGroup['dateControl'] >

Now you can access and manipulate the properties of the FormControl in the parent component just like you normally would.

While I agree that a control value accessor would also be a great solution, it might be a bit more complex to implement.

Answer №2

If you want your app-datetime-picker component to function as a form control, it's important to implement the ControlValueAccessor interface. This will enable you to utilize it similarly to an <input> or <select>.

Your date picker control should follow this structure:

@Component({
  selector: 'app-datetime-picker',
  templateUrl: './app-datetime-picker.html',
  styleUrls: ['./app-datetime-picker.css'],
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => DateControlPicker),
      multi: true
    }
  ]
})
export class DateTimePicker implements ControlValueAccessor {
  disabled = false;
  innerValue: Date;

  //Ensure that your template invokes this function to update the value
  valueChanged(obj: Date) {
    this.writeValue(obj); //store the value for rendering within this component
    this.onChangeCallback(obj); //update the form
  }

  //Fulfill the ControlValueAccessor contract
  writeValue(obj: any): void {
    this.selectedValue = obj;
  }

  registerOnChange(fn: any): void {
    this.onChangeCallback = fn;
  }

  registerOnTouched(fn: any): void {
    this.onTouchedCallback = fn;
  }

  setDisabledState(isDisabled: boolean): void {
    this.disabled = isDisabled;
  }

  private onTouchedCallback: () => void = () => {
  };
  private onChangeCallback: (_: any) => void = () => {
  };
}

You can then include it in a form template like so:

<form [formGroup]="myFormGroup">
    <app-datetime-picker formControlName="myControlName"></app-datetime-picker>
</form>

The corresponding code to establish the form group would be:

constructor(private formBuilder: FormBuilder){}

ngOnInit(){
    this.myFormGroup = this.formBuilder.group({
        myDateControlName: new FormControl(new Date());
    })
}

Answer №3

Shoutout to @SnorreDan for the inspiration... but check out this more detailed answer.

Below is the TypeScript code for the child component:

@Input() dateTime: FormGroup = new FormGroup({
    startDate: new FormControl("", Validators.required),
  });


constructor() { 
    this.dateTime = new FormGroup({});
    this.dateTime.addControl("startDate", new FormControl());
  }

In the child's HTML file:

<mat-form-field>
      <input matInput [ngxMatDatetimePicker]="picker" placeholder="{{ placeholder }}" required="true" formControlName="startDate">
      <mat-datepicker-toggle matSuffix [for]="picker"></mat-datepicker-toggle>
      <ngx-mat-datetime-picker #picker startView="year"></ngx-mat-datetime-picker>
</mat-form-field>

In the parent's formGroup:

 // Make sure this matches the structure in the child component.
 formGroup: FormGroup = new FormGroup({
    startDate: new FormControl("", Validators.required),
 });

And here's how it looks in the parent's HTML:

<app-datetime-picker [dateTime]="formGroup" [placeholder]="'Start Date'" [endDate]="false" [displayTime]="false" [allowDateInPast]="true"></app-datetime-picker>

If you have any questions or need help with this, feel free to reach out! It took me longer than expected to figure it all out.

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

Component re-rendering and initializing useReducer

I made some revisions to this post. Initially, I shared the entire problem with my architecture and later updated it to focus directly on the issue at hand in order to make it easier for the community to provide assistance. You can now jump straight to the ...

Exploring RouteReuseStrategy in Angular 2

I followed the RouteReuseStrategy advice provided here, but had to make some adjustments. Specifically, I had to modify the handling of routeConfig.path in the shouldAttach method as it was empty and causing issues with caching. My Angular router version i ...

Passing data in Angular 4 with eventEmitter across multiple layers of components

Struggling with a challenge in Angular and need some guidance. I am currently working with Angular 4 and here is the scenario: The app.component.html file contains a wrapper div that I want to be able to change its color by adding a class to it. However ...

Verify the accuracy of each object in an array by comparing it to an enum and confirming its validity

I am trying to determine how many matches/true values there are based on the values of all objects in an array, compared to an enums value. My array of objects is structured like this: const jobs = [{ description, title, }... ] In addit ...

Comparison between typings and @types in the NPM scope

There are different approaches when it comes to handling TypeScript definitions. In some cases, the typings tool is used, as seen in projects like angular/angular2-seed. Alternatively, some projects use scoped NPM packages with the prefix @types, complete ...

Tips for displaying only the initial 15 characters of a text value

The content extracted from a .ts file is being displayed on the home.html page. I am aiming to display only the initial 15 characters followed by 3 dots (...). Despite my efforts, the following code snippet is not functioning as expected: home.html < ...

Uncheck all boxes except for the required or disabled boxes in Angular

HTML: <mat-selection-list #selectedColumns [(ngModel)] ="selectedOptions"> <div class= "content-section"> <mat-expansion-panel> <mat-expansion-panel-header> ...

Insert a blank row at the top of the grid in Wijmo for Angular 2

I am attempting to insert a new empty row at the start of the grid when an external button is clicked. The grid is displaying correctly. <wj-flex-grid #flex [itemsSource]="data" [isReadOnly]="true" [headersVisibility]="'Column' ...

What is the method for incorporating a variable into a fragment when combining schemas using Apollo GraphQL?

In my current project, I am working on integrating multiple remote schemas within a gateway service and expanding types from these schemas. To accomplish this, I am utilizing the `mergeSchemas` function from `graphql-tools`. This allows me to specify neces ...

What steps do I need to take to create a fresh interface in useState with the help of Typescript

I'm attempting to replicate an input by utilizing useState with an interface. Each time I click on the + button, the interface should be duplicated in the state, thereby duplicating my input. Here is the code I am working on: interface newInputsInter ...

Potential null object in React/TypeScript

Encountering a persistent error here - while the code functions smoothly in plain react, it consistently throws an error in typescript stating "Object is possibly null". Attempts to resolve with "!" have proved futile. Another error logged reads as follow ...

Using Angular 2 to bind ngModel to a property's reference

I have a lengthy list of inputs provided by users that I would like to store in an object instead of listing them out in HTML. My goal is to connect these values to another object that holds the data of the user or customer. I am looking to use ngModel for ...

Creating a Typescript interface where one property is dependent on another property

Let's look at an illustration: type Colors = { light: 'EC3333' | 'E91515' dark: '#100F0F' | '140F0F' } interface Palette { colorType: keyof Colors color: Colors[keyof Colors] } Is it possible for the ...

Guide on accessing js file in an Angular application

I have a component where I need to create a function that can search for a specific string value in the provided JavaScript file. How can I achieve this? The file path is '../../../assets/beacons.js' (relative to my component) and it's named ...

When implementing angular routing, the default route is not automatically selected

When using routerLinkActive for selecting the default route and displaying an image, I encountered an issue where the image was not loading upon initial page load. However, when I switched tabs, the image would display correctly. Below is the code snippet ...

What could be the reason for the react hook form failing to capture the data upon submission?

I am struggling to access the props' value after clicking the button, as it keeps returning undefined. My goal is to display the years of service and profession details based on the user's selection. return ( <form onSubmit={handleSubmit(o ...

Guide on associating user IDs with user objects

I am currently working on adding a "pin this profile" functionality to my website. I have successfully gathered an array of user IDs for the profiles I want to pin, but I am facing difficulties with pushing these IDs to the top of the list of profiles. My ...

How to Open a Work Item in TFS 2017 Using an Angular Application

I recently developed a TFS 2017 extension using Angular Framework. One of the features in this extension is a table that includes a column for Work Item ID. The desired functionality is that when a user clicks on the ID, it should open up the corresponding ...

typescript mistakenly overlooked a potential undefined value in indexed records

In my code, I have defined an index-based type X. However, when using a non-existing key, TypeScript does not accurately infer the result type as ValueType | undefined. Is there a solution to correct this issue? type ValueType = { foobar:string; } t ...

What is the correct way to access and assign a value from a different getter or setter? I am facing an issue with the creation of my second array

Two http GET API calls are being made in the constructor. The first call is working fine and has a getter/setter to filter the main array (studentNameData) into a filtered array (filteredName). However, the second call is also trying to do the same thing b ...