Search code examples
angulargraphqlapollo

Angular graphql with Apollo


I' m trying to integrate Angular 8.2.3 with graphql using Apollo client, but getting the folowing error:

ERROR NullInjectorError: StaticInjectorError(AppModule)[ProductsListComponent -> Apollo]: 
  StaticInjectorError(Platform: core)[ProductsListComponent -> Apollo]:

app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { GraphQLModule } from './graphql.module';
import { HttpClientModule } from '@angular/common/http';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    BrowserAnimationsModule,
    GraphQLModule,
    HttpClientModule
  ],
  providers: [
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

app.routing.ts

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';


const routes: Routes = [
  {
    path: '',
    loadChildren: () => import(`./pages/secure/secure.module`).then(m => m.SecureModule),
  },
  { path: '', redirectTo: 'secure', pathMatch: 'full' },
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

secure.module.ts

import { AngularMaterialModule } from './../angular-material/angular-material.module';
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { SecureRoutingModule } from './secure-routing.module';
import { SecureComponent } from './secure.component';


@NgModule({
  declarations: [SecureComponent],
  imports: [
    CommonModule,
    AngularMaterialModule,
    SecureRoutingModule,
  ]
})
export class SecureModule { }

secure.routing.ts

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { Routes, RouterModule } from '@angular/router';
import { SecureComponent } from './secure.component';


const routes: Routes = [
  {
    path: '',
    component: SecureComponent,
    children: [
      {
        path: '',
        redirectTo: 'products',
        pathMatch: 'full',
      },
      {
        path: 'products',
        loadChildren: () => import('../products/products.module').then(m => m.ProductsModule),
        data: { title: 'Products' }
      },
    ]
  }
];

@NgModule({
  imports: [RouterModule.forChild(routes)],
  exports: [RouterModule]
})
export class SecureRoutingModule { }

graphql.module

import {NgModule} from '@angular/core';
import {APOLLO_OPTIONS} from 'apollo-angular';
import {ApolloClientOptions, InMemoryCache} from '@apollo/client/core';
import {HttpLink} from 'apollo-angular/http';

const uri = 'myUri'; // <-- add the URL of the GraphQL server here
export function createApollo(httpLink: HttpLink): ApolloClientOptions<any> {
  return {
    link: httpLink.create({uri}),
    cache: new InMemoryCache(),
  };
}

@NgModule({
  providers: [
    {
      provide: APOLLO_OPTIONS,
      useFactory: createApollo,
      deps: [HttpLink],
    },
  ],
})
export class GraphQLModule {}

product.module.ts

import { GraphQLModule } from './../../graphql.module';
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ProductsRoutingModule } from './products-routing.module';
import { ProductsListComponent } from './products-list/products-list.component';

@NgModule({
  imports: [
    CommonModule,
    ProductsRoutingModule,
    GraphQLModule
  ],
  declarations: [ProductsListComponent],
})
export class ProductsModule { }

product.routing.module.ts

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule, Routes } from '@angular/router';
import { ProductsListComponent } from './products-list/products-list.component';

const routes: Routes = [
  {
    path: '',
    component: ProductsListComponent,
  },
];

@NgModule({
  declarations: [],
  imports: [RouterModule.forChild(routes)],
  exports: [RouterModule]
})
export class ProductsRoutingModule { }

product.list.component.ts

import { Component, OnInit } from '@angular/core';
import { Apollo, gql } from 'apollo-angular';

const productsQuery = gql`
query products {
items {
  name
  description
  slug
  featuredAsset {
    name
    }
  assets {
    name
    preview
    }
  }
}
`;
@Component({
  selector: 'app-products-list',
  templateUrl: './products-list.component.html',
  styleUrls: ['./products-list.component.scss']
})

export class ProductsListComponent implements OnInit {

  constructor(private apollo: Apollo) { }

  ngOnInit() {
    this.apollo
      .watchQuery({
        query: productsQuery
      })
      .valueChanges.subscribe((result: any) => {
        console.log(result);
      });
  }

}


Why I am getting this error? what is the way to fix it?


