简体   繁体   English

ngFor内部的自定义结构指令,在ngFor之前进行更新

[英]Custom structural directive inside ngFor updates before ngFor

I am creating a small application, which display a list of People with Name, Surname and Age fields using ngFor. 我正在创建一个小型应用程序,该应用程序使用ngFor显示具有名称,姓氏和年龄字段的人的列表。 The application has search field, where one can enter a query, and then the list will get replaced with new Entities from the server, based on that query. 该应用程序具有搜索字段,可以在其中输入查询,然后根据该查询,列表将被服务器上的新实体替换。

I created a directive, that highlights letters from the query inside the ngFor row. 我创建了一个指令,该指令突出显示ngFor行内查询的字母。

For example, if I have a person in database that its name is David, and I enter "Dav" inside my query, only entities conatining "Dav" will be loaded from the server in ngFor, and "Dav" letters will be highlighted and "id" will be not. 例如,如果我在数据库中有一个名为David的人,并且在查询中输入“ Dav”,则只有包含“ Dav”的实体将从ngFor中从服务器加载,并且“ Dav”字母将突出显示,并且“ id”将不是。 If I have David and Davin, both entities will be highlighted. 如果我有David和Davin,两个实体都将突出显示。

The directive works as expected only if I am using an artifical setTimeout(), to make sure that the new list will load before the Directive takes action. 仅当我使用人工setTimeout()时,该指令才能按预期工作,以确保在指令执行操作之前将加载新列表。 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: 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: LIST-COMPONENT.HTML:

  <app-person-item 
    *ngFor="let person of personList;" 
    [query]="afterFilteringQuery">
  </app-person-item>

Instead of ngOnChanges approach you may try to apply BehaviorSubject approach . 您可以尝试使用BehaviorSubject方法来代替ngOnChanges方法。 I'm not sure but Observable's .next() call should guarantee an additional event loop cycle which is necessary in your case as we can see per working zero setTimeout call. 我不确定,但是Observable的.next()调用应该保证额外的事件循环周期,这对于您的情况是必需的,因为我们可以看到每个工作0 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'
    });
  }

}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM