2016-06-02 17 views
9

Sto lavorando a un Pokedex site e grazie al fatto che ora ci sono 721 Pokemon an nGPer sta impiegando molto tempo per visualizzare tutte le voci la prima volta. Una volta caricati tutti i dati, sembra che siano necessari circa 2400 ms per inserirli effettivamente nel DOM.Come posso velocizzare ngFor per un array di grandi dimensioni?

Ecco il ngFor in questione:

<entry *ngFor="let p of (pokedex | filter:search:SelectedVer), let i = index, let last = last" 
    [id]="'pokemon-entry-' + p.id" 
    [pokemon]="p" 
    [language]="SelectedLang" 
    (click)="SelectPokemon(p)"></entry> 

ho eseguito una timeline in strumenti di sviluppo di Chrome e ottenuto qualcosa che assomiglia a questo:

Timeline of MaterialPokedex.com loading up

non ho molta esperienza con la timeline ma mi sembra che ci sia un blocco troppo grande proprio lì nel mezzo (la parte superiore è etichettata come XHR Load (/csv/pokemon_game_indices.csv)). La chiamata ajax richiede 0,02 ms in base alla timeline. Suppongo che ciò che rende questo blocco così grande sia il rilevamento delle modifiche che avviene dopo che la richiesta di ajax è stata completata. Questo è quando prendo i miei modelli che ho costruito e li ho inseriti nella variabile pokedex che il ngFor usa sopra. La mia comprensione della timeline è che la costruzione dei 721 elementi DOM che devono essere aggiunti da ngFor richiede circa 2,5 secondi.

Ho provato a smantellare il mio componente entry nel solo html (il componente in realtà non fa nulla), ma questo non sembra influenzare il tempo in alcun modo evidente. La rimozione della pipe che utilizzo per filtrare l'elenco non ha alcun impatto sul tempo.

C'è un modo per velocizzare questo ngFor?

Sto usando Angular 2 RC1. Sto abilitando la modalità prod. Sto eseguendo questo in Chrome 51.0.2704.79 m

+2

Non sono sicuro di come si visualizzano i dati, ma non è necessario mostrare 721 elementi sullo schermo. Sarai solo in grado di vederne una frazione. Non puoi semplicemente sfogliare i dati in modo che sia più facile guardarli? Quindi mostreresti solo 10 - 100 pokemon anziché 721. Ciò renderebbe molto più veloce. – DerekMT12

+1

e [questo] (https: //www.npmjs.com/package/angular2-infinito-scroll) dovrebbe aiutarti con l'impaginazione – FernOfTheAndes

+1

La migliore ottimizzazione è non fare nulla. Come già detto, rende solo ciò che deve essere visualizzato in una sola volta. –

risposta

10

La risposta breve e dolce è "non iterare sull'intero array", ma non è stato abbastanza buono per me. Volevo che assomigliasse all'intera colonna di voci presenti. Quindi metto un distanziale sopra, il ngPer scorre su una sottosezione dell'array, e uno spaziatore sotto e insieme questo fa apparire la lista come tutti gli elementi ci sono sempre.

Ecco una versione semplificata del mio html con solo le parti relative a questo problema (full example on bitbucket):

<div (scroll)="ColScroll($event)"> 
    <div [style.height]="Math.max(0, Math.max(0, scrollPos - 10) * 132)"></div> 
    <entry *ngFor="let p of (base.pokemon | filter:search:SelectedVer:SelectedLang) | justafew:scrollPos" [pokemon]="p"></entry> 
    <div [style.height]="Math.max(0,((base.pokemon | filter:search:SelectedVer:SelectedLang).length - scrollPos - 40)) * 132"></div> 
</div> 

struttura ultra-minimale per assoluta chiarezza:

<div> <!-- column --> 
    <div></div> <!-- spacer --> 
    <entry *ngFor='...'></entry> 
    <div></div> <!-- spacer --> 
</div> 

In primo luogo, una chiave point: <entry> è sempre esattamente 120 pixel di altezza con un margine inferiore di 12 pixel per un totale di 132 pixel di spazio totale. Il CSS lo rende assoluto. Questo funziona per qualsiasi dimensione costante volessi selezionare, ma faccio ipotesi speciali che la dimensione sia esattamente di 132 pixel.

La versione breve è che quando si scorre, scrollHeight della colonna determina quali voci dovrebbero effettivamente essere sullo schermo. Se i primi 10 elementi creati da ngFor sono fuori dallo schermo, il primo elemento visibile inizia dal numero 11. Conto per uno schermo a 4k e visualizzo 40 voci (occupando 5280 pixel) per essere sicuro che l'intera colonna sia piena. Quindi, in modo che la barra di scorrimento appaia corretta, ho un distanziale sotto le 40 voci per forzare il div a avere l'altezza scorrevole adatta.Ecco un'immagine di quello che sta visivamente succedendo:

Spacer above and below the viewable space in the list of pokemon

Ecco le variabili e le funzioni pertinenti del controllore (bitbucket):

scrollPos = 0; 
... 
ColScroll(event: Event) { 
    let pos = $(event.target).scrollTop(); 
    this.scrollPos = Math.floor(pos/132); 
} 

uccide me di utilizzare jQuery qui, ma ero già usando per qualcos'altro e avevo bisogno di qualcosa di cross-browser. scrollPos contiene il primo indice del primo elemento che dovrei mostrare sullo schermo.

Il ngFor che costruisce effettivamente tutti gli elementi <entry> assomiglia a questo:

*ngFor="let p of (base.pokemon | filter:search:SelectedVer:SelectedLang) | justafew:scrollPos" 

rottura che giù:

base.pokemon è un array di dati pokemon necessari per creare ciascun elemento d'accesso.

... | filter:search:SelectedVer:SelectedLang) viene utilizzato per la ricerca nell'elenco. Lascio il mio esempio qui per dimostrare che è ancora possibile giocare con la lista prima che il mio hack entri in gioco.