Solution

  • I end up withe the following configuration:

    // package.json

    {
      "name": "camel",
      "version": "0.0.0",
      "scripts": {
        "ng": "ng",
        "start": "ng serve",
        "build": "ng build",
        "test": "ng test",
        "lint": "ng lint",
        "e2e": "ng e2e",
        "gq": "graphql-codegen --config codegen.yml"
      },
      "private": true,
      "dependencies": {
        "@angular/animations": "~9.1.0",
        "@angular/cdk": "^9.2.1",
        "@angular/common": "~9.1.0",
        "@angular/compiler": "~9.1.0",
        "@angular/core": "~9.1.0",
        "@angular/fire": "^6.0.0",
        "@angular/forms": "~9.1.0",
        "@angular/material": "^9.2.1",
        "@angular/platform-browser": "~9.1.0",
        "@angular/platform-browser-dynamic": "~9.1.0",
        "@angular/router": "~9.1.0",
        "@apollo/client": "^3.0.0",
        "apollo-angular": "^2.0.4",
        "body-parser": "^1.19.0",
        "express": "^4.17.1",
        "firebase": "^7.14.4",
        "graphql": "^15.0.0",
        "hammerjs": "^2.0.8",
        "localforage": "^1.5.0",
        "lodash": "^4.17.20",
        "ngx-spinner": "^9.0.2",
        "node-fetch": "^2.6.0",
        "nodemon": "^2.0.4",
        "rxjs": "~6.5.4",
        "tslib": "^1.10.0",
        "zone.js": "~0.10.2"
      },
      "devDependencies": {
        "@angular-devkit/architect": "~0.900",
        "@angular-devkit/build-angular": "~0.901.0",
        "@angular/cli": "~9.1.0",
        "@angular/compiler-cli": "~9.1.0",
        "@angular/language-service": "~9.1.0",
        "@graphql-codegen/cli": "1.13.5",
        "@graphql-codegen/introspection": "1.13.5",
        "@graphql-codegen/typescript": "1.13.5",
        "@graphql-codegen/typescript-apollo-angular": "1.13.5",
        "@graphql-codegen/typescript-operations": "1.13.5",
        "@types/hammerjs": "^2.0.36",
        "@types/jasmine": "~3.5.0",
        "@types/jasminewd2": "~2.0.3",
        "@types/node": "^12.11.1",
        "codelyzer": "^5.1.2",
        "firebase-tools": "^8.0.0",
        "fuzzy": "^0.1.3",
        "inquirer": "^6.2.2",
        "inquirer-autocomplete-prompt": "^1.0.1",
        "jasmine-core": "~3.5.0",
        "jasmine-spec-reporter": "~4.2.1",
        "karma": "~4.4.1",
        "karma-chrome-launcher": "~3.1.0",
        "karma-coverage-istanbul-reporter": "~2.1.0",
        "karma-jasmine": "~3.0.1",
        "karma-jasmine-html-reporter": "^1.4.2",
        "protractor": "~5.4.3",
        "ts-node": "^8.10.2",
        "tslint": "~6.1.0",
        "typescript": "~3.8.3"
      }
    }
    
    

    //graphql.module.ts

    import { APOLLO_OPTIONS } from 'apollo-angular';
    import { HttpLink } from 'apollo-angular/http';
    import { InMemoryCache, ApolloLink, ApolloClient, ApolloClientOptions } from '@apollo/client/core';
    import { setContext } from '@apollo/client/link/context';
    import { NgModule } from '@angular/core';
    import { HttpClientModule } from '@angular/common/http';
    import { onError } from '@apollo/client/link/error';
    import { MatSnackBar } from '@angular/material/snack-bar';
    
    const uri = 'my-graphql-uri';
    const token = 'AUTH-TOKEN';
    const sessionStorageToken = 'secretToken';
    export function provideApollo(httpLink: HttpLink, matSnackBar: MatSnackBar): ApolloClientOptions<any> {
      const basic = setContext((operation, context) => ({
        headers: {
          Accept: 'charset=utf-8'
        }
      }));
      const localMatSnackbar = matSnackBar;
    
      const afterwareLink = new ApolloLink((operation, forward) => {
        return forward(operation).map(response => {
          const { response: { headers } } = operation.getContext();
          if (headers) {
            headers.keys().map((key) => {
              if (key.toUpperCase() === token) {
                const token = headers.get(key);
                if (token) {
                  const existingToken = sessionStorage.getItem(sessionStorageToken);
                  if (!existingToken) {
                    sessionStorage.setItem(sessionStorageToken, token);
                  }
                }
              }
            })
          }
          return response;
        })
      })
      const authToken = sessionStorage.getItem(sessionStorageToken);
      const auth = setContext((operation, context) => (
        {
          headers: {
            Authorization: `Bearer ${authToken}`
          },
        }
      ));
    
      const errorLink = onError(({ graphQLErrors, networkError }) => {
        if (graphQLErrors) {
          graphQLErrors.map(({ message, locations, path }) => {
            localMatSnackbar.open(`from global module: ${message} on method: ${path[0]}`, 'DISMISS', {
              duration: 20000,
              verticalPosition: 'bottom',
              horizontalPosition: 'center',
              panelClass: 'error-snack-bar'
            });
            window.location.reload()
          }
          );
        }
    
        if (networkError) {
    
          console.log(`[Network error]: ${networkError}`)
        };
      });
    
      const link = ApolloLink.from([
        errorLink,
        basic,
        afterwareLink,
        auth,
        httpLink.create({ uri })
      ]);
      const cache = new InMemoryCache();
    
      return {
        link,
        cache,
        defaultOptions: {
          watchQuery: {
            errorPolicy: 'all'
          }
        }
      }
    }
    
    @NgModule({
      exports: [
        HttpClientModule,
      ],
      providers: [{
        provide: APOLLO_OPTIONS,
        useFactory: provideApollo,
        deps: [HttpLink, MatSnackBar]
      }]
    })
    export class GraphQLModule { }
    
    

    // app.module.ts

    import { BrowserModule } from '@angular/platform-browser';
    import { NgModule } from '@angular/core';
    
    import { AppRoutingModule } from './app-routing.module';
    import { AppComponent } from './app.component';
    import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
    import { GraphQLModule } from './graphql.module';
    import { HttpClientModule } from '@angular/common/http';
    import { MatSnackBarModule } from '@angular/material/snack-bar';
    
    
    
    @NgModule({
      declarations: [
        AppComponent
      ],
      imports: [
        BrowserModule,
        AppRoutingModule,
        BrowserAnimationsModule,
        GraphQLModule,
        HttpClientModule,
        MatSnackBarModule
      ],
      bootstrap: [AppComponent]
    })
    export class AppModule { }
    
    

    //secure-routing.module.ts

    import { NgModule } from '@angular/core';
    import { Routes, RouterModule } from '@angular/router';
    import { SecureComponent } from './secure.component';
    
    
    const routes: Routes = [
      {
        path: '',
        component: SecureComponent,
        children: [
          {
            path: '',
            redirectTo: 'products',
            pathMatch: 'full',
          },
          {
            path: 'products',
            loadChildren: () => import('../products/products.module').then(m => m.ProductsModule),
            data: { title: 'Products' }
          },
        ]
      }
    ];
    
    @NgModule({
      imports: [RouterModule.forChild(routes)],
      exports: [RouterModule]
    })
    export class SecureRoutingModule { }
    
    

    //secure.module.ts

    import { ProductPipePipe } from './../../shared/pipes/product-pipe.pipe';
    import { NgModule } from '@angular/core';
    import { CommonModule } from '@angular/common';
    import { SecureRoutingModule } from './secure-routing.module';
    import { SecureComponent } from './secure.component';
    import { MatToolbarModule } from '@angular/material/toolbar';
    import { MatIconModule } from '@angular/material/icon';
    import { MatSidenavModule } from '@angular/material/sidenav';
    import { MatListModule } from '@angular/material/list';
    import { MatBadgeModule } from '@angular/material/badge';
    import { MatDialogModule } from '@angular/material/dialog';
    import { CartDetailsComponent } from '../cart/cart-details/cart-details.component';
    import { HeaderModule } from '../header/header.module';
    
    
    @NgModule({
      declarations: [SecureComponent, CartDetailsComponent, ProductPipePipe],
      imports: [
        CommonModule,
        SecureRoutingModule,
        MatToolbarModule,
        MatIconModule,
        MatSidenavModule,
        MatListModule,
        MatBadgeModule,
        MatDialogModule,
        HeaderModule
      ],
      entryComponents: [CartDetailsComponent]
    })
    export class SecureModule { }
    
    
    ```