Sharing a callback function with a child component results in the error message "Invalid type: <func> is not recognized as a function"

I have been trying to pass a callback function down to my app-autocomplete component using the displayFn input parameter:

<app-autocomplete [displayFn]="displayWith" formControlName="starter">
</app-autocomplete>

The parent component simply implements this:

displayWith(item) {
  return item ? item.name : null;
}

Within the app-autocomplete component, there is a mat-autocomplete element where the [displayWith] function is wrapped by onSelectEvent:

<mat-autocomplete [displayWith]="onSelectEvent" #autocomplete="matAutocomplete">
</mat-autocomplete>

The purpose of onSelectEvent is to save the selected option and then call the provided displayFn function:

onSelectEvent(option) {
  this.selectedOption = option;
  return this.displayFn(option);
}

However, I am encountering an error:

ERROR TypeError: this.displayFn is not a function

I am puzzled about why this error is occurring. How does my approach differ from that in this answer?

The same error occurs with the following code snippet:

onSelectEvent = (option) => {
  this.selectedOption = option;
  return this.displayFn(option);
}

Below is the complete component code:

@Component({
  selector: 'app-autocomplete',
  styles: [`
  `],
  template: `
  <div class="app-autocomplete">

    <mat-form-field>
      <input #autocompleteInput matInput
             [placeholder]="placeholder" autocomplete="off" [matAutocomplete]="autocomplete"/>
    </mat-form-field>

    <button mat-icon-button type="button" [disabled]="disabled">
      <mat-icon>clear</mat-icon>
    </button>

    <mat-autocomplete #autocomplete="matAutocomplete" [displayWith]="onSelectEvent" autoActiveFirstOption>
      <mat-option *ngFor="let option of filteredOptions$ | async" [value]="option">
        {{option.name}}
      </mat-option>
    </mat-autocomplete>    
  </div>
  `,
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => AutocompleteComponent),
      multi: true,
    },
    {
      provide: NG_VALIDATORS,
      useExisting: forwardRef(() => AutocompleteComponent),
      multi: true,
    }]
})
export class AutocompleteComponent implements OnInit, ControlValueAccessor, Validator {

  @ViewChild('autocompleteInput')
  autocompleteInput: ElementRef;

  @Input()
  options = [];

  @Input()
  placeholder;

  @Input()
  displayFn: (value: any) => string;

  disabled;

  selectedOption;
  filteredOptions$;

  _onChangeCallback = (value: any) => {};
  _onTouchedCallback = () => {};


  ngOnInit() {
    this.filteredOptions$ = of(this.options);
  }

  filterOptions(v) {

  }

  writeValue(obj: any): void {
    this.autocompleteInput.nativeElement.value = obj.value;
  }

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

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

  setDisabledState(isDisabled: boolean): void {

  }

  validate(c: AbstractControl): ValidationErrors | any {
    return undefined;
  }

  onSelectEvent = (option) => {
    this.selectedOption = option;
    return this.displayFn(option);
  }

}

Answer №1

After realizing my mistake, I finally discovered the onSelectionChange event for mat-option, which surprisingly took me a while to figure out. However, there is currently no example available on this, but it is mentioned here.

Now, I am utilizing the onSelectionChange event:

<mat-option *ngFor="let option of filteredOptions$ | async" [value]="option" (onSelectionChange)="onSelectionChange(option)" >
  {{option.name}}
</mat-option>

This event saves the selection as follows:

onSelectionChange(option) {
  this.selectedOption = option;
}

Next, I simply pass the displayFn function to displayWith:

<mat-autocomplete #autocomplete="matAutocomplete" [displayWith]="displayFn" autoActiveFirstOption>
</mat-autocomplete>

The displayFn function can now be called from anywhere without requiring a reference to an app-autocomplete component:

displayWith(option) {
  return option ? option.name : null;
}

Answer №2

When dealing with the mat-autocomplete component, it is important to note that accessing this in a non-arrow function could lead to undefined behavior.

To rectify this issue, make sure your onSelectEvent is defined as an arrow function like so:

onSelectEvent = (option) => {
    this.selectedOption = option;
    return this.displayFn(option);
}

If you're still facing problems, feel free to check out my stackblitz demo. The first button demonstrates the incorrect usage of a non-arrow function, while the second button showcases the correct implementation using an arrow function.

Answer №3

To ensure proper functionality of the app-autocomplete component, it is important for this.displayFn to be an EventEmitter. If the onSelectEvent function is defined within the app-autocomplete component, you can use the code snippet below:

onSelectEvent(option) {
    this.selectedOption = option;
    return this.displayFn.emit(option);
}

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

Using a Typescript enum as a parameter type can lead to accepting incorrect values

When working with TypeScript, I have defined an enum, and now I want to create a function that accepts a parameter whose value is one of the enum's values. However, TypeScript does not validate the value against the enum, allowing values outside of th ...

