通过 DI 装饰器优雅地限制指令在特定指令上使用

Gracefully restrict a directive to be used on a specific directive via DI decorators

在我的应用程序中,我引入了一个映射器指令,负责从 class 填充表单值。旨在用于:

<form [formGroup]="form" [myItemMapper]="item$ | async">

不打算在任何其他元素上,例如表单数组或表单控件。

为了实现这个意图,我将选择器限制为 [formGroup][myItemMapper],这是可行的,即。如果你把这个指令放在任何其他元素上,你会得到一个很大的 Webpack 编译覆盖层,上面有错误说

`NG8002: Can't bind to 'myItemMapper' since it isn't a known property of '<whatever>'.

目标达成。

但我想知道为什么以下代码不起作用:

constructor(
  @Self() fg: FormGroupDirective,
) {
  if(!fg){
    console.error("'myItemMapper' can only be used on a FormGroup directive.")
  }
}

也尝试了 @Host() - 它基本上不关心指令在哪个元素上,当它被放置在 formControlName 上时它也能够找到 formGroup -还有一点不明白。

那么 - 第一个选项是我能为指令的消费者提供的最佳选择吗?

添加 [formGroup][myItemMapper] 作为指令的选择器时,您是在告诉编译器 MyItemMapperDirective 必须与 FormGroupDirective 一起使用。这可以在编译时强制执行。

通过添加 FormGroupDirective 作为构造函数参数,您要求依赖注入容器将其作为依赖项找到。由于依赖项会在 运行 时间受到其他组件的影响,因此所需的依赖项是否存在只能在 运行 时间确定,因此您会看到错误消息。

是的,第一个选项是您最好的选择。

But I was wondering why the following code didn't work:

instead of a nice informative console.error, I got a nasty "No provider for formGroupDirective found" runtime error with no explanation. For a consumer it is a misleading experience as if they didn't import ReactiveFormsModule at all.

当无法解析依赖关系时,Angular 将抛出错误“找不到 {token} 的提供程序”。这是预期的行为。

如果您想打印 console.error 消息而不是抛出错误,您还应该使用 @Optional() 装饰器作为:

constructor(
  @Self() @Optional() fg: FormGroupDirective,
) {
  if(!fg){
    console.error("'myItemMapper' can only be used on a FormGroup directive.")
  }
}

来自docs

@Optional() allows Angular to consider a service you inject to be optional. This way, if it can't be resolved at runtime, Angular resolves the service as null, rather than throwing an error.


现在进入下一个问题:

Tried @Host() too - it basically didn't care which element the directive was on, it was able to find formGroup also when it was placed on a formControlName- which is also something I don't understand.

当使用@Host()时,Angular会从当前Injector开始依赖解析,并会在Injector层级中继续搜索,直到到达宿主元素 当前组件

假设您的 SomeComponent:

有以下模板
<form [formGroup]="form">
  <label for="firstName">First Name</label>
  <input id="firstName" type="text" formControlName="firstName" yourDirective />
</form>

然后在指令构造函数中使用 @Host(),Angular 将尝试按如下方式解析依赖关系:

  1. yourDirective 的提供程序数组,后跟 FormControlName 指令的提供程序数组
  2. 供应商数组 FormGroupDirective
  3. viewProviders 数组 SomeComponent