By harnessing the power of Angular mat-chips and mat-autocomplete combined with input, users can easily add an additional chip by selecting

After combining mat-chips autocomplete and input as per the example section in the documentation, I encountered an issue. When I type a part of an Autocomplete Option to filter the options and then click on it, both the input value and the clicked item get added to the chip list. While this behavior seems logical, as there's no reason for it to ignore the blur event when the item was clicked, I'm wondering if there's a built-in way or a hack to address this issue. Below is my code snippet:

<mat-form-field class="chip-list">
    <mat-chip-list #chipList aria-label="Op selection" class="mat-chip-list-stacked">
        <mat-basic-chip *ngFor="let sop of selectedOps" [selectable]="selectable" 
         [removable]="removable" (removed)="remove(sop)">
            <mat-icon matChipRemove *ngIf="removable">cancel</mat-icon>
            {{sop.val}}
        </mat-basic-chip>
    </mat-chip-list>
    <div style="position: relative;">
        <input matInput [formControl]="chipControl" aria-label="subcats" #SelectInput
            [matAutocomplete]="auto" [matChipInputFor]="chipList" 
           [matChipInputSeparatorKeyCodes]="separatorKeysCodes"
            (matChipInputTokenEnd)="add($event)" [matChipInputAddOnBlur]="addOnBlur">
       
        <mat-icon class="icon" (click)="SelectInput.focus()">keyboard_arrow_down</mat-icon>
    </div>

    <mat-autocomplete #auto="matAutocomplete">
        <mat-option *ngFor="let op of filteredOps | async" [value]="op">

            <div (click)="optionClicked($event, op)">
                <mat-checkbox [checked]="op.selected" (change)="toggleSelection(op)"
                    (click)="$event.stopPropagation(); ">
                    {{ op.val }}
                </mat-checkbox>
            </div>

        </mat-option>

    </mat-autocomplete>
</mat-form-field>

Am I missing something here? Or are these elements not meant to work together in the first place? Any feedback would be appreciated!

Answer №1

Check out this solution I found on https://github.com/angular/components/issues/19279#issuecomment-627263513

Here's a snippet from the source:

We can simulate the behavior of matChipInputAddOnBlur ourselves.

I included (blur)=addOnBlur($event) on the input and disabled matChipInputAddOnBlur. Any clicks on the mat-input should be disregarded.

Here is my implementation:

 addOnBlur(event: FocusEvent) {
    const target: HTMLElement = event.relatedTarget as HTMLElement;
    if (!target || target.tagName !== 'MAT-OPTION') {
      const matChipEvent: MatChipInputEvent = {input: this.fruitInput.nativeElement, value : this.fruitInput.nativeElement.value};
      this.add(matChipEvent);
    }
  }

See an example at https://stackblitz.com/edit/angular-rd38q1-jxbjjb?file=src%2Fapp%2Fchips-autocomplete-example.ts

Answer №2

Adding another solution for the issue at hand.

The challenge lies in the fact that blur behaves differently across various browsers, hence a need for a listener to detect clicks outside while ignoring those on mat-option and input:

constructor(private eRef: ElementRef, @Inject(DOCUMENT) private _document: Document) {
    this.detectOutsideClick();
}

private detectOutsideClick(): void {
    fromEvent(this._document, 'click', { passive: false }).pipe(takeUntil(this.isDestroyed$)).subscribe((event) => {
        const inside = this.eRef.nativeElement.contains(event.target);
        const clickOnOption = (event.target as HTMLElement).tagName === 'MAT-OPTION';

        if (clickOnOption || inside) {
            // clicked inside;
        } else {
            // clicked outside
            if (this.chipsInput.nativeElement.value) {
                this.add({ 
                    input: this.chipsInput.nativeElement, 
                    value: this.chipsInput.nativeElement.value 
                } as MatChipInputEvent
                );
            }
        }
    })
}

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

Modify the style of an element using a media query and Angular 4

Is there a way to update a class of an element based on the browser's width in my .ts file? matchMedia('(max-width: 400px)').addListener((mql => { if (mql.matches) { this.myclass = 'toggled'; } })); In the HTML, it shou ...

How can we access child components in vanilla JavaScript without using ng2's @ViewChild or @ContentChild decorators?

Recently, I delved into the world of using ViewChildren and ContentChildren in Angular 2. It got me thinking - can these be implemented in ES6 without TypeScript annotations? The TypeScript syntax, according to the official documentation, looks something ...

Top approach for Constructing Angular Front-End Forms with User Input

Greetings to all who happen upon this message, thank you for taking the time to potentially offer assistance! In my past experiences working with Wordpress (PHP), I utilized a plugin called Advanced Custom Fields from advancedcustomfields.com. This plugin ...

