如何将服务注入 angular 13 中的指令

How to inject a service into a directive in angular 13

我有一个名为 UniqueVersionNumberDirective 的指令,我正在使用它来验证响应式表单,因为除了表单控件的值之外我还需要其他信息,而且我可以从路由参数中获取此信息,但我不能'无法注入到 HttpClient,我还尝试注入另一个可以帮助我的服务,但这也不起作用,并且控制台向我抛出此错误:

Cannot read properties of undefined (reading 'http')

这是我的指令代码:


import { Directive, forwardRef, Input, OnInit } from '@angular/core';
import { NG_ASYNC_VALIDATORS, AbstractControl, FormControl, AsyncValidator } from '@angular/forms';
import { catchError, map, Observable, of } from 'rxjs';
import { ActivatedRoute } from '@angular/router';
import { HttpClient } from '@angular/common/http';


@Directive({
  // tslint:disable-next-line:directive-selector
  selector: '[asyncValidator][formControlName], [asyncValidator][ngModel]',
  providers: [
    {
      provide: NG_ASYNC_VALIDATORS,
      useExisting: forwardRef(() => UniqueVersionNumberDirective),
      multi: true,
    }
  ],
})
export class UniqueVersionNumberDirective implements AsyncValidator, OnInit {
  @Input() asyncValidator: { coupledControl: AbstractControl };
  createdBy: string;
  projectName: string;
  constructor(private route: ActivatedRoute, private http: HttpClient) {}
  ngOnInit(): void {
    this.projectName = this.route.snapshot.data['projectName'];
  }
  validate(control: FormControl): Promise<{ [key: string]: any }> | Observable<{ [key: string]: any }>{
    const {value} = control;
    return this.http.post<{ available: boolean }>(
      `http://localhost:5000/api/version/check/available/${this.projectName}`,
      { value }
    ).pipe(
      map(() => {
        return null;
      }),
      catchError((err) => {
        if (err) {
          return of({nonUniqueVersionNumber: true})
        }
        return of({noConnection: true})
      })
    );
  }
  
}
```

正如@jhyot 在他的评论中提到的,它使用 bind 工作,但没有使用 HttpClient,我使用另一个服务做了一个解决方法,我在 VersionsService 中创建了一个函数来处理请求,这实际上是一种更有效的方法,所以这里是使用另一个服务的指令的新代码,即 VersionsService :

import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
@Injectable({
  providedIn: 'root',
})
export class VersionsService {
  constructor(private http: HttpClient) {}

  versionNumberAvailable(
    createdBy: string,
    projectName: string,
    versionNumber: string
  ): Observable<{ available: boolean }> {
    return this.http.post<{ available: boolean }>(
      `http://localhost:5000/api/version/check/available/${createdBy}/${projectName}`,
      { versionNumber }
    );
  }
}

这是 UniqueVersionNumberDirective 的编辑代码:


import { Directive, forwardRef, Input, OnInit } from '@angular/core';
import { NG_ASYNC_VALIDATORS, AbstractControl, FormControl, AsyncValidator } from '@angular/forms';
import { catchError, map, Observable, of } from 'rxjs';
import { ActivatedRoute } from '@angular/router';
import { VersionsService } from '../services/versions.service';

@Directive({
  // tslint:disable-next-line:directive-selector
  selector: '[asyncValidator][formControlName], [asyncValidator][ngModel]',
  providers: [
    {
      provide: NG_ASYNC_VALIDATORS,
      useExisting: forwardRef(() => UniqueVersionNumberDirective),
      multi: true,
    }
  ],
})
export class UniqueVersionNumberDirective implements AsyncValidator, OnInit {
  @Input() asyncValidator: { coupledControl: AbstractControl };
  createdBy: string;
  projectName: string;
  constructor(private route: ActivatedRoute, private versionsService: VersionsService) {}
  ngOnInit(): void {
    this.createdBy = this.route.snapshot.data['createdBy'];
    this.projectName = this.route.snapshot.data['projectName'];
  }
  validate(control: FormControl): Promise<{ [key: string]: any }> | Observable<{ [key: string]: any }>{
    const {value} = control;
    return this.versionsService.versionNumberAvailable(this.createdBy, this.projectName, value).pipe(
      map(() => {
        return null;
      }),
      catchError((err) => {
        if (err) {
          return of({nonUniqueVersionNumber: true})
        }
        return of({noConnection: true})
      })
    );
  }
  
}

这是表单验证的代码, 请注意,我在此组件中注入了 VersionsService,以便在绑定它们时可以获取指令

constructor(
    public fb: FormBuilder,
    private store: Store<AppState>,
    private uniqueVersionNumber: UniqueVersionNumberDirective,
    private route: ActivatedRoute,
    private versionsService: VersionsService
    ) {
    this.versionForm = this.fb.group({
      number: ['', Validators.required, uniqueVersionNumber.validate.bind(this)],
    });

在你自己的回答之后,我想我现在明白了整个问题。我认为您正在以一种迂回的方式绑定 this,虽然它有效,但在幕后它做了一些不同于最初看起来的事情。 (因为绑定 this 是组件而不是指令)。

只有在使用 template-driven forms 时才需要将验证器声明为指令。那么正确的this也自动绑定了,不需要额外做任何事情。请参阅 link.

中的示例

但实际上您在那里使用的是反应式形式,它与指令的绑定方式与 template-driven 不同。在那种情况下,您不应该直接使用 validate 方法引用,而应该使用另一个 returns AsyncValidatorFn

方法引用

将以下内容添加到您的指令中:

validator(): AsyncValidatorFn {
  return (control: FormControl) => this.validate(control);
}

并在您的组件中使用它:

constructor(
    public fb: FormBuilder,
    private store: Store<AppState>,
    private uniqueVersionNumber: UniqueVersionNumberDirective,
    private route: ActivatedRoute
    ) {
    this.versionForm = this.fb.group({
      number: ['', Validators.required, uniqueVersionNumber.validator()],
    });