我正在尝试使用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);
});
}
}
为什么我会收到此错误?怎么解决呢?
答案 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 { }
```