web-dev-qa-db-ja.com

Angular `runGuardsAndResolvers`が 'always'に設定されているにもかかわらず、リゾルバがデータを更新または再フェッチしませんか?

Angularルートのセット、エンティティのリスト、そのようなエンティティの作成と既存のエンティティの編集のための2つの子ルートがあります。エンティティのリストにはresolverを付加します。これらのルートは次のように要約できます。これらのルートがコードでどのように記述されているかについては、さらに下を参照してください。

  • インデックス:/items
  • 作成:/items/create
  • 編集:/items/:itemId/edit

ただし、私が/items/create、アイテムを正常に作成し、「[戻る]」に移動して/itemsまたは編集ルート、または/は、リゾルバが期待したように更新されたデータをフェッチする結果にはなりません。 これは、runGuardsAndResolversプロパティが "always"に設定されているにもかかわらずです。 私の理解 は、このプロパティが私が探している機能を有効にするはずであることです。

これはなぜですか、そして私が探している機能をどのように有効にすることができますか?コンポーネントのルーターイベントのサブスクライブやロジックの複製などを行わずに.

ルート

const itemRoutes: Routes = [
    {
        path: '', // nb: this is a lazily loaded module that is itself a child of a parent, the _actual_ path for this is `/items`. 
        runGuardsAndResolvers: 'always',
        component: ItemsComponent,
        data: {
            breadcrumb: 'Items'
        },
        resolve: {
            items: ItemsResolver
        },
        children: [
            {
                path: 'create',
                component: CreateItemComponent,
                data: {
                    breadcrumb: 'Create Item'
                }
            },
            {
                path: ':itemId/edit',
                component: EditOrArchiveItemComponent,
                data: {
                    breadcrumb: 'Editing Item :item.name'
                },
                resolve: {
                    item: ItemResolver
                }
            }
        ]
    }
];

ItemsResolver

@Injectable()
export class ItemsResolver implements Resolve<ItemInIndex[]> {
    public constructor(public itemsHttpService: ItemsHttpService, private router: Router) {}

    public resolve(ars: ActivatedRouteSnapshot, rss: RouterStateSnapshot): Observable<ItemInIndex[]> {
        return this.itemsHttpService.index().take(1).map(items => {
            if (items) {
                return items;
            } else {
                this.router.navigate(['/']);
                return null;
            }
        });
    }
}

ItemsHttpService

(リクエストに応じて投稿)

@Injectable()
export class ItemsHttpService  {

    public constructor(private apollo: Apollo) {}

    public index(): Observable<ItemInIndex[]> {
        const itemsQuery = gql`
            query ItemsQuery {
                items {
                    id,
                    name
                }
            }
            `;

        return this.apollo.query<any>({
            query: itemsQuery
        }).pipe(
            map(result => result.data.items)
        );
    }
}

まず第一に

これは、Resolverをどのように使用すべきかではありません

Resolverは、必要なデータが存在することがルートにとって必要不可欠な場合にのみ使用してください。したがって、リゾルバーは、専用のデータのルートがあり、コンポーネントのロードを続行する前にこのデータが存在することを確認したい場合に意味があります。

Item Appを例に取り、ユーザーの視点から問題に直面しましょう。

/ items

ユーザーが/itemsに移動すると、最初に表示されるものは何もありません。これは、ユーザーがコンポーネントに到達する前に、リゾルバーがすべてのアイテムをフェッチしているためです。リゾルバーがAPIからすべてのアイテムをフェッチした後にのみ、ユーザーはItemsComponentに委任されます。

しかし、これは必須ではなく、ユーザーエクスペリエンスも悪いです。

ユーザーを直接ItemsComponentに委任しないのはなぜですか。たとえば、たくさんのボタンとおしゃれなCSSを備えた素晴らしいテーブルがある場合、ServiceがAPIからデータをフェッチしている間に、これらの要素をレンダリングする時間をアプリに与えることができます。データがロードされていることをユーザーに示すには、データが到着するまで、テーブル内にニースprogress-spinner(-bar)を表示します。

/ items/create

こっちも一緒。ユーザーは、ResolverCreateComponentに到達する前に、APIからすべてのデータをフェッチするまで待機する必要があります。これは、ユーザーだけがアイテムを作成できる場合にのみ、すべてのアイテムをフェッチするのはやり過ぎであることは明らかです。したがって、Resolverは必要ありません。

/ items /:itemId/edit

それはリゾルバーにとって最高の場所です。ユーザーは、アイテムが存在する場合にのみ、このコンポーネントにルーティングできる必要があります。しかし、子ルートの1つがアクティブ化されたときにすべてのアイテムをフェッチするようにリゾルバーを定義すると、ユーザーは以前のルートと同じように悪いユーザーエクスペリエンスに直面します。

解決

ルートからItemsResolverを削除します。

const routes: Routes = [
  {
      path: '',
      component: ItemsComponent,
      children: [
        {
          path: '',
          component: ItemsListComponent,
          data: {
            breadcrumb: 'Items'
          },
        },
        {
          path: 'create',
          component: CreateItemComponent,
          data: {
            breadcrumb: 'Create Item'
          },
        },
        {
          path: ':itemId/edit',
          component: EditOrArchiveItemComponent,
          data: {
            breadcrumb: 'Editing Item :item.name'
          },
          resolve: {
            item: ItemResolverService
          }
        }
      ]
  }
];

キャッシュのように最後にフェッチされたアイテムを保持するBehaviorSubjectをItemsServiceに定義できます。ユーザーがcreateまたはeditからItemsComponentにルーティングするたびに、アプリはキャッシュされたアイテムを即座にレンダリングし、その間、サービスはバックグラウンドでアイテムを同期できます。

私は小さな実用的なサンプルアプリを実装しました: https://stackblitz.com/github/SplitterAlex/stackoverflow-48640721

結論

Resolverは、アイテム編集ルートでのみ意味があります。

Resolverは慎重に使用してください。

10
SplitterAlex