The NgModel within the parent component is expected to mirror the state of the child component

My Angular 10 project includes a library with a wrapper component around a primeng-component. The primeng-component utilizes ngModel. I am trying to set the ngModel in the parent-component accessing the wrapper-component, and I want any changes made to the ngModel within the primeng-component to be reflected back in the parent-component.

This is how the parent-component incorporates the wrapper:

parent.component.html

<div class="col">
      <wrapper-autocomplete
        [suggestions]="projects"
        ngDefaultControl
        [ngModel]="selectedProject"
        (ngModelChange)="changeSelectedProject($event)"
        [field]="'name'"
        (completeMethod)="getProjects($event)"
        [forceSelection]="true">
        
        ...

      </wrapper-autocomplete>
    </div>

In the parent-component, the [ngModel] is set to a variable named selectedProject, which triggers a change through ngModelChange event calling a custom function:

parent.component.ts

 changeSelectedProject(event: any) {
   this.selectedProject = event;
 }

The wrapper-component includes the primeng-component as follows:

wrapper.component.html

<p-autoComplete
  ...
  [ngModel]="ngModel"
  (ngModelChange)="ngModelChangeCallback($event)">

  ...

</p-autoComplete>

The TypeScript portion of the code is structured like this:

wrapper.component.ts

@Component({
  selector: 'wrapper-autocomplete',
  templateUrl: './autocomplete-list.component.html',
  styleUrls: ['./autocomplete-list.component.css']
})
export class AutocompleteListComponent {

  @Input() ngModel: any;
  @Output() ngModelChange: EventEmitter<any> = new EventEmitter<any>();

  ngModelChangeCallback(event: any): void {
    this.ngModelChange.emit(event);
  }
}

Although the current setup effectively reflects changes back to the parent-component, it is not the ideal approach for future users. I aim to simplify the model binding process by enabling users to bind the model using [(ngModel)] instead of [ngModel] and (ngModelChange)=....

Any insights on where I might be going wrong would be greatly appreciated. I have looked into the ControlValueAccessor feature, but I struggled to implement it correctly in my code.

Thank you for your help!

Answer №1

If you're looking to implement a custom ControlValueAccessor, I can help guide you through the process. Here's a step-by-step example:

<p-autoComplete
  ...
  [ngModel]="value"
  (ngModelChange)="ngModelChangeCallback($event)">

  ...

</p-autoComplete>

@Component({
  selector: 'wrapper-autocomplete',
  templateUrl: './autocomplete-list.component.html',
  styleUrls: ['./autocomplete-list.component.css'],
  providers: [
        {
            provide: NG_VALUE_ACCESSOR,
            useExisting: forwardRef(() => AutocompleteListComponent ),
            multi: true,
        }
    ],
  })
export class AutocompleteListComponent implements ControlValueAccessor {

    onValueChange: any; 
    value: any;

    writeValue(val: any): void {
        this.value = val;
    }
    registerOnChange(fn: any): void {
        this.onValueChange = fn;
    }
    registerOnTouched(fn: any): void {}
    setDisabledState?(isDisabled: boolean): void {}

    ngModelChangeCallback(event: any): void {
      this.onValueChange(event);
    }

}


// To use it:

  <wrapper-autocomplete
     [suggestions]="projects"
     ngDefaultControl
     [(ngModel)]="selectedProject"
     [field]="'name'"
     (completeMethod)="getProjects($event)"
     [forceSelection]="true">
       
     ...

  </wrapper-autocomplete>

I have implemented a custom Control Value Processor with 4 methods for handling parent ngModel changes:

  1. writeValue(val: any) - Invoked whenever the parent ngModel updates.
  2. registerOnChange(fn: any) - Called at component initialization to store the function that emits data to the parent.
  3. registerOnTouched(fn: any) and setDisabledState(isDisabled: boolean) - Optional methods for registering touch or disabled state changes.

Another approach is using the Banana in a box trick:

You can replace input/output names instead of ngModel. Here's an example:

export class AutocompleteListComponent {

  @Input() input: any;
  @Output() inputChange: EventEmitter<any> = new EventEmitter<any>();

  ngModelChangeCallback(event: any): void {
    this.inputChange.emit(event);
  }
}

 <wrapper-autocomplete
   [suggestions]="projects"
   [(input)]="selectedProject">  </wrapper-autocomplete>


Answer №2

A handy solution for handling this kind of problem is to utilize the ControlValueAccessor. In addition, there's a clever workaround that involves using less code.

All you have to do is simply change the name of ngModel in your child component to something like Smth, and as a result, the output will automatically become SmthChange. This allows you to effortlessly bind [(Smth)].

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

The absence of the 'classes' property in the MUI component type is causing an issue in Typescript Material UI

Simply put, typescript is giving me a hard time by complaining about the missing property classes on every material-ui component. Essentially, Typescript requires the presence of the classes property in nearly all material-ui components. Here is the error ...

Although VSCode is functioning properly, there seems to be a discrepancy between the VSCode and the TS compiler, resulting in an

