Custom structure directive inside ngFor updates before ngFor

I am creating a small application that displays a list of "People with first names, last names and ages" fields using ngFor. The application has a search field into which you can enter a query, and then this list will be replaced by new objects from the server based on this query.

I created a directive that extracts the letters from the query inside the ngFor line.

For example, if I have a person in the database, his name is David, and I enter "Dav" inside my query, only the entities that link "Dav" will be downloaded from the server to ngFor, and the letters "Dav" will be highlighted , but id is not. If I have David and Devin, both objects will be selected.

The directive works as expected only if I use the artificial setTimeout () to make sure the new list loads before the directive takes effect. Is there any other way to make this work?

DIRECTIVE:

import { Directive, Input, ElementRef } from '@angular/core';
import { SimpleChanges, Renderer2 } from '@angular/core';

@Directive({
  selector: '[appQueryHighlight]'
})
export class QueryHighlightDirective {

  @Input('appQueryHighlight') query: string;
  queryPos: number;
  paragraphElement: HTMLParagraphElement;

  constructor(private element: ElementRef, private renderer: Renderer2) {
    this.paragraphElement = (<HTMLParagraphElement>this.element.nativeElement);
  }

  ngOnChanges(changes: SimpleChanges){
    // Temporary timeout solution
    setTimeout(()=>{

      var childCount = this.paragraphElement.childElementCount;
      var text: string = "";

      // If paragraph contain SPANS, we need to flat them to innerHTML
      if(childCount > 1) {
        for(var i = 0; i < childCount; i++) {
          text += (<HTMLSpanElement>this.paragraphElement.childNodes[0]).innerHTML;
          console.log("SPAN" + (<HTMLSpanElement>this.paragraphElement.childNodes[0]).innerHTML);
          this.paragraphElement.removeChild(this.paragraphElement.childNodes[0]);
        }
        console.log("Text=" + text)
        this.paragraphElement.innerHTML = text;
      }

      console.log('Directive ngOnChanges: query=' + this.query + ", paragraph=" + this.paragraphElement.innerHTML);

      this.queryPos = this.paragraphElement.innerHTML.toUpperCase().indexOf(this.query.toUpperCase());
      if(this.query!="" && this.queryPos >= 0) {
        //this.paragraphElement.style.backgroundColor = 'yellow';

        //First span, containing pre-colored text
        var span1 = this.renderer.createElement('span');
        var text1 = this.renderer.createText(this.paragraphElement.innerHTML.substring(0,this.queryPos));
        this.renderer.appendChild(span1, text1);

        //Colored text span, containing query
        var span2 = this.renderer.createElement('span');
        var text2 = this.renderer.createText(this.paragraphElement.innerHTML.substr(this.queryPos, this.query.length));
        this.renderer.setStyle(span2, 'color', "red");
        this.renderer.setStyle(span2, 'text-decoration', "underline");
        this.renderer.appendChild(span2, text2);

        //Third span, containing text after query
        var span3 = this.renderer.createElement('span');
        var text3 = this.renderer.createText(this.paragraphElement.innerHTML.substring(this.queryPos + this.query.length));
        this.renderer.appendChild(span3, text3);

        this.paragraphElement.innerHTML = "";
        this.renderer.appendChild(this.paragraphElement, span1);
        this.renderer.appendChild(this.paragraphElement, span2);
        this.renderer.appendChild(this.paragraphElement, span3);
      }
      else {
        //this.paragraphElement.style.color = 'black';
      }
    }, 15);
  }
}

LIST-COMPONENT.TS:

ngOnChanges(changes: SimpleChanges) { 
  this.debtsService.getFilteredDebts(this.query)
    .subscribe(
      (data) => {
        this.debtsList = data;
        this.afterFilteringQuery = this.query;
      },
      (err) => console.log("Error occured: " + err)
    );
}

LIST-COMPONENT.HTML:

  <app-person-item 
    *ngFor="let person of personList;" 
    [query]="afterFilteringQuery">
  </app-person-item>
0
source share
1 answer

Instead of an approach, ngOnChangesyou can try applying an approach BehaviorSubject. I'm not sure, but calling Observable .next()should guarantee an additional event loop, which is necessary in your case, since we can see beyond working zero setTimeout.

import { Directive, Input, ElementRef, Renderer2 } from '@angular/core';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';

@Directive({
  selector: '[appQueryHighlight]'
})
export class QueryHighlightDirective {

  private _query = new BehaviorSubject<string>('');

  @Input('appQueryHighlight')
  set query(value: string) {
    this._query.next(value);
  };
  get query(): string {
    return this._query.getValue();
  }

  ngOnInit() {
    this._query.subscribe((query: string) => {
      // on query change handler
      // ... ngOnChanges-setTimeout previous code with 'query' instead of 'this.query'
    });
  }

}
0
source

Source: https://habr.com/ru/post/1684704/


All Articles