>

rxx를 처음 사용하고 긴 값 목록 (500+)을 렌더링해야하는 각도 다중 선택 목록 구성 요소를 개발 중입니다. UL을 기반으로 목록을 렌더링하고 있으며 LI를 렌더링 할 관찰 가능 항목을 반복하고 있습니다. 모든 요소를 ​​한 번에 렌더링하여 성능에 영향을 미치지 않도록 옵션을 생각하고 있습니다. 그러나 이것이 가능한지 여부와 가능하다면 가능한 가장 좋은 연산자는 무엇인지 모르겠습니다.

제안 된 솔루션 :

  • 초기화 할 때 모든 데이터를 Observable에로드합니다. (src) 100 개의 요소를 가져 와서 관찰 가능한 대상 (목록을 렌더링하는 데 사용되는 대상)에 배치합니다.
  • 사용자가 목록의 끝에 도달 할 때마다 (scrollEnd 이벤트 발생) src 관찰 가능 값이 더 이상 없을 때까지 100 개의 요소를 더로드합니다.

  • 목표 관찰 가능한 새로운 값의 방출은 scrollEnd 이벤트에 의해 트리거됩니다.

아래에서 내 코드를 찾으면서도 제안 된 솔루션을 구현해야하지만이 시점에서 멈췄습니다.

수정: @martin 솔루션을 구현하고 있지만 여전히 코드에서 작동하지 않습니다. 첫 번째 단계는 코드에서 코드를 복제하여 기록 된 값을 얻는 것이었지만 관찰 가능 값은 값을 생성하지 않고 즉시 완료됩니다. 이벤트를 트리거하는 대신 주제를 추가했습니다. scrollindEnd 출력이 발생할 때마다 새로운 값을 피사체에 푸시합니다. 이를 지원하도록 템플릿이 수정되었습니다.

multiselect.component.ts

import { Component, AfterViewInit } from '@angular/core';
import { zip, Observable, fromEvent, range } from 'rxjs';
import { map, bufferCount, startWith, scan } from 'rxjs/operators';
import { MultiSelectService, ProductCategory } from './multiselect.service';
@Component({
  selector: 'multiselect',
  templateUrl: './multiselect.component.html',
  styleUrls: ['./multiselect.component.scss']
})
export class MultiselectComponent implements AfterViewInit {
  SLICE_SIZE = 100;
  loadMore$: Observable<Event>;
  numbers$ = range(450);
  constructor() {}

  ngAfterViewInit() {
    this.loadMore$ = fromEvent(document.getElementsByTagName('button')[0], 'click');
    zip(
      this.numbers$.pipe(bufferCount(this.SLICE_SIZE)),
      this.loadMore$.pipe(),
    ).pipe(
      map(results => console.log(results)),
    ).subscribe({
      next: v => console.log(v),
      complete: () => console.log('complete ...'),
    });
  }
}

multiselect.component.html

<form action="#" class="multiselect-form">
  <h3>Categories</h3>
  <input type="text" placeholder="Search..." class="multiselect-form--search" tabindex="0"/>
  <multiselect-list [categories]="categories$ | async" (scrollingFinished)="lazySubject.next($event)">
  </multiselect-list>
  <button class="btn-primary--large">Proceed</button>
</form>

multiselect-list.component.ts

import { Component, Input, Output, EventEmitter } from '@angular/core';
@Component({
  selector: 'multiselect-list',
  templateUrl: './multiselect-list.component.html'
})
export class MultiselectListComponent {
  @Output() scrollingFinished = new EventEmitter<any>();
  @Input() categories: Array<string> = [];
  constructor() {}
  onScrollingFinished() {
    this.scrollingFinished.emit(null);
  }
}

multiselect-list.component.html

<ul class="multiselect-list" (scrollingFinished)="onScrollingFinished($event)">
  <li *ngFor="let category of categories; let idx=index" scrollTracker class="multiselect-list--option">
    <input type="checkbox" id="{{ category }}" tabindex="{{ idx + 1 }}"/>
    <label for="{{ category }}">{{ category }}</label>
  </li>
</ul>

참고 :scrollingFinished 이벤트는 추적 로직을 보유하는 scrollTracker 지시문에 의해 트리거됩니다. multiselect-list에서 multiselect 구성 요소로 이벤트를 버블 링하고 있습니다.