While developing my project in VSCode, I encountered an issue with accessing req.user.name from the Request object for my Node.js Express application. VSCode was indicating that 'name' does not exist in the 'user' object. To address thi ...

What is the most efficient way to simultaneously check multiple variables for undefined values?

Before executing my code, I need to ensure that none of the variables in a given list are undefined. In the code snippet below, there are 4 variables with uncertain values. While I can manually check variables a and b to satisfy TypeScript's requirem ...

formatting the date incorrectly leads to incorrect results

Angular example code snippet: console.log( moment('2013-07-29T00:00:00+00:00').format('YYYY-MM-DD') ); Why is the output of this code showing 2013-07-28 instead of 2013-07-29? I would appreciate some help in understanding what may ...

Angular: The type AbstractControl<any> cannot be assigned to type FormControl

I am working with a child component that includes an input tag <input [formControl]="control"> component.ts file @Input() control: FormControl; In the parent component, I am using it as follows: <app-input [control]="f['email ...

The Mat-Datepicker will always show the current date in your local time zone, never in

Utilizing a datepicker and momentjs, I have successfully implemented sending UTC dates to my server. However, the issue arises when retrieving these UTC dates back into the date picker, as it always reflects my timezone (EST). This discrepancy can result i ...

Troubleshooting: Why is my Datatables data not showing up with Angular 2/4 server side processing

Angular version 4.2.4 Angular-Datatables version 4.2.0 HTML Code Snippet <table datatable [dtOptions]="dtOptions"></table> Component Function ngOnInit() { this.dtOptions = { ajax: { url: "http://localhost:8880/nmets ...

Is it preferable to place my http services in the core folder as singletons, or within the lazy loaded modules themselves within Angular?

(React) Is it more advisable to store my http services in a core folder (as singleton) or within the lazy loaded modules themselves? What is the recommended approach? ...

What happens when i18next's fallbackLng takes precedence over changeLanguage?

I am currently developing a Node.js app with support for multi-language functionality based on the URL query string. I have implemented the i18next module in my project. Below is a snippet from my main index.ts file: ... import i18next from 'i18next& ...

Trigger a new Action upon successful completion of another action

I am a newcomer to the world of Angular and Redux. I have a common question for which I can't seem to find the right answer or maybe just need a helpful tip. I am utilizing ngrx in my application and I need to update some basic user data and then refr ...

Webpack is encountering difficulties in locating the entry module when working with typescript

I've been working on integrating webpack into my typescript application. To get a better understanding of webpack, I decided to do a minimal migration. I started by cloning the Angular2 quickstart seed and added a webpack.config.js: 'use strict& ...

Combining two datasets within a single subscription either renders the set as read-only or allows for modifications to be made to the set in Angular

Hey there! I've been encountering some issues with a service I'm using. It takes a long time to return its result, and to make matters worse, I have to call it twice. The data I receive forms two grids, with one being editable and triggering ev ...

Error TS 2322 - The property 'id' is not present in the object of type '{ id: number'

Just starting out with Angular and TypeScript. I created a model with the same properties but encountered an error and am struggling to find a solution: TS2322: Type '{ id: number; model: string; plate: string; deliveryDate: string; deadline: st ...

Replace pipeline function during component testing

During my unit testing of a component that utilizes a custom pipe, I encountered the need to provide a fake implementation for the transform method in my test. While exploring options, I discovered that it's feasible to override components, modules, ...

Conditions are in an angular type provider with AOT

I am facing an issue with my Angular project that is compiled using AOT. I am trying to dynamically register a ClassProvider based on certain configurations. The simplified code snippet I am currently using is below: const isMock = Math.random() > 0.5; ...

Error in Typescript: The type 'Element' does not have a property named 'contains'

Hey there, I'm currently listening for a focus event on an HTML dialog and attempting to validate if the currently focused element is part of my "dialog" class. Check out the code snippet below: $(document).ready(() => { document.addEventListe ...

Angular functions fail to update the loop variable

Using the documentSnapshot function in Firestore to verify the existence of a document. The function is executed in a loop up to a value of 5. However, even though the function runs 5 times, the value of 'i' always reflects the last value. The ...

Tips on determining the data type for a personalized useFetch hook

I want to develop a useFetch hook to handle various types of data like objects and arrays. Is there a way to make it dynamic without specifying a specific type? Sample function useRequest(url: string, method: Method, data: any) { const [response, s ...

Determining the total number of items in an array in Angular efficiently without causing any lag

Currently, I am using the function checkDevice(obj) to validate if a value is present or not. In addition to this functionality, I also require a separate method to determine the number of occurrences of Device in the Array. component.ts public checkDevi ...

Getting News API and showcasing the information in Vuetify.js card components: A step-by-step guide

I'm trying to develop a news website by utilizing the News API for news data. I obtained an API Key from the official News API website, but my code is encountering some issues. The error message reads: TypeError: response.data.map is not a function ...