kamilkisiela / apollo-angular

A fully-featured, production ready caching GraphQL client for Angular and every GraphQL server 🎁
https://apollo-angular.com
MIT License
1.5k stars 311 forks source link

Serialized cache from server is not used when InMemoryCache `possibleTypes` is set #1927

Closed denisyilmaz closed 1 year ago

denisyilmaz commented 1 year ago

We have an angular application with apollo + codegen. We generate the possibleTypes with these settings:

overwrite: true
schema: "http://example.ddev.site/api"
documents: "projects/frontend/**/*.graphql"
config:
  addExplicitOverride: true
  useExplicitTyping: true
generates:
  generated/possibleTypes.graphql-gen.ts:
    plugins:
      - "fragment-matcher"

Which is working great in a non-universal usage. We now wanted to enable Angular Universal and followed the instructions to restore the InMemoryCache of Apollo on the server with Transferstate:

  if (isBrowser) {
    const state = transferState.get<any>(STATE_KEY, null);
    cache.restore(state);
  } else {
    transferState.onSerialize(STATE_KEY, () => {
      return cache.extract();
    });
    // Reset cache after extraction to avoid sharing between requests
    cache.reset();
  }

Debugging the cache.extract() object i can confirm that the cache is successfully restored on the browser having all Queries and fragments in it.

Unfortunately the apollo is not honoring the cache and therefore re-fetching the queries in the browser. Here an example resolver we use:

@Injectable({
  providedIn: 'root'
})
export class PageResolver implements Resolve<ApolloQueryResult<GetPageQuery>> {
  constructor(private getPageGQL: GetPageGQL) { }
  resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<ApolloQueryResult<GetPageQuery>> {
    return this.getPageGQL.fetch({
      id: route.data['id'], siteId: route.data['siteId'], entryId: +route.data['id'], level: +route.data['level']
    }, { fetchPolicy: "cache-first" });
  }
}

In the resolver i tried to access the cache with readQuery() to check what cache is available on resolver call and this was resulting in null. Checking the full cache object though showed the entry to be present. So for whatever reason apollo is not using the cached query but rather fetches frech from the server.

Now, for a test I removed the possibleTypes object from the InMemoryCache config and voilà, the frontend does not load the query again. Of course this is not working now as the fragments are not matched (therefor my frontend is broken) but it shows that the problem is somewhere to find in possibleTypes.

Is this a known limitation/bug with apollo? I'm rather clueless here how to get rid of that issue. Anyone experience something similar?

- @angular/cli@^15.1.0
- @angular/core@^15.1.0
- @apollo/client@^3.7.7
- apollo-angular@^4.2.1
- graphql@^16.6.0
- typescript@~4.9.4
- @graphql-cli/codegen@^3.0.0
- @graphql-codegen/fragment-matcher@^4.0.0
denisyilmaz commented 1 year ago

... I found the issue. A fragment of mine was using a field twice (with an alias on second usage). The subselection did not had a id in it. Adding the id the query is not fetched a second time on the browser.

For future reference and others who might have similar issues, here an example query that was causing the cache to fail:

fragment Anchor on bodyContent_Field {
  __typename
  showAnchor
  anchorId
  label
}
fragment BodyContent on bodyContent_Field {
  __typename
  id
  title
  content
}

fragment DefaultPage on defaultPage_Entry {
  id
  siteId
  title
  anchors: bodyContent {
    ...Anchor
  }
  bodyContent {
    ...BodyContent
  }
}

query GetPageTest($uri: String) {
  entry(uri: [$uri]) {
    __typename
    ...DefaultPage
    parent {
      id
      siteId
      title
      uri
    }
    children {
      id
      siteId
      title
      uri
    }
  }
}

The fixed version (adding a id to Anchorfragment):

fragment Anchor on bodyContent_Field {
  __typename
  id
  showAnchor
  anchorId
  label
}