Search code examples
angularangular-directive

How can I bind to a directive input from within another directive?


I am trying to simplify my templating process a little by compressing a couple of validation inputs into a directive I can place on an <input> element that needs them.

The input in question would look like this:

<input [(ngModel)]='options.org_name' required id="org_name" type="text" nbInput fullWidth
                placeholder="What is your organization called?"
                [appValidating]='this'
                minlength="3" maxlength="40" />

where my directive is [appValidating]. The code for appValidating is this:

@Directive({
  selector: '[appValidating]'
})
export class ValidatingDirective {

  @Input('appValidating') validator: IValidatingComponent;
  @Input() id: string;

  @HostBinding('status')
  get status() {
    return this.validator.validity[this.id] === true ? null : 'danger';
  }

  @HostListener('input', ['$event'])
  onInput(event: any) {
    this.validator.checkValidity(this.id, event);
  }

  constructor() { }

}

My problem is that it's not letting me use @HostBinding on 'status', which is an input of the nbInput directive that also sits on the element.

Uncaught (in promise): Error: Template parse errors: Can't bind to 'status' since it isn't a known property of 'input'.

Is there a good way for me to bind to that directive's input from within my directive?


Solution

  • First, you can set the attribute with HostBinding, but that won't work in the end for what you need

    @HostBinding('attr.status') // this will set an attribute on the element
    

    the reason this won't work for you is that it isn't an angular aware attribute that you are setting, so it has no way to bind to the @Input of the nbInput directive.

    I thought about this for a bit, and tried out several solutions to the problem, and finally settled on this one, but admittedly for it to work you might need to have access and ability to change both directives.

    First Directive

    @Directive({
        selector: '[firstDirective]',
    })
    export class FirstDirective {
        @Input('status') private status: boolean;
    
        @HostListener('click', ['$event'])
        public onHostClicked(event: MouseEvent): void {
            event.preventDefault();
            event.stopImmediatePropagation();
            event.stopPropagation();
            console.log('first directive: ', this.status);
        }
    }
    

    the first directive takes in a status Input and onClick of host element logs out what the status is

    Second Directive

    @Directive({
        selector: '[secondDirective]',
    })
    export class SecondDirective {
        @Input('status') private status = false;
        @Output('statusChange') private statusChangeEvent = new EventEmitter(
            this.status,
        ); // an output event named as attributeNameChange is how you tell a two way bound attribute that it's value has changed
    
        @HostListener('click', ['$event'])
        public onHostClicked(event: MouseEvent): void {
            event.preventDefault();
            event.stopImmediatePropagation();
            event.stopPropagation();
    
            this.status = !this.status; // toggle the boolean
            this.statusChangeEvent.emit(this.status); // emit that it changed
        }
    

    the second directive takes in a status Input and emits a statusChange Output and onClick of host element flips the value of status and emits that it changed.

    Component

    @Component({
        template: '<button firstDirective secondDirective [(status)]="status">Change Status</button>',
    })
    export class Component {
        status = true; // the value in the implementing component that is being toggled
        constructor() {}
    }
    

    So in the example above, what is happening is there is a value that is set in the implementing component that both directives can access, and it acts as a passthrough between the two of them. Whenever the second directive sees a click, it flips the status boolean and the first directive sees that change.