Search code examples
angularangular-material

How to trigger MatTooltip on hover on MatChips inside a disabled MatFormField?


I'm struggling to find a solution here. Basically I have a disabled FormField that is composed by a series of MatChips, each of them with a specific tooltip.

Most of the solutions that I find online suggest to move the tooltip in an outer div, that is impossible for me since I need to add them in an NgFor inside the disabled FormField.

In code:

<form [formGroup]="fruitsForm">
  <mat-form-field>
    <mat-chip-grid 
      #chipGrid aria-label="Enter fruits" 
      formControlName="fruits" <!-- This is the disabled formField in my scenario -->
   >
      @for (fruit of fruits(); track fruit) {
      <mat-chip-row>
        <div [matTooltip]="fruit.name">{{fruit.name}}</div>
      </mat-chip-row>
      }
      <input
        ...
      />
    </mat-chip-grid>
  </mat-form-field>
</form>

How would you approach the issue? What can I do to have the tooltip triggered even on a disabled chips that are disabled by the outer formfield?

A complete working example can be found here: https://stackblitz.com/edit/c2pken?file=src%2Fexample%2Fchips-input-example.html


Solution

  • The tooltip is disabled by the CSS pointer-events: none, you can just override this css with pointer-events: all !important, then it will display over the disabled chips.

      ::ng-deep .show-tool-tip-disabled .mdc-text-field--disabled {
          pointer-events: all !important;
      }
      ::ng-deep .show-tool-tip-disabled .mdc-evolution-chip--disabled, 
      ::ng-deep .show-tool-tip-disabled .mdc-evolution-chip__action:disabled {
          pointer-events: all !important;
      }
    

    Full Code:

    HTML:

    <form [formGroup]="fruitsForm">
      <mat-form-field class="example-chip-list">
        <mat-label>Favorite Fruits</mat-label>
        <mat-chip-grid
          #chipGrid
          aria-label="Enter fruits"
          formControlName="fruits"
          class="show-tool-tip-disabled"
        >
          @for (fruit of fruits(); track fruit) {
          <mat-chip-row
            (removed)="remove(fruit)"
            [editable]="true"
            (edited)="edit(fruit, $event)"
            [aria-description]="'press enter to edit ' + fruit.name"
          >
            <div [matTooltip]="fruit.name">{{fruit.name}}</div>
            <button matChipRemove [attr.aria-label]="'remove ' + fruit.name">
              <mat-icon>cancel</mat-icon>
            </button>
          </mat-chip-row>
          }
          <input
            placeholder="New fruit..."
            [matChipInputFor]="chipGrid"
            [matChipInputSeparatorKeyCodes]="separatorKeysCodes"
            [matChipInputAddOnBlur]="addOnBlur"
            (matChipInputTokenEnd)="add($event)"
          />
        </mat-chip-grid>
      </mat-form-field>
    </form>
    

    TS:

    import { LiveAnnouncer } from '@angular/cdk/a11y';
    import { COMMA, ENTER } from '@angular/cdk/keycodes';
    import {
      ChangeDetectionStrategy,
      Component,
      inject,
      signal,
    } from '@angular/core';
    import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
    import {
      MatChipEditedEvent,
      MatChipInputEvent,
      MatChipsModule,
    } from '@angular/material/chips';
    import { MatFormFieldModule } from '@angular/material/form-field';
    import { MatIconModule } from '@angular/material/icon';
    import { MatTooltipModule } from '@angular/material/tooltip';
    
    export interface Fruit {
      name: string;
    }
    
    /**
     * @title Chips with input
     */
    @Component({
      selector: 'chips-input-example',
      templateUrl: 'chips-input-example.html',
      styleUrl: 'chips-input-example.css',
      standalone: true,
      imports: [
        MatFormFieldModule,
        MatChipsModule,
        MatIconModule,
        MatTooltipModule,
        ReactiveFormsModule,
      ],
      styles: [
        `::ng-deep .show-tool-tip-disabled .mdc-text-field--disabled {
          pointer-events: all !important;
      }
      ::ng-deep .show-tool-tip-disabled .mdc-evolution-chip--disabled, 
      ::ng-deep .show-tool-tip-disabled .mdc-evolution-chip__action:disabled {
          pointer-events: all !important;
      }
      `,
      ],
      changeDetection: ChangeDetectionStrategy.OnPush,
    })
    export class ChipsInputExample {
      readonly addOnBlur = true;
      readonly separatorKeysCodes = [ENTER, COMMA] as const;
      readonly fruits = signal<Fruit[]>([
        { name: 'Lemon' },
        { name: 'Lime' },
        { name: 'Apple' },
      ]);
      readonly announcer = inject(LiveAnnouncer);
      public fruitsForm: FormGroup = new FormGroup({
        fruits: new FormControl({ value: [], disabled: true }),
      });
    
      add(event: MatChipInputEvent): void {
        const value = (event.value || '').trim();
    
        // Add our fruit
        if (value) {
          this.fruits.update((fruits) => [...fruits, { name: value }]);
        }
    
        // Clear the input value
        event.chipInput!.clear();
      }
    
      remove(fruit: Fruit): void {
        this.fruits.update((fruits) => {
          const index = fruits.indexOf(fruit);
          if (index < 0) {
            return fruits;
          }
    
          fruits.splice(index, 1);
          this.announcer.announce(`Removed ${fruit.name}`);
          return [...fruits];
        });
      }
    
      edit(fruit: Fruit, event: MatChipEditedEvent) {
        const value = event.value.trim();
    
        // Remove fruit if it no longer has a name
        if (!value) {
          this.remove(fruit);
          return;
        }
    
        // Edit existing fruit
        this.fruits.update((fruits) => {
          const index = fruits.indexOf(fruit);
          if (index >= 0) {
            fruits[index].name = value;
            return [...fruits];
          }
          return fruits;
        });
      }
    }
    

    Stackblitz Demo