带有 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 客户端库创建带有角度参数的 graphql 查询