Communication between directives

Asked

Viewed 136 times

0

Next guys, I have a directive in Angular as the code below:

import {Directive, HostBinding, HostListener} from '@angular/core';

@Directive({
    selector: '[borders]'
})
export class BordersDirective {

    clicks = 0;

    @HostBinding('style.border') border: string;

    @HostListener('click') click() {
        if (this.clicks === 0) {
            this.border = '2px solid #2d88c7';
            this.clicks++;
        } else {
            this.border = '1px solid #dee2e6';
            this.clicks = 0;
        }
    }

    constructor() {
    }

}

then I have two tags p with the directive:

<p borders>Elemento 1</p>
<p borders>Elemento 2</p>

When I click on the first element it activates and then I click again it changes the edge, exactly as it was supposed to happen. The problem comes now, when I click on the second element I would like the one of the first element to disable or execute the else.

Does anyone know how I fix?

  • 1

    I would say that the simplest way is not to use the directive, if you need to manage the state of multiple components, it needs to be done in a parent component, so pass this logic to the component that renders the elements. But it should be possible to do it with directives, probably something like ngSwitch, where you have a parent directive and other children and somehow communicate (but I can’t tell you what that shape is)

1 answer

1


It is not possible to communicate through events, which would be the most natural way:

<div appFather>
  <button appChild>Child 1</button>
  <button appChild>Child 2</button>
</div>

The son would issue an event

@Directive({
  selector: '[appChild]'
})
export class ChildDirective {

  @Output('customClick')
  private customClickEvent = new EventEmitter();

  constructor() { }

  @HostListener('click')
  private click() {
    console.log('child emit');
    this.customClickEvent.emit();
  }

}

And Dad would hear this event

@Directive({
  selector: '[appFather]'
})
export class FatherDirective {

  constructor() { }

  @HostListener('customClick')
  private click() {
    console.log('father receives');
  }

}

Unfortunately, that doesn’t work

But it is possible to do what you want through a service that would control everything:

@Injectable({
  providedIn: 'root'
})
export class D2DService {

  private static lastClicked;

  private static subject = new Subject();

  constructor() { }

  click(key) {
    D2DService.lastClicked = key;

    D2DService.subject.next();
  }

  isLastClicked(key) {
    return D2DService.lastClicked === key;
  }

  subscribe(listener) {
    D2DService.subject.subscribe(listener);
  }

}

It is important that data is stored in a static variable because this service will be injected into multiple directives, if not each directive will have a service that accesses different data

The directive will use this service to change which state should be applied to the component:

@Directive({
  selector: '[appWithService]'
})
export class WithServiceDirective {

  @Input('key')
  private key;

  @HostBinding('style.background-color')
  private backgroundColor = null;

  constructor(private service: D2DService) {
    this.service.subscribe(() => this.backgroundColor = service.isLastClicked(this.key) ? 'yellow' : null);
  }

  @HostListener('click')
  private click() {
    console.log('clicked');
    this.service.click(this.key);
  }

}
<button appWithService [key]="'any identifier'">Child 1</button>
<button appWithService [key]="2">Child 2</button>

This can be improved by automatically creating the directive keys, adding a value to serve as a container so that different groups can be used at the same time

There are other ways to do it, but the idea is to centralize the state (data) at a point outside the directive that should consult this state to know what should be done

Another interesting way to do it, using only directives, is to create a parent directive that works as a service

@Directive({
  selector: '[appFatherWithHost]'
})
export class FatherWithHostDirective {

  private lastClicked;

  private subject = new Subject();

  constructor() { }

  click(key) {
    this.lastClicked = key;

    this.subject.next();
  }

  isLastClicked(key) {
    return this.lastClicked === key
  }

  subscribe(listener) {
    this.subject.subscribe(listener);
  }

}

And import in child directive through annotation Host

@Directive({
  selector: '[appChildWithHost]'
})
export class ChildWithHostDirective {

  @Input('key')
  private key;

  @HostBinding('style.background-color')
  private backgroundColor = null;

  constructor(
    @Host() private father: FatherWithHostDirective
  ) {
    father.subscribe(() => this.backgroundColor = father.isLastClicked(this.key) ? 'yellow' : null);
  }

  @HostListener('click')
  private click() {
    console.log('child clicked');
    this.father.click(this.key);
  }

}

The operation is the same, but in this way it already separates into isolated groups:

<div appFatherWithHost>
  <button appChildWithHost [key]="'any identifier'">Child 1.1</button>
  <button appChildWithHost [key]="2">Child 1.2</button>
</div>
<div appFatherWithHost>
  <button appChildWithHost [key]="'any identifier'">Child 2.1</button>
  <button appChildWithHost [key]="2">Child 2.2</button>
</div>

Browser other questions tagged

You are not signed in. Login or sign up in order to post.