使用指令在 Angular 中创建一个超级简单的 Badge 组件。

2025-06-11

使用指令在 Angular 中创建一个超级简单的 Badge 组件。

所以我又回来写一篇小文章,展示如何利用指令在 Angular 应用中实现神奇的功能。指令非常强大。一旦你了解了它,就可以开始利用它将命令式逻辑迁移到指令中。

我们要建造什么?

Bade 组件使用指令
我们今天要构建的组件是一个非常简单的徽章组件。徽章几乎存在于我能想到的所有 UI 框架中。
徽章是一种状态描述符,通常显示诸如未读电子邮件/通知的数量,或者选中要删除的项目数量之类的信息。

我们将使用Angular Material所使用的一种模式。我写这篇文章的目的是向你展示这些库是如何实现这一点的。我们只是使用这些库提供的功能,而且大多数时候我们并不清楚它的具体工作原理。

读完这篇文章后,你肯定会想,制作很酷的东西总是这么容易吗?

为什么要有指令?

你可能会问这个问题,这很合理。为什么我们不能只写一个简单的div样式,然后在组件中相应地添加样式呢?当然可以。
但如果需要在多个组件中实现相同的样式,最好将其从组件中抽取出来,单独写一个组件。
这样我们的组件代码看起来会简洁得多。

指令可以轻松添加到 HTML 元素中,并且可以与模板流完美配合。

<button class="button button-with-badge">
  <p>My Button</p>
  <span class="badge some-class">18</span>
</button>
Enter fullscreen mode Exit fullscreen mode

或者

<button class="button" badge="18">My Button</button>
Enter fullscreen mode Exit fullscreen mode

您认为哪一个更好?

今天,我们将构建badge一个指令,在添加的元素顶部添加一个小徽章。

规划

因此,我们理想情况下希望指令执行的操作是添加一个带有徽章内容的新元素,然后将其定位absolute到宿主元素。

我们需要动态创建一个元素并将其附加到宿主元素。 Angular 有一个东西可以做这种事。它就是Renderer2

Renderer2是一个类,它为我们提供了一种优雅的方式来操作元素,而无需直接接触 DOM。

虽然我们可以使用 Rendered2,但我最近得知 Material 团队也正在放弃它,转而使用原生方法。我们可以Document通过注入DOCUMENTAngular 提供的 token 来访问它。

import { DOCUMENT } from "@angular/common";
constructor(@Inject(DOCUMENT) private document: Document){}
Enter fullscreen mode Exit fullscreen mode

这将使我们能够访问该Document对象。

因此,当我们发现badge指令附加到某个元素时,我们会创建一个span元素,然后将其附加到宿主元素。很简单,不是吗?

现在让我们看看如何编写代码!

徽章指令

与往常一样,我们首先创建一个指令,然后为该指令创建一个模块。该模块将声明并导出我们的指令。因此,无论我们需要在哪里使用这个指令,只需导入BadgeModule即可。

import { CommonModule } from "@angular/common";
import { NgModule } from "@angular/core";
import { Badge } from "./badge.directive";

@NgModule({
  declarations: [Badge],
  imports: [CommonModule],
  exports: [Badge]
})
export class BadgeModule {}
Enter fullscreen mode Exit fullscreen mode

以下是我们将为该指令提供的一些自定义选项:

  • 尺寸
  • 徽章位置
  • 颜色变体
  • 自定义类
@Directive({
  selector: "[badge]"
})
export class Badge implements OnChanges, OnDestroy {
  @Input() badge = null;
  @Input() size: BadgeSizes = "medium";
  @Input() position: BadgePositions = "top-right";
  @Input() customBadgeClasses: string | null = null;
  @Input() variant: BadgeVariants = "secondary";

  badgeElement: HTMLElement | null = null;

  constructor(
    @Inject(DOCUMENT) private document: Document,
    private elRef: ElementRef<HTMLElement>
  ) {}
  ngOnChanges(changes: SimpleChanges): void {
    if ("badge" in changes) {
      const value = `${changes.badge.currentValue}`.trim();
      if (value?.length > 0) {
        this.updateBadgeText(value);
      }
    }
  }

  ngOnDestroy() {
    if (this.badgeElement) {
      this.badgeElement.remove();
    }
  }

  private updateBadgeText(value: string) {
    if (!this.badgeElement) {
      this.createBadge(value);
    } else {
      this.badgeElement.textContent = value;
    }
  }

