アプリケーションをNGRXでデータをプリフェッチすることによって、より速く感じさせること


さえずりのツイッターで私について来てください.
NGRXあなたのアプリケーションを高速に感じるようにすることができます.として、“NGRXを使用して角度アプリケーションのユーザーエクスペリエンスを向上させる5つのヒント”で指摘したように、あなたはすぐに代わりにサーバーの応答を待つの要求(キャッシュされた)データを表示するには、Newsletterすることができます.
HTTPリクエストが保留中にあなたのユーザーに空白の画面を提示する代わりに、別の(より良い)オプションは、すでにグローバルストア(キャッシュ)で利用可能なデータを表示することです.必要な場合は、キャッシュされたデータを更新するには、バックグラウンドでデータを再取得できます.
しかし、ユーザーがページに移動するのは初めてですか?いいえデータが利用可能になり、アプリケーションはまだ低迷しているだろう.
スムーズなユーザーエクスペリエンスを保証するには、ナビゲーションが発生する前にデータをプリフェッチできます.

timdeschryver .dev 初期経験


出発点として、我々は英雄のツアーを例として使用します.
この例では、ヒーローとダッシュボードが含まれており、各ヒーローの詳細ページがあります.
世界的な店がなければ、両方の見解の間を操縦することはゆっくり感じます、そして、したがって、それは良い経験を提供しません.
これは、すべてのナビゲーションでデータを再読み込みするためです.
我々はより良いユーザーのために行うことができます.
use the NgRx Global Store as a cache

キャッシュデータ


グローバルストア内にデータを格納すると、そのページを読み込むときにそのデータを使用できます.
しかし、ユーザーがページに移動するのが初めてであるとき、HTTPリクエストが解決するまで、データがまだ利用できないので、ユーザーはまだ空白のスクリーンをじっと見つめなければなりません.
これは改善です、しかし、これは我々のユーザーのために改善されなければなりません.

プリフェッチ


