Apollo-angular: [Idea] Create Apollo on Dependency Injection level

Created on 11 Jan 2018  路  16Comments  路  Source: kamilkisiela/apollo-angular

TL;DR: An InjectionToken for options of the default ApolloClient as an alternative way to initialize Apollo, a replacement for Apollo.create (both will be supported).

An example:

import {NgModule} from '@angular/core';
import {HttpClientModule} from '@angular/common/http';
import {ApolloOptionsToken, ApolloModule} from 'apollo-angular';
import {HttpLink, HttpLinkModule} from 'apollo-angular-link-http';
import {InMemoryCache} from 'apollo-cache-inmemory';

@NgModule({
  imports: [HttpClientModule, ApolloModule, HttpLinkModule],
  providers: [
    {
      provide: ApolloOptionsToken,
      useFactory: httpLink => {
        return {
          link: httpLink.create({uri: 'https://api.example.com/graphql'}),
          cache: new InMemoryCache(),
        };
      },
      deps: [HttpLink],
    },
  ],
})
class AppModule {}

Details

Why?

Why we didn't do it in a first place? For one reason, it's way more complex and not all of Angular Developers are familiar with how Dependency Injection's work and how to provide a service other than just put a class in an Array (NgModules.providers). Apollo.create is way simpler and easier to get started with Apollo for everyone and I think it's the best approach.

Do you like it? I already prepared a branch with those changes.

idea

Most helpful comment

I am still not sure how to use this module with feature modules. The Client has not been defined yet error comes up for me with lazy-loaded feature modules (via angular router) but fine when under imports.

Edit: Ended up using the method described in https://alligator.io/angular/providers-shared-modules/

All 16 comments

Is this an approach to have an apollo client before everything else?

If yes, I do it like this at the moment: https://github.com/apollographql/apollo-angular/issues/454#issuecomment-355309077 (angular's own way of doing this)

If not, what benefit does this have?

By using ApolloOptionsToken or whatever name it would have, you define options that will be passed to Apollo service. That Token is being created at the same time as any other Token in your Angular app, that's it works in Angular.

About:

before everything else

Angular's DI works this way. It gets all the providers, token, etc that have been provided. If A service depends on B service than B will be created first. So if you want

The error mentioned in #454 says Client has not been defined yet. It means that Apollo.create() hasn't been used at all or has been used but after a service or component used Apollo.watchQuery(), Apollo.mutate() etc.

I am still not sure how to use this module with feature modules. The Client has not been defined yet error comes up for me with lazy-loaded feature modules (via angular router) but fine when under imports.

Edit: Ended up using the method described in https://alligator.io/angular/providers-shared-modules/

@kamilkisiela any idea when this will be merged to master? I have been running into same issue as @patricknazar with lazy loading routes returning Client has not been defined yet

@nate-snaptech I have gotten past the issue by creating a shared module using the forRoot technique described in the link I posted. You include it into your app module with .forRoot () and without it in the lazy loaded module. The shared module has a constructor that injects apollo and that's where I create the Client. Works well.

@patricknazar figured it out after reading through https://alligator.io/angular/providers-shared-modules/. The issue was resolved from removing ApolloModule imports/exports on sub modules. Perhaps apollo client was being overwritten or reset upon duplicate imports on lazy loaded modules. Thanks for the article

Here is an example repo for anyone wanting to see how this works.

@kamilkisiela, first, thanks for all your work on Apollo Angular!

Q: Could Apollo Angular Modules also support something like "Feature Module State Composition" from NGRX?

Feature Module State Composition

// feature.module.ts
import { StoreModule, ActionReducerMap } from '@ngrx/store';

export const reducers: ActionReducerMap<any> = {
  subFeatureA: featureAReducer,
  subFeatureB: featureBReducer,
};

@NgModule({
  imports: [
    StoreModule.forFeature('featureName', reducers)
  ]
})
export class FeatureModule {}

I am a big fan of NGRX, however I feel like Apollo and GraphQL might be even better. While I understand you are working on getting them to work together I have been exploring apollo-link-state as a store for local values. I really like the fact it normalises the way data is fetched throughout the app.

