带有 Apollo 的 Angular graphql

Posted

技术标签:

【中文标题】带有 Apollo 的 Angular graphql【英文标题】:Angular graphql with Apollo 【发布时间】:2021-01-08 11:05:33 【问题描述】:

我正在尝试使用 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);
      );
  




为什么我会收到此错误?有什么办法可以解决?

【问题讨论】:

尝试导入ApolloModule 什么意思?我在 product.module 中导入了 GraphQLModule,但它并没有解决问题 在 GraphQLModule 中导入 ApolloModul 没有 ApolloModul,我认为它在 Apollo >2 上被删除了 @camel 你有解决办法吗? 【参考方案1】:

看看我这里的配置,它可能对你有帮助。我也面临着类似的问题。

我有一个有效的订阅

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> 

【讨论】:

【参考方案2】:

我最终得到以下配置:

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

//安全路由.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  


```

【讨论】:

以上是关于带有 Apollo 的 Angular graphql的主要内容,如果未能解决你的问题,请参考以下文章

在 Angular Apollo Graphql 中访问突变结果

Apollo Angular 客户端总是使用缓存

模拟 graph-ql 请求

如何使用 apollo 客户端库创建带有角度参数的 graphql 查询

订阅未定义的错误处理 - Apollo 和 Angular 2

watchQuery (Angular-Apollo) 的缓存管理