与阿波罗角graphql

时间:2020-09-22 10:23:02

标签: angular graphql apollo

我正在尝试使用Apollo客户端将Angular 8.2.3与graphql集成,但是出现以下错误:

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);
      });
  }

}


为什么我会收到此错误?怎么解决呢?

2 个答案:

答案 0 :(得分:0)

在这里查看我的配置,可能会对您有所帮助。我正面临类似的问题。

我有一个有效的订阅

https://github.com/stevelaclasse/Apollo_Angular_GraphQl_Start

这是我的配置:

grapql.module.ts

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

import { split } from 'apollo-link';
import { getMainDefinition } from 'apollo-utilities';
import {WebSocketLink} from 'apollo-link-ws';
//import { HttpLink } from 'apollo-angular-link-http';  //Documentation use this import, but Data wasn't with it
//so i use the oder one up


const uri = 'http://localhost:8080/graphql'; // <-- add the URL of the GraphQL server here

export function createApollo(httpLink: HttpLink): ApolloClientOptions<any> {
//Create a HttpLink
  const http = httpLink.create({uri})

// Create a WebSocket link, subscription link
const ws = new WebSocketLink({
  uri: `ws://localhost:8080/subscriptions`,
  options: {
    reconnect: true
  }
});

const link = split(
// split based on operation type
({ query }) => {
  const definition = getMainDefinition(query);
  return definition.kind === 'OperationDefinition' && definition.operation === 'subscription';
},
ws,
http as any //need it
); 

  

  return {
    link: link as any, //need it , replace by link:http as any , if you want to ignore the subscription
    cache: new InMemoryCache(),
  };
}

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

app.component.ts

import { Component,OnInit } from '@angular/core';
import {Apollo} from 'apollo-angular';
import gql from 'graphql-tag';
import {Location,MyQuery,MyMutation,MySubscription, LocationInput} from './types'
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';


@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit{




constructor (private apollo:Apollo){}
  //Token for authentification, just a parameter for our Queries, Mutation and Subscription
  sso: string

  //Location for Mutation
  location : Location

  //Locations for Subscriptions
  allSubscribedLocations: Location[]  //Raw Data Array received from server
  allSubscribedLocationsArray = [] //converted data into Array of Location




//Location for the Queries
  newLocations: Location[]; //for Query 1
  Locations: Observable<Location[]>; //For Query 2
  allLocations : Location[] //for Query 3

  myLocationInput : LocationInput


  ngOnInit() {

this.sso = ""  //value of the Token
//Query

//Query 1
 this.apollo.query<MyQuery>({
  query: gql`
  query locations($sso: String!){
    allLocations(ssoToken: $sso)
    {
      id,
      name
    }
  }`,
  variables: {
    sso: this.sso
  }
}).subscribe(({data}) => {
  this.newLocations = data.allLocations;
  console.log('Data received Query 1: ' , data.allLocations);
}); 


//Query 2
this.Locations = this.apollo.watchQuery<MyQuery>({
  query: gql`
  query locations($sso: String!){
    allLocations(ssoToken: $sso)
    {
      id,
      name
    }
  }`,
  variables: {
    sso: this.sso
  }
}).valueChanges.pipe(
  map(result => result.data.allLocations)
); 


//Query 3
this.apollo.watchQuery<MyQuery>({
  query: gql`
  query locations($sso: String!){
    allLocations(ssoToken: $sso)
    {
      id,
      name
    }
  }`,
  variables: {
    sso: this.sso
   },
}).valueChanges.subscribe(({data})=>{
  this.allLocations = data.allLocations;
  console.log('Data received Query 3: ' , data.allLocations);
}); 



//Mutation

this.myLocationInput = {name:"Par"}

this.apollo.mutate<MyMutation>({
  mutation : gql`
  mutation location($sso: String!,$myLocationInput:LocationInput!){
    createLocation(ssoToken: $sso, locationInput:$myLocationInput)
    {
      id,
      name
    }
  }`,
  variables: {
    sso: this.sso,
    myLocationInput:this.myLocationInput
  }
} 
  
).subscribe(({ data }) => {
  this.location = data.createLocation;
  console.log('added data', data.createLocation);
},(error) => {
  console.log('there was an error sending the query', error);
});




//Subscription
this.apollo.subscribe({
  query : gql`
  subscription subscribedLocations($sso: String!){
    subscribeAllLocations(ssoToken: $sso)
    {
      id,
      name
    }
  }`,
  variables: {
    sso: this.sso
  }
}).subscribe(res => {
  this.allSubscribedLocations = (res.data as any );
  
  this.allSubscribedLocationsArray = []

  for( let i in this.allSubscribedLocations) {  
  this.allSubscribedLocationsArray.push(this.allSubscribedLocations[i]);
}

this.allSubscribedLocationsArray = this.allSubscribedLocationsArray[0]
  console.log("Data Recieved From Subscription:",this.allSubscribedLocationsArray)
  
}); 


  }
  title = 'frontend-test';
}

这是我的类型

types.ts

import { type } from 'os'

export type Location = {
id: number;
name: string;
serial: number;
}

export type LocationInput = {
    name: string;
    }

export type MyQuery ={
    allLocations: Location[];
}

export type MyMutation ={
    createLocation:Location;
}

export type MySubscription = {
    subscribedLocations : Location[]
}

您可以在app.component.html中看到结果

app.component.html

<!-- You can put it anywhere in the file app.component.html
I have put it in the Footer
-->

<p> Section for the result of the Graphql Server for Query 1</p>
    <br/>
      <table>
      <div *ngFor = "let oneLocation of newLocations">
        <br/>
        <tr> <td>{{oneLocation.name}}</td> <td>{{oneLocation.id}}</td></tr>
      </div>
      </table> 

      
    <p> Section for the result of the Graphql Server for Query 2</p>
    <br/>
     <table>
      <div *ngFor = "let oneLocation of Locations | async">
        <br/>
        <tr> <td>{{oneLocation.name}}</td> <td>{{oneLocation.id}}</td></tr>
      </div>
      </table>

      <p> Section for the result of the Graphql Server for Query 3</p>
    <br/>
     <table>
      <div *ngFor = "let oneLocation of allLocations">
        <br/>
        <tr> <td>{{oneLocation.name}}</td> <td>{{oneLocation.id}}</td></tr>
      </div>
      </table> 
    
   

       <p> Section for the result of the Graphql Server for Mutation</p>
     <br/>
    <p> New Location : id: {{location.id}}, name: {{location.name}}</p>
   


  <p> Section for the result of the Graphql Server for Subscription</p>
    <br/>
     <table>
      <div *ngFor = "let oneLocation of allSubscribedLocationsArray">
        <br/>
        <tr> <td>{{oneLocation.name}}</td> <td>{{oneLocation.id}}</td></tr>
      </div>
      </table> 

答案 1 :(得分:0)

我最终得到以下配置:

// 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 { }


```