Tips for resolving the error message "Cannot use type '{}' as an index type"

interface TicketTime { startTime: string; endTime: string; } interface TicketInfo { id: number; from: string; to: string; company: string; price: number; time: TicketTime; duration: number; connectionAmount: numb ...

How can I enable autofocus on a matInput element when clicking in Angular 6?

Is there a way to set focus on an input element after a click event, similar to how it works on the Google Login page? I've tried using @ViewChild('id') and document.getElementId('id'), but it always returns null or undefined. How ...

Update of input not available

Does anyone know how to effectively transfer data from one page to another programmatically? I'm facing an issue where updating an input field doesn't work unless I manually edit it afterwards. Is there a better way to achieve this? Another prob ...

Bringing in information from a TypeScript document

I'm having trouble importing an object from one TypeScript file to another. Here is the code I am working with: import mongoose from "mongoose"; import Note from './models/notes'; import User from './models/users'; import ...

Show the hash codes in angular2

I need assistance with displaying hash values in angular2. The response I am receiving is in the following format: [{"1":"7"},{"2":"6"},{"3":"8"},{"4":"1"}] The key "1" represents user_id and the value "7" represents the count of posts for that user. I w ...

The output indicated an error: Unrecognized argument: '--extract-css'

Encountering the error "The NPM script 'start' exited without indicating that the Angular CLI was listening for requests. The error output displayed an unknown option: '--extract-css'" I recently revisited a project that had been untou ...

Formatting for data sources in Angular Material's mat-table component

Looking for assistance with converting data into Angular Material Mat-Table format. Can someone guide me on transforming this data to be compatible with mat-table dataSource? Check out the sample code here "timelineitems": [ { "1/29/2020": { ...

Execute a function once an observable variable has been successfully initialized

I'm currently putting together a chat application using socket.io in Angular. I've encountered an issue where I can't seem to execute a particular code or function right after an observable variable is initialized through subscription. The i ...

"Maximizing the potential of Bootstrap and Ionic within Angular: A step-by

I have an Angular project created using the Ionic framework, but I also want to incorporate Bootstrap. How can I achieve this? I have installed Bootstrap and made changes to the angular.json file as shown below: "styles": [ { ...

Leveraging default values in generic implementations

Imagine a scenario where the following code is present: type QueryResult<ResultType = string, ErrorType = string> = { result: ResultType, } | { errors: ErrorType, } So, if I want to initialize my result, I can proceed like this: const myResult: ...

Incorporate a linked select dropdown into the registration form

I am working on a sign-up form and trying to integrate 2 linked select boxes into the form. The code for the linked select boxes works fine separately but when I attempt to add it to the form, it doesn't display as expected. I attempted to incorporate ...

How can Array<T> in Typescript be used to dynamically determine and generate a new instance of T?

My challenge involves managing multiple typed Arrays containing different objects. I am searching for a way to create a function that can add a new blank object to any array, as long as the type has an empty constructor available. Being new to Angular 2 a ...

"Utilize Tuple in TypeScript to achieve high performance programming

I've been delving into TypeScript, focusing on the tuple type. As per information from the documentation, here is the definition of a tuple: A tuple type is another form of Array type that precisely knows its element count and types at specific posi ...

Top tips for maximizing efficiency with angular cicd jenkins and docker

Currently, I am in the process of setting up a CI/CD pipeline for my Angular UI. I am unsure whether the build commands should be executed in Jenkins or in the Docker build. I have come across different approaches where the build commands are executed in ...

Detecting changes in Angular when the @Input() value remains the same

I have created an Angular Custom scroll directive that utilizes an @Input() to pass an HTML element as a parameter, allowing the scrollbar to move to that specific element. However, I've encountered an issue where if I pass the same HTML Element mult ...

Demystifying the Mechanics of RxJS Subscriptions during an HTTP Request

export class VendorHttpService { result = '0'; constructor(private http: HttpClient, private global: GlobalService) { } getProfileStatus(uid: String): string { this.http.get(this.global.getUrl()+"/vendor/profile-status/"+uid) ...

Exploring the Process of Iterating Through a JSON Array in Angular

Within my angular application, I am utilizing an API to retrieve data regarding a chosen country. However, I am encountering difficulties in showcasing the "name" property from the languages section within the response data: [{ "name": "Colombia", ...

Creating personalized error messages for Angular 4 validation

I'm currently tackling a form in Angular 4 (reactive forms) and I'm encountering an issue with displaying error messages for the email address field individually. I've attempted the following format but haven't had any success. <di ...

What is the best way to show TypeScript code using "<code>" tags within an Angular application?

I am looking to showcase some TypeScript (angular code) as plain text on my website using prismjs. However, the Angular framework is executing the code instead. How can I prevent it from running? I have attempted enclosing it within pre and code tags wit ...