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>
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)– Costamilam