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
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;
}
<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>
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;
});
}
}