これはプリフェッチデータが再生される場所です.
プリフェッチデータは、ユーザーがページに移動する前に、キャッシュがバックグラウンドで構築されることを意味します.
このテクニックは、ページ間のスムーズな移行とユーザーのより良い経験を確実にします.
あなたが既にNGRXを使用しているならば、グローバルな店がキャッシュとして役立つので、prefetchingは実装するのが簡単です.
NGRXを使用しているアプリケーション内のほとんどのものと同様に、すべてのアクションを開始します.
プリフェッチプロセスを開始するには、アクションを送信するだけです.
ユースケースによっては、できるだけ早くデータをプリフェッチしたり、ユーザがデータを必要とすることがほぼ確実になっているかもしれません.
私はディレクティブにこのロジックを抽象化し、データをプリフェッチしたいときに消費者にシグナルを発します.
両方のケースをカバーするために、指令はシグナルを発します:
  • は、ロードされます
  • ユーザーは、その上にホバー
  • @Directive({
      selector: '[prefetch]',
    })
    export class PrefetchDirective implements OnInit {
      @Input()
      prefetchMode: ('load' | 'hover')[] = ['hover']
      @Output()
      prefetch = new EventEmitter<void>()
    
      loaded = false
    
      ngOnInit() {
        if (this.prefetchMode.includes('load')) {
          this.prefetchData()
        }
      }
    
      @HostListener('mouseenter')
      onMouseEnter() {
        if (!this.loaded && this.prefetchMode.includes('hover')) {
          this.loaded = true
          this.prefetchData()
        }
      }
    
      prefetchData() {
        if (navigator.connection.saveData) {
          return
        }
        this.prefetch.next()
      }
    }
    
    最初のユースケースは、できるだけ早くデータをプリフェッチするために、以下のスニペットで実証されます.
    我々はダッシュボードのビューでは、最も人気のあるアイテムが表示されます.
    アイテムの数が制限されているため、ユーザーがダッシュボードの項目のいずれかに移動すると確信しているので、我々はバックグラウンドですべてのヒーローの詳細を読み込むことを選択します.
    @Component({
      selector: 'app-dashboard',
      template: `
        <h3>Top Heroes</h3>
        <div class="grid grid-pad">
          <a
            *ngFor="let hero of heroes$ | async"
            class="col-1-4"
            routerLink="/detail/{{ hero.id }}"
            (prefetch)="prefetch(hero.id)"
            [prefetchMode]="['load']"
          >
            <div class="module hero">
              <h4>{{ hero.name }}</h4>
            </div>
          </a>
        </div>
      `,
    })
    export class DashboardComponent {
      heroes$ = this.store.pipe(select(selectHeroesDashboard))
      constructor(private store: Store) {}
    
      prefetch(id) {
        this.store.dispatch(heroDetailLoaded({ id }))
      }
    }
    
    あなたが下のGIFのネットワークタブを見るならば、英雄のダッシュボードリストがレンダリングされるならば、詳細が荷を積まれるのを見ることができます.

    番目のユースケースは、すべてのヒーローが記載されている概要ページです.
    これは大きなリストになることができ、我々はどのヒーローがクリックされるかわからないので、我々はそれが立ち往生した後ヒーローの詳細を読み込むことを選んだ.
    これは前の例ほど高速ではありませんが、以前より速くなっていますが、他のプラス側はデータを取得しないことです.

    Note: This is a popular approach that is used for static site generators.


    @Component({
      selector: 'app-heroes',
      template: `
        <h2>My Heroes</h2>
        <ul class="heroes">
          <li *ngFor="let hero of heroes$ | async">
            <a routerLink="/detail/{{ hero.id }}" (prefetch)="prefetch(hero.id)">
              <span class="badge">{{ hero.id }}</span> {{ hero.name }}
            </a>
          </li>
        </ul>
      `,
    })
    export class HeroesComponent {
      heroes$ = this.store.select(selectHeroes)
      constructor(private store: Store) {}
    
      prefetch(id: number) {
        this.store.dispatch(heroDetaiHovered({ id }))
      }
    }
    
    あなたが下のGIFのネットワーク・タブに注意を払うならば、あなたが英雄を乗り越えるとき、英雄詳細が要請されるのを見ることができます.

    詳細を取得する効果はheroDetailLoadedのアクションとheroDetaiHoveredのアクションに耳を傾けます.
    @Injectable()
    export class HeroesEffects {
      detail$ = createEffect(() => {
        return this.actions$.pipe(
          // 👂 listen to both actions
          ofType(heroDetailLoaded, heroDetaiHovered),
          // ⚙ fetch all in parallel
          // @link https://rxjs-dev.firebaseapp.com/api/operators/mergeMap
          mergeMap(({ id: heroId }) =>
            this.heroesService
              .getHero(heroId)
              .pipe(map((hero) => heroDetailFetchSuccess({ hero }))),
          ),
        )
      })
    
      constructor(private actions$: Actions, private heroesService: HeroService) {}
    }
    
    上記の例で気づいているかもしれないように、詳細はグローバルストアですでに永続化されていても、ヒーローの詳細は毎回フェッチされます.
    これは必ずしも理想的ではない.
    我々は、で議論されるように、我々が店に格納されない英雄の詳細を得るだけである効果を微調整することができます.
    @Injectable()
    export class HeroesEffects {
      detail$ = createEffect(() => {
        return this.actions$.pipe(
          ofType(heroDetailLoaded, heroDetaiHover),
          concatMap((action) =>
            of(action).pipe(
              withLatestFrom(this.store.pipe(select(selectHeroDetail(action.id)))),
            ),
          ),
          filter(([_action, detail]) => Boolean(detail) === false),
          mergeMap(([{ id: heroId }]) =>
            this.heroesService
              .getHero(heroId)
              .pipe(map((hero) => heroDetailFetchSuccess({ hero }))),
          ),
        )
      })
    
      constructor(
        private actions$: Actions,
        private store: Store,
        private heroesService: HeroService,
      ) {}
    }
    

    結論


    プリフェッチは、アプリケーションをより速く感じるように使用することができます.データを永続化するためにキャッシュを持っている限り、ユーザーの経験を改善するためにこのテクニックを使用できます.
    グローバルストアがキャッシュオブジェクトであるため、グローバルNGRXストアを使用しているアプリケーション内ではこれは些細なことです.
    アクションを作成し、そのアクションを有効にするだけで、キャッシュをプリロードできます.
    さえずりのツイッターで私について来てください.