Angular Directive not working with *ngIf. Calling directive from component
Asked Answered
F

2

4

I have two DIVs - one, that is always visible, and another one, that gets displayed after I click a button (it is toggable):

<div>
  <div>
    <small>ADDRESS</small>
    <p [appQueryHighlight]="query">{{ address}}</p>
  </div>
</div>

<div *ngIf="showDetails">
  <div>
    <small>NAME</small>
    <p [appQueryHighlight]="query">{{ name}}</p>
  </div>
</div>

My custom directive works well on the first DIV (it changes the color of letters inside the paragraph that match the query entered in an "input"), but does not work on the second DIV.

Here is an example of how "Test address" looks like when query is "addr":

Test address
where bold text is for example red-colored text.

But when I have name John, and query is "Joh", it should also be colored once shown with the button, but it is not.

As I understand, I need to re-run the directive after I click and toggle the second div. Also, probably I have to do this with delay, so it has time to be shown. How can I do this?

Felizio answered 15/1, 2018 at 16:10 Comment(15)
You're doing something wrong. I suspect it should workBauble
@ConnorsFan I corrected - "query"Felizio
sorry i was mislead by a doc title, it's from angular 1. But you can use [hidden]="!showDetails" actuallyIpsambul
Yes - corrected. No more typos I guess. And I think @Ipsambul is right. I should use hidden instead of *ngIf. But I am using animation - opacity and height to slide second div from first one. It will not work when using [hidden]. Any suggestions?Felizio
Can you show us the directive code? And, if you can, make a plunker or stackblitz showing the problem?Miosis
If ngIf does not show/hide elements but rather, it simply won't post them to the DOM if false. Therefor the element does not exist and is exempt from taking action against. Am I misunderstanding something? Does this work if you use ngShow/ngHide instead.Immunochemistry
Well, the directive is quite complicated, but everything is working well when I use [hidden] instead of *ngIf, so directive is working fine. It gets fired every time my Input() query changes, so it is fired when I enter the query, and by then there is no second DIV in the DOM. Is there any way to fire the directive manually (when I press the TOGGLE button)?Felizio
@Immunochemistry yes then this works. Is it possible to make it work using *ngIf, and manually (?) triggering directive once the second div becomes visible??Felizio
In principle, it should work with *ngIf. Try simplifying the directive to see if it works, and put back the complicated stuff gradually until you find the problem. Or try to make it simple enough to show it in a plunker or a stackblitz.Miosis
Definitely, you will likely have to write a little custom code against the element using the ElementRef API in your component code.Immunochemistry
@ConnorsFan Are you sure it should work with ngIf? In my case, directive get applied every time the query changes (the main directive code is within ngOnChanges() method). So what happens is: 1. Query is entered 2. Directive is applied to elements that exist -> DIV 2 does not exist, so directive is not applied. Thats why I am looking for a way to somehow trigger the directive ngOnChanges again.Felizio
Show us a simple live code example where an attribute directive does not work with *ngIf.Miosis
Take a look at this stackblitz and check what is different from your implementation. Maybe you don't apply the style at the right moment (e.g. I do it in ngOnInit in my code sample, it does not work in the constructor).Miosis
@ConnorsFan, as you can see in my answer, the problem is caused by the DIV inside *ngFor. So when I change the query, before the new list get propagated, directive ngOnChanges or ngOnInit gets called (they will only work if I add artificial setTimeout to delay then - to wait for ngFor to load). I have no idea how I can make this work -> run directive after list loadsFelizio
#48246394 here is the problem in more details.Felizio
F
1

UPDATE

Problem with my directive is related to *ngFor - both DIV are located withing ngFor. I have to call directive ngOnChanges or ngOnInit only after the new list get propagated based on the query. I have no idea how to do this, and currently, directives get loaded before ngFor fully loads - this cause problems.

Here is my previous answer:

I finally managed to solve this problem, thanks to @iHazCode.

So firstly, this is probably a duplication of problem described here: Angular 4 call directive method from component

Because my directive hightlights the specific letters in paragraph based on input query, each time the query changes, the directive should fire, thus I am using ngOnChanges withing the directive.

This is not working with *ngIf, because when ngOnChanges fires, the second div and paragraph is not visible.

As a workaround, we can access the directive from out component using @ViewChildren:

@ViewChildren(QueryHighlightDirective) dirs;

And then call it's ngOnChanges method, once div is toggled. In my case, the problem was related with last occurence of the directive within the component:

toggleDetails() {
  ...
  this.dirs.last.ngOnChanges();
}

That was exactly what I was looking for, I hope it will help someone.

In my case I also encountered another problem - the defined DIVs are within *ngFor, which is also propagated based on the query, so I have to make sure that the list will get propagated before calling ngOnChanges. I haven't find a solution for this yet.

Felizio answered 15/1, 2018 at 17:40 Comment(0)
I
0

ngIf does not show/hide elements. ngIf determines if the element exists on the DOM. You can verify this by looking at the source of your rendered page. Change the code to use ngHide/Show instead.

Immunochemistry answered 15/1, 2018 at 16:26 Comment(7)
I need a solution for this when using *ngIf. So, after pressing the toggle button, the second DIV gets animated from first one (opacity and height). And now, I want to re-render the paragraph inside, so the directive can be applied.Felizio
Gotcha, makes sense. There are a few ways to approach this, but it may be cleaner to just access the element in the templates backing code and run your Directive. Perhaps during the ngOnChanges lifecycle hook. Take a look at the below link which has some examples of using ElementRef API: #32693561Immunochemistry
How can I re-run the directive when I have access to my element? Is there a way to do this?Felizio
You said your code is pretty complex so I've tried to steer clear of concrete examples, however the answer is yes. Do you have any experience using pipes in your component code? It would work exactly the same. If not I can try to find or generate a simple example which may be of some use.Immunochemistry
Maybe this is the solution? angular.io/api/core/ViewChildren I could get all the directives within my component, and the re-run ngOnChanges method. But as I understand, there should be a build-in method in a directive that allows to re-run it or to trigger change detection or something?Felizio
You want the directive to execute when your ngIf condition changes (specifically for the "true" case) right? If that's the case, ngOnChanges should fire automagically. You should just need to add the hook, and put your code inside of it. One thing to be aware is that ngOnChanges fires frequently, so if your directive is expensive or dependent on some external resource that wouldn't be a great idea. However I suspect in your case it should be fine.Immunochemistry
I'll be away for a bit, but I understand your trying to work through this. If you don't get this sorted by the time I return we can work through it step by step. Your on the right track with @ViewChildren, this will be necessary. The below link may also be helpful (coupled with the other one I linked on elementRef): #45948096Immunochemistry

© 2022 - 2024 — McMap. All rights reserved.