Exporting modules in TypeScript using "module.exports"

While working on my Yeoman generator, I initially wrote it in JavaScript like this: "use strict"; var Generator = require("yeoman-generator"); var chalk = rquire("chalk"); module.exports = class extends Generator { initializing() { this.log( c ...

Tips for indicating ngbDatepicker as valid in a form even without selecting a value

In my Angular2 project, I am utilizing ng-bootstrap's ngbDatepicker within a Reactive Form. The dates in this form are not required, but the problem is that ngbDatepicker always considers the form as Invalid unless a date is chosen. Is there a method ...

Invoke the service's get method from the child's custom element by calling the parent function

I have developed a unique web component using angular elements that requires calling a method in the main application service.ts file. Custom Web Component @Component({ templateUrl: './sample.component.html', styleUrls: ['./sam ...

The Delicacy of Ionic Date and Time

I'm currently working with Ionic 6 and Angular 14. Has anyone found a way to adjust the sensitivity of the Ionic Time Picker? The scrolling speed is too fast, making it challenging to select the correct hour/minute. https://ionicframework.com/docs/a ...

Combining arrays using Observables in Typescript with RxJS

Having some issues using rxjs Observable.concat function in typescript. Encountering an error "Cannot read property 'apply' of undefined" The problem appears to be limited to typescript and may be related to rxjs version 5 concat. The code seems ...

Prevent Angular from performing change detection on specific @Input properties

Within my Angular component, there are extensive calculations that take place when changes are detected. @Component ( selector: 'my-table', ... 400+ lines of angular template ... ) class MyTable implements OnDestroy, AfterContentInit, On ...

Angular - send multiple HTTP requests for the length of an array and combine the responses into one array

Exploring angular for the first time and dabbling with the trello API. I have an array containing some list IDs that I need to make HTTP GET calls for, equal to the length of the array. For instance, if there are two IDs in the array, then the HTTP call sh ...

What could be the reason for my function throwing a TypeError with the message "<function> is not a function"?

Every time I try to call a function that clearly appears to be defined as a function, I continuously receive the error message: TypeError: [function name] is not a function. To demonstrate the issue, here is a simple example: main.ts import someFunction ...

Updating state atoms in Recoil.js externally from components: A comprehensive guide for React users

Being new to Recoil.js, I have set up an atom and selector for the signed-in user in my app: const signedInUserAtom = atom<SignedInUser | null>({ key: 'signedInUserAtom', default: null }) export const signedInUserSelector = selecto ...

The progression indicator on the mat-sidenav only displays halfway even when it shows 100 percent completed

I have encountered an issue with a simple mat progress bar in a mat side nav and content. When the value is set to 100, it should be completed, but it only fills halfway inside the content. Strangely, it works as expected in the side nav. https://i.sstat ...

Combining Arrays Together in TypeScript

I am in need of a solution to merge two arrays into one using TypeScript. First Array Object: export interface Item{ Label : string, Code : string, Price : number, } Second Array Object: export interface Amou ...

Switching between various conditions

Here is a sample of my component: import { Component, OnInit } from '@angular/core'; @Component({ selector: 'myApp-component', templateUrl: './myApp.component.html', styleUrls: ['./myApp.component.scss'] }) ex ...

When attempting to display the title of a route in an Angular header component, it consistently results in undefined

I am facing a seemingly simple issue. I have a header component that needs to display the title of the currently active route. To achieve this, I am injecting the ActivatedRoute into the header component and attempting to display the route title using the ...

What kind of Input field is being provided as an argument to a TypeScript function?

Currently, I am working through an Angular 2 tutorial where an input element is being passed to a function through a click event. The tutorial includes an addTodo function with the following signature: addTodo(event, todoText){ }. However, there is a warn ...

A step-by-step guide on utilizing Observables to extract JSON data from a file

Currently, I am experimenting with reading data from a JSON file that contains information about countries and states. Previously, I used a method to achieve this but now I want to switch to using httpClient for fetching the data. Below are the methods I h ...

Error: The method this._parentSubscription.unsubscribe() does not exist

When encountering an error, a TypeError is being caused within the observable. Despite removing fbLogin.unsubscribe();, the error persists and I am unsure of the root cause. My project utilizes rxjs@^5.5.2. ERROR TypeError: this._parentSubscription.unsu ...

Converting Typescript objects containing arrays into a single array

Looking for some assistance with this problem :) I am trying to convert the object into an array with the following expected result: result = [ { id: 'test-1', message: 'test#1.1' }, { id: 'test-1', mess ...