미리 감사합니다!

  • 답변 # 1

    이 예제는 450 개의 항목으로 구성된 배열을 생성 한 다음 100 청크로 분할합니다. . 먼저 처음 100 개의 항목을 덤프하고 버튼을 클릭 할 때마다 다른 100 가 걸립니다.  이전 결과에 추가합니다. 이 데이터는 모든 데이터를로드 한 후 올바르게 완료됩니다.

    이 문제를 해결하고 사용할 수 있어야한다고 생각합니다. 버튼 클릭 대신 Subject 를 사용하십시오.  사용자가 맨 아래로 스크롤 할 때마다 방출됩니다.

    import { fromEvent, range, zip } from 'rxjs'; 
    import { map, bufferCount, startWith, scan } from 'rxjs/operators';
    const SLICE_SIZE = 100;
    const loadMore$ = fromEvent(document.getElementsByTagName('button')[0], 'click');
    const data$ = range(450);
    zip(
      data$.pipe(bufferCount(SLICE_SIZE)),
      loadMore$.pipe(startWith(0)),
    ).pipe(
      map(results => results[0]),
      scan((acc, chunk) => [...acc, ...chunk], []),
    ).subscribe({
      next: v => console.log(v),
      complete: () => console.log('complete'),
    });
    
    

    실시간 데모 : https://stackblitz.com/edit/rxjs-au9pt7?file=index.ts

    성능이 걱정된다면 trackBy 를 사용해야합니다   *ngFor 를 위해  기존 DOM 요소를 다시 렌더링하지 않으려면 이미 알고 계신 것 같습니다.

  • 답변 # 2

    스택 블리츠에 대한 라이브 데모입니다.

    컴포넌트가 전체 목록을 표시하는 관찰 가능 항목을 구독하면 서비스가이 전체 목록을 보유하고 항목이 추가 될 때마다 새 목록을 보내야합니다. 다음은이 패턴을 사용한 구현입니다. 목록은 참조로 전달되므로 관찰 가능 항목에 푸시 된 각 목록은 단순히 참조이며 목록의 복사본이 아니므로 새 목록을 보내는 것은 비용이 많이 드는 작업이 아닙니다.

    서비스에는 BehaviorSubject 를 사용하십시오  관찰 가능 항목에 새 항목을 주입합니다. asObservable() 를 사용하여 관찰 가능합니다.  방법. 다른 속성을 사용하여 현재 목록을 보유하십시오. 매번 loadMore()  이 호출되면 목록에서 새 항목을 푸시 한 다음 주제에서이 목록을 푸시하면 옵저버 블에서도 푸시되고 구성 요소가 다시 렌더링됩니다.

    여기서 나는 모든 아이템을 보유한리스트 ( allCategories )로 시작합니다 ), 매번 loadMore()   Array.splice() 를 사용하여 현재 목록에 가져와 놓으면 100 개 항목의 블록이라고합니다. :

    @Injectable({
      providedIn: 'root'
    })
    export class MultiSelectService {
      private categoriesSubject = new BehaviorSubject<Array<string>>([]);
      categories$ = this.categoriesSubject.asObservable();
      categories: Array<string> = [];
      allCategories: Array<string> = Array.from({ length: 1000 }, (_, i) => `item #${i}`);
      constructor() {
        this.getNextItems();
        this.categoriesSubject.next(this.categories);
      }
      loadMore(): void {
        if (this.getNextItems()) {
          this.categoriesSubject.next(this.categories);
        }
      }
      getNextItems(): boolean {
        if (this.categories.length >= this.allCategories.length) {
          return false;
        }
        const remainingLength = Math.min(100, this.allCategories.length - this.categories.length);
        this.categories.push(...this.allCategories.slice(this.categories.length, this.categories.length + remainingLength));
        return true;
      }
    }
    
    

    그런 다음 loadMore() 를 불러  당신의 multiselect 에서 서비스에 방법  하단에 도달하면 구성 요소 :

    export class MultiselectComponent {
      categories$: Observable<Array<string>>;
      constructor(private dataService: MultiSelectService) {
        this.categories$ = dataService.categories$;
      }
      onScrollingFinished() {
        console.log('load more');
        this.dataService.loadMore();
      }
    }
    
    

    당신의 multiselect-list 에서  구성 요소, scrollTracker 를 배치   ul 를 포함하는 지시문   li 가 아니라 :

    <ul class="multiselect-list" scrollTracker (scrollingFinished)="onScrollingFinished()">
      <li *ngFor="let category of categories; let idx=index"  class="multiselect-list--option">
        <input type="checkbox" id="{{ category }}" tabindex="{{ idx + 1 }}"/>
        <label for="{{ category }}">{{ category }}</label>
      </li>
    </ul>
    
    

    맨 아래로 스크롤을 감지하고 이벤트를 한 번만 발생 시키려면이 논리를 사용하여 scrollTracker 를 구현하십시오.  지시문 :

    @Directive({
      selector: '[scrollTracker]'
    })
    export class ScrollTrackerDirective {
      @Output() scrollingFinished = new EventEmitter<void>();
      emitted = false;
      @HostListener("window:scroll", [])
      onScroll(): void {
        if ((window.innerHeight + window.scrollY) >= document.body.offsetHeight && !this.emitted) {
          this.emitted = true;
          this.scrollingFinished.emit();
        } else if ((window.innerHeight + window.scrollY) < document.body.offsetHeight) {
          this.emitted = false;
        }
      }
    }
    
    

    도움이 되길 바랍니다!

관련 자료

  • 이전 internet explorer 11 - IE11에 대한 'Gatsby develop'지원
  • 다음 c# - datagridview의 아랍어 데이터를 데이터베이스에 저장