Search code examples
javascriptangulartypescriptangular-directiveangular8

Can't resolve all parameters in validation directive during ng build --prod


I have go through with following questions but didn't found any solution.

I have made a custom validation directive to validate unique permalink. this code working fine but when i try to create a build for production then it gives me following error:-

ERROR in : Can't resolve all parameters for UniquePermalinkValidatorDirective in E:/Manish/Projects/ampleAdmin/src/app/shared/permalink-validation.directive.ts: ([object Object], ?).

permalink-validation.directive.ts

import { Directive } from '@angular/core';
import { AsyncValidator, AbstractControl, ValidationErrors, NG_ASYNC_VALIDATORS, AsyncValidatorFn } from '@angular/forms';
import { Observable, of } from 'rxjs';
import { map } from 'rxjs/operators';
import * as qs from 'qs';
import { PageService } from '../services/page.service';
import { IPage } from '../client-schema';

export function UniquePermalinkValidator(pageService: PageService, page: IPage): AsyncValidatorFn {
  return (ctrl: AbstractControl): Promise<ValidationErrors | null> | Observable<ValidationErrors | null> => {
    if (!(ctrl && ctrl.value)) { return null; }

    const cond: any = {
      where: {
        permalink: ctrl.value
      }
    };

    if (page && page.id) {
      cond.where.id = { nin: [page.id]};
    }
    const query = qs.stringify(cond, { addQueryPrefix: true });

    return pageService.getPageCount(query).pipe(
      map(res => {
        return res && res.count ? { uniquePermalink: true } : null;
      })
    );
  };
}

@Directive({
  selector: '[appUniquePermalink]',
  providers: [{ provide: NG_ASYNC_VALIDATORS, useExisting: UniquePermalinkValidatorDirective, multi: true }]
})
export class UniquePermalinkValidatorDirective implements AsyncValidator {

  constructor(private pageService: PageService, private page: IPage) { }

  validate(ctrl: AbstractControl): Promise<ValidationErrors | null> | Observable<ValidationErrors | null> {
    return UniquePermalinkValidator(this.pageService, this.page)(ctrl);
  }
}

page.component.ts

import { Component, OnInit, TemplateRef } from '@angular/core';
import * as _ from 'lodash';
import { NotifierService } from 'angular-notifier';
import { BsModalService, BsModalRef } from 'ngx-bootstrap/modal';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { IPage } from 'src/app/client-schema';
import { Utils } from 'src/app/shared/utils';
import { PageService } from 'src/app/services/page.service';
import { UniquePermalinkValidator } from 'src/app/shared/permalink-validation.directive';

@Component({
  selector: 'app-page',
  templateUrl: './page.component.html',
  styleUrls: ['./page.component.css']
})
export class PageComponent implements OnInit {

  private notifier: NotifierService;

  pageForm: FormGroup;
  pageDetail: IPage;

  isAddFold = false;
  isEditFold = false;
  editFoldIndex = -1;

  constructor(
    private pageService: PageService,
    private notifierService: NotifierService,
    private modalService: BsModalService,
    private formBuilder: FormBuilder,
  ) {
    this.notifier = notifierService;
  }

  initPageForm() {
    this.pageForm = this.formBuilder.group({
      name: ['', [Validators.required, Validators.minLength(2), Validators.maxLength(250)]],
      permalink: ['', [Validators.required], UniquePermalinkValidator(this.pageService, this.pageDetail)],
      folds: [
        []
      ],
      data: null,
      status: true
    });
  }
}

I am using single form for Add/Edit page so i have to require records details to allow permalink on editing a page.

is there any way to pass current page details to directive?


Solution

  • Given

    export function UniquePermalinkValidator(pageService: PageService, page: IPage): AsyncValidatorFn {
      // ...
    }
    

    And given

    @Directive({
      selector: '[appUniquePermalink]',
      providers: [{ provide: NG_ASYNC_VALIDATORS, useExisting: UniquePermalinkValidatorDirective, multi: true }]
    })
    export class UniquePermalinkValidatorDirective implements AsyncValidator {    
      constructor(private pageService: PageService, private page: IPage) {}
      // ...
    }
    

    And given that IPage is defined solely by

    export interface IPage {
      id: number;
      // ...
    }
    

    Then UniquePermalinkValidatorDirective will not work by definition, failing in the manner described.

    An interface defines something only in the type space, not in the value space and therefore has no runtime manifestation whatsoever. This means it cannot be used in a value position.

    In essence, Angular's dependency injection system reads the type of constructor parameters and, when there is a correspondingly named declaration in the value space, it will use that correspondingly named value as the injection token.

    For example, the following

    import {Injectable} from '@angular/core';
    
    @Injectable() export class Service {
        constructor(http: Http) {}
    }
    

    can also be written

    import {Inject} from '@angular/core';
    
    export class Service {
        constructor(@Inject(Http) http: ThisTypeIsArbitraryWithRespectToInjection) {}
    }
    

    which means the same thing

    Notice how Http is pass as an argument to Inject. But Inject(IPage), where IPage is an interface, is malformed.

    A primary purpose of @Inject(ProviderToken) is to allow one to inject a provider orthogonally to the type of the decorated paramater in cases such as yours.

    Therefore, you need something like

    constructor(@Inject(pageToken) page) {}
    

    Which means one needs to define a token and use it to register a provider that can be injected.

    One can, and should, still write

    constructor(@Inject(pageToken) page: IPage) {}
    

    In order to give the parameter a type but the type is unrelated to the value injected for the parameter.

    For example

    import {InjectionToken, NgModule} from '@angular/core';
    
    export const pageToken = new InjectionToken<IPage>('Page');
    
    @NgModule({
      providers: [
        {
          provide: pageToken,
          useFactory: functionReturningAPage
        }
      ]
     }) export class // ...