  private createBadge(value: string): HTMLElement {
    const badgeElement = this.document.createElement("span");
    this.addClasses(badgeElement);
    badgeElement.textContent = value;
    this.elRef.nativeElement.classList.add("badge-container");
    this.elRef.nativeElement.appendChild(badgeElement);
    return badgeElement;
  }

  private addClasses(badgeElement: HTMLElement) {
    const [vPos, hPos] = this.position.split("-");
    badgeElement.classList.add("badge", vPos, hPos);
    if (this.customBadgeClasses) {
      const customClasses = this.customBadgeClasses.split(" ");
      badgeElement.classList.add(...customClasses);
    }
    badgeElement.classList.add(this.variant);
    badgeElement.classList.add(this.size);
  }
}
Enter fullscreen mode Exit fullscreen mode

代码分解

Inputs一旦我们在组件上设置了所有需要的内容,我们就会创建一些函数来创建span元素并将其附加到主机。

createBadge()该函数用于创建我们的徽章并将其附加到主机。

const badgeElement = this.document.createElement("span");
Enter fullscreen mode Exit fullscreen mode

createElement()我们使用( ref ) 方法创建 span 元素。然后,我们为 span 添加一些类,以便稍后为其添加样式。
我们使用classList.add()( ref ) 方法来实现这一点。

badgeElement.textContent = value;
Enter fullscreen mode Exit fullscreen mode

这会将文本设置在跨度内,因此我们将得到如下内容:

<span class="badge top right primary medium">20</span>
Enter fullscreen mode Exit fullscreen mode

appendChild()现在我们使用(ref )方法将此跨度附加到宿主元素

this.elRef.nativeElement.appendChild(badgeElement);
Enter fullscreen mode Exit fullscreen mode

好了!我们成功创建了一个 span 元素,并将其附加到我们的主机元素。

附在主持人身上的徽章

造型

现在,您可以根据自己的喜好设置徽章的样式。我们为所有输入元素添加了类名,例如sizevariantposition等。这样,您可以轻松自定义样式。
此外,我们还badge-container为宿主元素添加了一个类名,方便我们添加
position:relative其他内容。

如果您想要自定义样式,该指令也接受自定义类。您可以像这样传递它:

<p badge="12" customBadgeClasses="custom-bagde my-badge">Online</p>
Enter fullscreen mode Exit fullscreen mode
.badge-container {
  position: relative;
}

.badge {
  position: absolute;
  display: flex;
  justify-content: center;
  align-items: center;
  background-color: var(--bg-color);
  color: #fff;
  font-size: 12px;
  text-overflow: ellipsis;
  white-space: nowrap;
  overflow: hidden;
  border-radius: 50%;
  box-shadow: 0px 2px 6px -1px rgb(0 0 0 / 50%);
}
.badge.primary {
  --bg-color: var(--primary);
}

.badge.secondary {
  --bg-color: var(--secondary);
}

.badge.top {
  top: -10px;
}
.badge.bottom {
  bottom: -10px;
}
.badge.left {
  left: -10px;
}
.badge.right {
  right: -10px;
}
.badge.small {
  width: 18px;
  height: 18px;
  font-size: 10px;
}
.badge.medium {
  width: 22px;
  height: 22px;
  font-size: 11px;
}
.badge.large {
  width: 28px;
  height: 28px;
  font-size: 12px;
}
Enter fullscreen mode Exit fullscreen mode

以下是我们在模板中使用该指令的方法:

<button badge="4" size="small">Test</button>
<button 
      badge="5" 
      size="medium" 
      position="top-left"
      variation="secondary">Test</button>
Enter fullscreen mode Exit fullscreen mode

我们还确保在指令被销毁时移除该元素。
上面的代码可以优化,也可以添加新功能,就交给大家自己决定吧。我只是想展示一下我是如何做到的,希望这篇博文能够帮助大家。

代码

链接:https ://codesandbox.io/s/ng-custom-badge-native-0vq9f

链接(渲染器2):https://codesandbox.io/embed/ng-custom-badge-ene1t

与我联系

请在评论区留言,分享你的想法。
注意安全❤️

鏂囩珷鏉ユ簮锛�https://dev.to/angular/creating-a-super-simple-badge-component-in-angular-using-directives-ohc
PREV
开发人员在测试时面临的 12 个问题及其解决方法
NEXT
构建你的 Pokédex:第 2 部分 - @ngrx/entity 简介 @ngrx/entity 结论更多、更多、更多……