如何将服务注入 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()],
});
我有一个名为 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()],
});