Reputation: 2422
I have a custom Angular directive that takes a couple inputs. Its purpose is to highlight the matching parts of the element to which the directive is added with the input matchTerm
. It's supposed to work with a typeahead results list, so as the user types the results that come back have the matching strings highlighted.
Here is the entire directive:
import { Directive, Input, ElementRef, OnChanges, SimpleChanges } from '@angular/core';
import { highlightStringMatches } from './typeahead.util';
@Directive({
selector: '[hsaTypeaheadResult]',
})
export class TypeaheadResultDirective implements OnChanges {
@Input() matchTerm: string = '';
@Input() highlightMatches: boolean = true;
@Input() caseInsensitiveMatch: boolean = true;
constructor(private _element: ElementRef) {
console.log({ element: this._element });
}
ngOnChanges(changes: SimpleChanges) {
console.log({ changes });
if (changes.matchTerm && this.highlightMatches) {
this.markStringMatches(this._element);
}
}
public markStringMatches(ref: ElementRef) {
const itemString = ref.nativeElement.textContent.trim();
console.log({
itemString,
matchTerm: this.matchTerm,
highlightMatches: this.highlightMatches,
caseInsensitiveMatch: this.caseInsensitiveMatch,
});
ref.nativeElement.innerHTML =
this.highlightMatches && this.matchTerm
? highlightStringMatches(itemString, this.matchTerm, this.caseInsensitiveMatch)
: itemString;
}
}
The directive works if I do the following:
<ul>
<li hsaTypeaheadResult matchTerm="res">Result 1</li>
<li hsaTypeaheadResult matchTerm="res">Result 2</li>
<li hsaTypeaheadResult matchTerm="res">Result 3</li>
</ul>
The "Res" of each li
is bolded. But it doesn't work if I do either of the following:
<ul>
<li hsaTypeaheadResult matchTerm="res" *ngFor="let result of resultsArr">{{ result }}</li>
</ul>
<ul>
<li hsaTypeaheadResult matchTerm="res">{{ results[0] }}</li>
<li hsaTypeaheadResult matchTerm="res">{{ results[1] }}</li>
<li hsaTypeaheadResult matchTerm="res">{{ results[2] }}</li>
</ul>
Any time a variable is interpolated in the curly brackets for an element where the directive is used, the value of the variable doesn't show up on the screen:
Just in case it was something with having the structural *ngFor
directive on the li
element as well, I tried putting the ngFor
on an ng-template
tag, but it still didn't work.
You can see a full example on Stackblitz here. I'm not sure why it doesn't work with interpolation. In my tests, which you can see on the Stackblitz project, I used an ngFor
loop once I found out that it wasn't working with interpolation in my apps and the tests still pass.
I've tried this in a brand new Angular CLI project as well as a Stackblitz and neither of those projects had any other dependencies that should affect it negatively. Any help would be greatly appreciated.
Upvotes: 1
Views: 574
Reputation: 2422
After thinking about this for some time, I realized that the ElementRef.nativeElement.textContent
was empty in the directive's constructor when using interpolation. So I realized that must mean I was running the function in the directive too soon. To test, I used a setTimeout
and waited 2 seconds before running the function to highlight the match. When I did that, the directive worked as expected.
After that it was just a matter of finding a lifecycle method that would run after the view was ready, and AfterViewInit
worked perfect. The directive code is as follows now:
import { Directive, Input, ElementRef, OnChanges, SimpleChanges, AfterViewInit } from '@angular/core';
import { highlightStringMatches } from './typeahead.util';
@Directive({
selector: '[hsaTypeaheadResult]',
})
export class TypeaheadResultDirective implements OnChanges, AfterViewInit {
@Input() matchTerm: string = '';
@Input() highlightMatches: boolean = true;
@Input() caseInsensitiveMatch: boolean = true;
constructor(private _element: ElementRef) {}
ngOnChanges(changes: SimpleChanges) {
if (changes.matchTerm && this.highlightMatches && this._element.nativeElement.textContent) {
this.markStringMatches(this._element);
}
}
ngAfterViewInit() {
if (this.matchTerm && this.highlightMatches && this._element.nativeElement.textContent) {
this.markStringMatches(this._element);
}
}
public markStringMatches(ref: ElementRef) {
const itemString = ref.nativeElement.textContent.trim();
ref.nativeElement.innerHTML =
this.highlightMatches && this.matchTerm
? highlightStringMatches(itemString, this.matchTerm, this.caseInsensitiveMatch)
: itemString;
}
}
Upvotes: 1