Home > database >  How to inject a service into a directive in angular 13
How to inject a service into a directive in angular 13

Time:03-26

I have a directive called UniqueVersionNumberDirective, and I'm using it to validate a reactive form because I need other information besides the value of form control, And I can get this information from the routing params, But I can't get to inject to HttpClient, I also tried to inject another service which can help me out, but that didn't work either, and console throws me this error :

Cannot read properties of undefined (reading 'http')

This is the code of my directive :


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})
      })
    );
  }
  
}
```

CodePudding user response:

It worked using bind as @jhyot mentioned in his comment, but not using HttpClient, I made a workaround using another Service, I created a function in VersionsService to handle the request which is actually a more efficient way, so here's the new code of the directive using another service, which is 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 }
    );
  }
}

Here's the edited code of 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})
      })
    );
  }
  
}

And here's the code of form validation, Please note that I injected VersionsService in this component so that the directive can get when I bind them

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)],
    });

CodePudding user response:

After your own answer I think I now understand the full problem. I think you are binding this in a bit of a roundabout way, and while it works, behind the scenes it does something else than what it at first looks like. (since the bound this is the component and not the directive).

Declaring the Validator as a Directive is only necessary if you are using template-driven forms. Then the correct this is also automatically bound, without doing anything additionally. See the example in the link.

But you are actually using reactive forms there, which don't bind the directive in the same way as the template-driven. In that case you shouldn't use the validate method reference directly, but another one that returns AsyncValidatorFn

Add the following to your Directive:

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

And use it in your component:

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