Angular:使用属性选择器时移除组件内部button标签的实现方案
问题:Angular组件同时支持元素与属性选择器时的DOM重复问题
我定义了如下ButtonComponent组件:
button.component.ts
@Component({ selector: 'app-button, [app-button]', templateUrl: './button.component.html', styleUrls: ['./button.component.scss'] }) export class ButtonComponent { @Output() clicked: EventEmitter<any> = new EventEmitter<any>(); }
button.component.html
<button class="btn btn-sm btn-primary" (click)="clicked.emit()"> <ng-content></ng-content> </button>
使用元素选择器<app-button>调用时,最终DOM是<app-button>包裹内部<button>的正常结构;但用属性选择器<button app-button>调用时,会出现外部<button>与组件内部<button>嵌套重复的问题。
请问是否可以检测组件的选择器使用类型,并在使用属性选择器时移除组件内部的<button>标签?
解决方案
完全可以实现,核心是检测组件当前的使用方式,再动态调整模板内容:
1. 判断宿主元素类型
在组件类中注入ElementRef,通过宿主元素的标签名判断是元素选择器还是属性选择器:
import { Component, EventEmitter, Output, ElementRef } from '@angular/core'; @Component({ selector: 'app-button, [app-button]', templateUrl: './button.component.html', styleUrls: ['./button.component.scss'] }) export class ButtonComponent { @Output() clicked: EventEmitter<any> = new EventEmitter<any>(); isElementSelector = false; constructor(private el: ElementRef) { // 对比宿主元素标签名,判断使用的选择器类型 this.isElementSelector = this.el.nativeElement.tagName.toLowerCase() === 'app-button'; } }
2. 动态控制模板渲染
根据isElementSelector的值,决定是否渲染内部<button>标签:
<ng-container *ngIf="isElementSelector; else useHostButton"> <button class="btn btn-sm btn-primary" (click)="clicked.emit()"> <ng-content></ng-content> </button> </ng-container> <ng-template #useHostButton> <ng-content></ng-content> </ng-template>
3. 优化方案:直接绑定宿主元素样式与事件
如果用属性选择器,更优雅的方式是把按钮的样式和事件直接绑定到宿主元素上,彻底避免嵌套:
import { Component, EventEmitter, Output, ElementRef, HostBinding, HostListener } from '@angular/core'; @Component({ selector: 'app-button, [app-button]', templateUrl: './button.component.html', styleUrls: ['./button.component.scss'] }) export class ButtonComponent { @Output() clicked: EventEmitter<any> = new EventEmitter<any>(); isElementSelector = false; // 给宿主元素绑定按钮样式类 @HostBinding('class.btn') btnClass = true; @HostBinding('class.btn-sm') btnSmClass = true; @HostBinding('class.btn-primary') btnPrimaryClass = true; constructor(private el: ElementRef) { this.isElementSelector = this.el.nativeElement.tagName.toLowerCase() === 'app-button'; } // 给宿主元素绑定点击事件 @HostListener('click') onHostClick() { this.clicked.emit(); } }
简化后的模板:
<ng-content></ng-content>
这样两种调用方式的DOM结构都正常:
- 使用
<app-button>时,宿主是<app-button>元素,通过HostBinding赋予按钮样式 - 使用
<button app-button>时,宿主本身就是<button>,直接继承样式和点击事件
内容的提问来源于stack exchange,提问作者Anuj TBE
相关产品推荐
相关产品推荐