As my app grows I ideally want the code for seperate sections to be lazy loaded.

Currently I have two local state variables, the theme selected and the search value set. While I want the theme globally available and am happy to load it in the root module not everyone would need the search query variable.

// my current Apollo Module just loaded in the root
export class GraphQLModule {
  constructor(
    private apollo: Apollo,
    private httpLink: HttpLink
  ) {
    const cache = new InMemoryCache();
    const remoteHttpLink = httpLink.create({uri});

    const stateLinkConfig = {
      cache,
        defaults: {
    ...themeDefault,
    ...searchQueryDefault
    },
      resolvers: {
        Mutation: {
        ...themeResolverMutation,
        ...searchQueryResolverMutation,
        }
      },
    };

    const stateLink = withClientState(stateLinkConfig as any);

    const apolloLink = ApolloLink.from([
      stateLink,
      remoteHttpLink,
    ] as any);

    apollo.create({
      link: apolloLink,
      cache: cache,
    } as any);
  }
}

I was thinking about using apollo.create(options, 'featureName'), I am just not sure this is the best approach.

Ideally I want a GraphQLBase module that would contain my theme state and my primary remoteHttpLink and then a SearchGraphQL module that would contain the search state. I would still want access to the base remoteHttpLink. So maybe I need to use schema stitching?

What are your thoughts? Would this fit into the work you are doing on modules.

Let me know if there is any specific way I can help out. I tried pulling the repo to play around but I was getting loads of typescript errors after using yarn link.

@vespertilian Here's an example of that: https://github.com/kamilkisiela/apollo-angular-lazy-modules

Also @vespertilian , if I remember correctly, when you StoreModule.forRoot({foo: fooReducer}), it creates an object, called state with foo as a property. Then StoreModule.forFeature('bar', reducers) puts bar property in that object. It means that you share the state throughout entire app.

@vespertilian Apollo works with lazy modules because Angular creates an injector for each lazy loaded modules and for app module itself. This injector has it's own instances of every service (sometimes, service tells injector to take it from parent injector - very short and general explanation). Apollo service contains clients inside its instance so every time injector creates Apollo it has its own information.

@vespertilian Right now it's impossible to has multiple caches in one ApolloClient and I don't think it will be ever available, it's just better to create multiple ApolloClients so they manage their own cache.

@kamilkisiela Thank you for your quick reply and the sample repository, it's very helpful.

So initially I did go down the route of creating multiple clients. The issue I had was that Apollo-Dev-Tools don't seem to play well with that setup so I figured I must have been doing it wrong. You don't get to see the store, or the queries you run, as I was really keen to use the dev-tools I backed off from this approach.

I noticed the fact you can create named clients in the Angular documentation apollo.create(options, name?) and there is an issue to Switch between multiple clients in Github for the apollo-client-devtools so maybe I should follow that up. The named clients seem to work better with the dev tools than multiple clients.

However reading this thread for the main apollo-client module seems to imply that schema stitching is the direction the team is heading.

I am going to look into schema stitching and see if that works, I will report back soon, any additional thoughts welcome.

@kamilkisiela similar discussion I added to over at apollo-link-state

I think I will see where that one goes.

is this going to happen and publish a new version?

@kamilkisiela
Hello Kamil

Referring to your repo above, am I correct if I say each lazily loaded module creates its own copy of Apollo Client?

Doesn鈥檛 this cause to have a separate cache per Apollo Client?

I鈥檓 facing a problem now which is I define Apollo Client on app level and I inject Apollo client into components inside lazily loaded module the query is not being stored in cache. So my assumption might be injecting Apollo client in lazily loaded modules might be creating a new instance of the client and hence a new cache.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

jiharal picture jiharal  路  5Comments

Pokerkoffer picture Pokerkoffer  路  3Comments

GlauberF picture GlauberF  路  6Comments

TobiasKrogh picture TobiasKrogh  路  4Comments

simonhaenisch picture simonhaenisch  路  3Comments