... | justafew:scrollPos è dove la magia accade. Ecco che il filtro nella sua interezza (bitbucket):

import { Pipe, PipeTransform } from '@angular/core'; 

import { MinPokemon } from '../models/base'; 

@Pipe({ 
    name: 'justafew', 
    pure: false 
}) 
export class JustAFewPipe implements PipeTransform { 
    public transform(value: MinPokemon[], start: number): MinPokemon[] { 
    return value.slice(Math.max(0, start - 10), start + 30); 
    } 
} 

scrollPos è stata approvata nel come parametro start. Ad esempio, se ho spostato i miei 13200 pixel verso il basso nella colonna, scrollPos è impostato su 100 (vedere l'evento di scorrimento nel controller in alto). Questo troncerà la matrice in modo che restituisca gli elementi da 90 a 130. Voglio un po 'esagerare lo schermo per garantire che lo scorrimento veloce non provochi uno spazio bianco visibile (lo scorrimento follemente veloce potrebbe ancora mostrarlo ma ti stai muovendo così velocemente è facile pensare che il browser non sia stato reso così veloce, quindi lo lascio scorrere). Io uso Math.max quindi non affondo utilizzando numeri negativi come quando sono in cima alla lista e scrollPos è 0.

Ora i distanziali. Mantengono la barra di scorrimento onesta. Lego il loro [style.height] e uso un po 'di matematica per fare in modo che questi distanziatori occupino lo spazio richiesto. Mentre scorro verso il basso, il distanziale superiore diventa più alto e il distanziatore inferiore si restringe esattamente dello stesso valore in modo che la colonna abbia sempre la stessa altezza. Quando eseguo il backup, la matematica risolve esattamente il contrario: la parte superiore si restringe e il fondo cresce. Lo spacer bottom utilizza la stessa logica del filtro di ngFor in modo tale che se eseguo una ricerca che restituisce 100 anziché 721 pokemon, si adatta all'altezza di 100 voci. Il primo distanziale utilizza scrollPos - 10 perché il filtro justafew torna indietro 10. Per lo stesso motivo, lo spaziatore di fondo utilizza scrollPos - 30 perché è il numero di justafew restituito.

So che sembra un sacco di parti mobili ma sono tutte semplici e veloci. Sfortunatamente ci sono molti "numeri magici" in tutto il luogo che si fidano l'uno dell'altro ma considerando i miglioramenti e l'affidabilità delle prestazioni questo mi ha dato la possibilità di mostrare l'intera lista che ho lasciato scorrere. Forse un giorno farò un componente o una direttiva per mettere tutto in un posto configurabile.

+1

Io, ho dovuto usare '[style.height.px]' invece di '[style.height]' per farlo funzionare. Soluzione fantastica, comunque! – mackcmillion

+1

Inoltre, Angular genera un errore quando si utilizza 'Math' all'interno del modello. Ho dovuto dichiarare i getter nel componente per risolvere questo problema. Grazie per la soluzione semplice ed elegante! – vanelizarov

+0

@vanelizarov Giusto, avrei dovuto dirlo. Nel mio componente che ospita la colonna ho 'public Math: Math = Math;' questo lo rende disponibile. –

Problemi correlati