Angular 5中为文本动态添加tooltip(Angular Material 2或自定义方案)
Got it, let's break down how to solve this problem. You want to dynamically add tooltips (preferably using Angular Material 2's built-in component) to specific words in text loaded from a secure server—like showing "german shepherd" when hovering over "dog" in "the dog is barking". The catch is you can't just slap a directive onto basic string interpolation, right? Let's go through the best approaches:
First off, forget using a pipe for this—pipes that return HTML strings require [innerHTML] to render, which carries XSS risks (especially with server-loaded text) and, more importantly, Angular won't compile directives like matTooltip inside raw HTML. Instead, use a dedicated component to split and render your text with proper tooltip directives.
Step 1: Create a Tooltip Text Component
This component will take your raw server string and a config object mapping words to their tooltip content, then split the text into segments to apply tooltips where needed.
import { Component, Input } from '@angular/core'; @Component({ selector: 'app-tooltip-text', template: ` <span *ngFor="let segment of textSegments"> <!-- Apply Material Tooltip to target words --> <span *ngIf="segment.isTooltip" matTooltip="{{ segment.tooltipText }}" matTooltipPosition="above" <!-- Customize position as needed --> matTooltipShowDelay="500"> <!-- Add delay if desired --> {{ segment.text }} </span> <!-- Render regular text segments --> <span *ngIf="!segment.isTooltip">{{ segment.text }}</span> </span> ` }) export class TooltipTextComponent { @Input() rawText: string; @Input() tooltipConfig: { [key: string]: string }; // Split text into tooltip and non-tooltip segments get textSegments() { if (!this.rawText || !this.tooltipConfig) { return [{ text: this.rawText || '', isTooltip: false }]; } // Regex to match any word in our tooltip config (case-insensitive) const targetWords = Object.keys(this.tooltipConfig).join('|'); const regex = new RegExp(`(${targetWords})`, 'gi'); return this.rawText.split(regex).map(segment => { const lowerSegment = segment.toLowerCase(); if (this.tooltipConfig[lowerSegment]) { return { text: segment, isTooltip: true, tooltipText: this.tooltipConfig[lowerSegment] }; } return { text: segment, isTooltip: false }; }); } }
Step 2: Use the Component in Your Template
Replace your original <p>{{ stringFromSecureServer }}</p> with this component, passing in your server text and tooltip config:
<app-tooltip-text [rawText]="stringFromSecureServer" [tooltipConfig]="{ 'dog': 'german shepherd', 'barking': 'vocalization used by canines to communicate' }"> </app-tooltip-text>
Why This Works
- Security: No
innerHTMLmeans no XSS vulnerabilities, which is critical for server-loaded content. - Full Material Tooltip Support: You can use all Material tooltip features (positioning, delays, themes, etc.) out of the box.
- Maintainable: The component encapsulates all tooltip logic, making it easy to update or reuse across your app.
If you can't use Angular Material for some reason, you can build a simple custom tooltip component with the same text-splitting logic:
import { Component, Input, HostListener } from '@angular/core'; @Component({ selector: 'app-custom-tooltip-text', template: ` <span *ngFor="let segment of textSegments"> <span *ngIf="segment.isTooltip" class="tooltip-trigger" (mouseenter)="showTooltip(segment.tooltipText, $event)" (mouseleave)="hideTooltip()"> {{ segment.text }} </span> <span *ngIf="!segment.isTooltip">{{ segment.text }}</span> </span> <!-- Custom tooltip box --> <div *ngIf="showTooltipBox" class="custom-tooltip" [style.top]="tooltipTop" [style.left]="tooltipLeft"> {{ tooltipContent }} </div> `, styles: [` .tooltip-trigger { text-decoration: underline dotted #666; cursor: help; } .custom-tooltip { position: absolute; background-color: #2d2d2d; color: #fff; padding: 6px 10px; border-radius: 4px; font-size: 0.85rem; z-index: 1000; box-shadow: 0 2px 4px rgba(0,0,0,0.2); } `] }) export class CustomTooltipTextComponent { @Input() rawText: string; @Input() tooltipConfig: { [key: string]: string }; showTooltipBox = false; tooltipContent = ''; tooltipTop = '0px'; tooltipLeft = '0px'; get textSegments() { // Same splitting logic as the Material component if (!this.rawText || !this.tooltipConfig) { return [{ text: this.rawText || '', isTooltip: false }]; } const targetWords = Object.keys(this.tooltipConfig).join('|'); const regex = new RegExp(`(${targetWords})`, 'gi'); return this.rawText.split(regex).map(segment => { const lowerSegment = segment.toLowerCase(); if (this.tooltipConfig[lowerSegment]) { return { text: segment, isTooltip: true, tooltipText: this.tooltipConfig[lowerSegment] }; } return { text: segment, isTooltip: false }; }); } showTooltip(content: string, event: MouseEvent) { this.tooltipContent = content; this.showTooltipBox = true; // Position tooltip slightly below and to the right of the mouse this.tooltipTop = `${event.clientY + 12}px`; this.tooltipLeft = `${event.clientX + 12}px`; } hideTooltip() { this.showTooltipBox = false; } }
Use it just like the Material version:
<app-custom-tooltip-text [rawText]="stringFromSecureServer" [tooltipConfig]="{ 'dog': 'german shepherd' }"> </app-custom-tooltip-text>
Quick Note on Your Original Pipe Attempt
Pipes don't work here because Angular doesn't compile Angular directives (like matTooltip) inside HTML strings rendered via [innerHTML]. Even if you tried to inject the tooltip directive into the raw HTML, it wouldn't be recognized by Angular's compiler, and you'd open yourself up to XSS attacks if the server text isn't fully sanitized.
内容的提问来源于stack exchange,提问作者don

