0
votes

How can I create a custom angular directive to show and hide child element when clicked on neighbour element?

So when clicked on the h2 I want to display custom_elem using a custom directives.

Remember that the div is repeating with ngFor. So the instance of div should only show the child element (custom_elem) when clicked on h2 while the child element(custom_elem) of other instances of the div stay hidden .

<div *ngFor="let item of items"> 
 <h2 (click)="" >item.name</h2>   // if I click here it will pass show or hide (true or false) values to the directive. 
 <custom_elem>somedata</custom_elem>  //show these (then the directive will handle the display properties of these child elements)
<div>

see that I don't; want to dynamically add anything new.

The ngfor is already looping(at the beginning) itself. Only What I want is that I just want to toggle the display of the (custom_elem) when I click on the h2 (using a custom directive).

We want a custom directive. so that when I click on the h2 I can pass true or false values to the directive & then the directive will handle the display properties of (custom_elem)

this way the show or hide properties are bound locally with the instance of the corresponding element. & not connected to the component.ts.

UPDATE div is repeating with ngFor. h2 will be always visible. when clicked on h2, custom_element display turns on and off.

This is the final result needed. When I click on h2 I want to display the corresponding custom_elem (which is neighbour of h2).

2
No. Not like this. I want to handle the show or hide properties inside the directive itself so that each instance of the div is detached from the component. - mx_code
We dont want to store any properties regarding the display of the elements inside the component. - mx_code
If you hide all children, the component will collapse in a way that you won't have any clickable area to use in order to click again and restore the visibility. How do you imagine this behavior? - julianobrasil
Ok . Sorry. Then click on h2 I want to display the custom_elem. - mx_code

2 Answers

1
votes

Well, it'll work as a start point, but you'll have to take care of those several special cases that will occur:

  1. Addition/removal of elements
  2. header without children
  3. Make it possible to specify the header element type

Basically, an initial version, with no optimizations, of what you are asking for is shown on this Stackblitz demo (because there are too many lines of code to post it here - but I'll try to highlight some points).

the directive is named after appHideChildren and it can be used like this:

<hello [appHideChildren]="true"></hello>

By doing that all the children of hello component, but the h2 elements, will be hidden (because it is being set to true). It is just an initial state of the children. After the component initialization, the clicks on the headers will take care of toggling the display.

What was done is find all the direct children of the host component:

<!-- $el is the host element -->
this._children = $el.querySelectorAll(`${$el.tagName}>*`);

Then, event listeners are added to the headers (h2 elements) and the following siblings of each header are found and a reference to them are kept. The event listener iterates through all the children of a clicked header, toggling its status. The previous state of the display property is stored to be restored when the component must be shown.

1
votes

this doesn't feel like something that needs a custom directive to me:

<div *ngFor="let item of items"> 
 <h2 (click)="item.showData = !item.showData" >item.name</h2>
 <custom_elem *ngIf="item.showData">somedata</custom_elem> 
<div>

the above simple bit of code using stock directives seems to cover the requirements... if your goal is to manipulate visibility instead of adding / removing, you could just use ngClass instead with a class to apply the needed styles... is there a specific reason you're after something more custom than this? I'm generally in favor of dry reusable directives, but seems overkill in this instance.

Standard front end best practices are that your data model and your view model are related but different things. Every data model needs to be represented in a view, and the data model is almost never sufficient to model the view, as the view has things like hide / show toggles or display values that the data model isn't concerned with. this is normal and expected.

if you had an interface for your data model like:

interface MyItem {
  id: number;
  user: {
    firstName: string,
    lastName: string
  };
  data: any;
}

and you want to show the full name and a toggle of open / closed, maybe the item can even be in a selected state, you need to extend this model, as it is not sufficient for the given view of this data.

interface MyItemVm extends Item {
  displayName: string;
  selected: boolean;
  showData: boolean;
}

and you'd build your view model with a function of some kind:

this.itemVm = items.map(i => ({
  ...i, 
  ...{displayName: `${i.user.firstName} ${i.user.lastName}`, selected: false, showData: false }
}));

now your data is properly modeled for the view and easy to translate into a template with useful properties for the view.