Search code examples
angularangular-directiveangular4-forms

Angular 4 Directive on Input element with data binding


I'm trying to write an attribute directive for input elements in my forms which validates the given username if it's available or not.

I have 2 problems doing so.

1) I want to make this directive reusable by allowing it to receive the API route it should be validating usernames with. Here's what I have and as you can see it doesn't accept any input (doesn't have bindings) and it works perfectly.

import { Directive, forwardRef } from '@angular/core';
import { AbstractControl, Validator, NG_ASYNC_VALIDATORS } from '@angular/forms';
import { HttpClient } from '@angular/common/http';
import { environment } from '../../../../environments/environment';
import { Observable } from 'rxjs/Observable';

@Directive({
  selector: '[uniqueCompanyUsername]',
  providers: [
    {
      provide: NG_ASYNC_VALIDATORS,
      useExisting: forwardRef(() => CompanyUsernameValidatorDirective), multi: true
    },
  ]
})
export class CompanyUsernameValidatorDirective implements Validator {
  constructor(private http: HttpClient) {
  }

  validate(username: AbstractControl) {
    return Observable.timer(1000).switchMap(() => {
      return this.http.post(environment.apiEndpoint + '/company/username-check', {
        username: username.value
      }).map((result: any) => result.success ? null : { uniqueCompanyUsername: false });
    });
  }
}

Once I add @Input('uniqueCompanyUsername') route: string; to it and try to take the route via the directive itself it gives me this error:

Can't bind to 'uniqueCompanyUsername' since it isn't a known property of 'input'.

Here's what I wrote in my directive controller:

import { Directive, forwardRef, Input } from '@angular/core';
import { AbstractControl, Validator, NG_ASYNC_VALIDATORS } from '@angular/forms';
import { HttpClient } from '@angular/common/http';
import { environment } from '../../../../environments/environment';
import { Observable } from 'rxjs/Observable';

@Directive({
  selector: '[uniqueCompanyUsername]',
  providers: [
    {
      provide: NG_ASYNC_VALIDATORS,
      useExisting: forwardRef(() => CompanyUsernameValidatorDirective), multi: true
    },
  ]
})
export class CompanyUsernameValidatorDirective implements Validator {
  constructor(private http: HttpClient) {
  }

  @Input('uniqueCompanyUsername') route: string;

  validate(username: AbstractControl) {
    return Observable.timer(1000).switchMap(() => {
      return this.http.post(`${environment.apiEndpoint}${this.route}`, {
        username: username.value
      }).map((result: any) => result.success ? null : { uniqueCompanyUsername: false });
    });
  }
}

Here's my template:

<input name="companyUsername" [(ngModel)]="company.username" id="input-username" #companyUsername="ngModel" class="form-control" placeholder="Company Username"
                [class.form-control-danger]="companyUsername.invalid && companyUsername.touched" [minlength]="3" [maxlength]="50" [required]="true"
                [uniqueCompanyUsername]="/company/username/" autofocus>

I'm frustrated and I don't simply understand what's wrong here!


Solution

  • Your attribute name needs to match your Input decorator name.

    Change this:

    @Input('uniqueCompanyUsername') route: string;
    

    to this:

    @Input() uniqueCompanyUsername: string;
    

    and remember to also change "this.route"s in your component to "this.uniqueCompanyUsername"s.

    More info on Avoid aliasing inputs and outputs.