使用Apollo2.0在Angular中订阅GraphQL
订阅是一个强大的 GraphQL 功能,它可以使用前端的 WebSockets 等技术轻松地从后端服务器实时接收更新。 在这篇快速文章中,我们将介绍如何使用 Apollo Client 2.0 在 Angular 中设置订阅前端。
对于本文的示例,我们假设您已经启动并运行了 GraphQL 服务器,并且在服务器端正确设置了订阅。 您可以使用 graphql-yoga 等工具轻松设置您自己的 GraphQL 服务器,或者您可以使用 Graphcool 框架 并让 Graphcool 托管您的 GraphQL 服务。
所需的包
我们假设您要构建一个同时具有 GraphQL 订阅以及常规查询和突变的应用程序,因此我们将使用常规 HTTP 链接和 WebSocket 链接进行设置。 split 是 apollo-link 包中的一个实用程序,可以轻松地将请求定向到正确的链接。
首先,我们需要一大堆包来让一切正常工作:apollo-angular、apollo-angular-link-http、apollo-cache-inmemory[X154X ], apollo-link, apollo-link-ws, apollo-utilities, graphql, graphql-tag[X268X ]、apollo-client 和 subscriptions-transport-ws。
让我们使用 npm 或 Yarn 一次性安装它们:
$ npm i apollo-angular apollo-angular-link-http apollo-cache-inmemory apollo-link apollo-link-ws apollo-utilities graphql graphql-tag apollo-client subscriptions-transport-ws # or, using Yarn: $ yarn add apollo-angular apollo-angular-link-http apollo-cache-inmemory apollo-link apollo-link-ws apollo-utilities graphql graphql-tag apollo-client subscriptions-transport-ws
设置
我们将使用我们的 Apollo 配置和链接创建一个模块。 我们称它为 GraphQLConfigModule:
阿波罗.config.ts
import { NgModule } from '@angular/core'; import { HttpClientModule, HttpClient } from '@angular/common/http'; import { Apollo, ApolloModule } from 'apollo-angular'; import { HttpLinkModule, HttpLink } from 'apollo-angular-link-http'; import { InMemoryCache } from 'apollo-cache-inmemory'; import { split } from 'apollo-link'; import { WebSocketLink } from 'apollo-link-ws'; import { getMainDefinition } from 'apollo-utilities'; @NgModule({ exports: [HttpClientModule, ApolloModule, HttpLinkModule] }) export class GraphQLConfigModule { constructor(apollo: Apollo, private httpClient: HttpClient) { const httpLink = new HttpLink(httpClient).create({ uri: 'REGULAR_ENDPOINT' }); const subscriptionLink = new WebSocketLink({ uri: '___SUBSCRIPTION_ENDPOINT___', options: { reconnect: true, connectionParams: { authToken: localStorage.getItem('token') || null } } }); const link = split( ({ query }) => { const { kind, operation } = getMainDefinition(query); return kind === 'OperationDefinition' && operation === 'subscription'; }, subscriptionLink, httpLink ); apollo.create({ link, cache: new InMemoryCache() });
关于我们的配置,有几点需要注意:
- 在我们模块的构造函数中,我们首先使用 apollo-angular-link-http 包定义一个 HTTP 链接。 在内部,我们的链接使用 Angular 的 HttpClient。
- 然后,我们还定义了一个带有 GraphQL 服务器订阅端点的 WebSocket 链接。 在这里您可以看到我们还从 localStorage 获取了一个授权令牌来验证我们的 WebSocket 连接。 如果您的服务器仅接受经过身份验证的订阅请求,则您将需要它。
- 接下来我们使用 split 实用程序,它接受查询并返回一个布尔值。 在这里,我们使用另一个名为 getMainDefinition 的实用程序检查查询,以提取查询类型和操作名称。 从那里,我们可以检查查询是否是订阅,如果是,则使用订阅链接。 否则,请求将使用 HTTP 链接。
- 最后我们简单地创建链接并使用 InMemoryCache 进行缓存。
此时,如果 TypeScript 编译器出现类似 Cannot find name 'AsyncIterator'
的问题,您可以将 esnext 添加到 tsconfig.json 文件中的库列表中:
tsconfig.json
{ "compileOnSave": false, "compilerOptions": { "outDir": "./dist/out-tsc", ..., "lib": [ "es2017", "dom", "esnext" ] } }
有了这个配置模块,我们的设置剩下要做的就是在我们的主应用程序模块中导入模块:
app.module.ts
import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { GraphQLConfigModule } from './apollo.config'; import { AppComponent } from './app.component';
简单订阅
我们现在准备在前端订阅不同的事件。 这是一个简单的订阅查询,我们将运行它来自动接收在服务器上创建的新待办事项:
subscription newTodos { Todo(filter: { mutation_in: [CREATED] }) { node { title description completed } } }
对于一个真正的应用程序,您可能希望在服务中解耦您的订阅逻辑,但为了简单起见,这里我们将做的一切都是我们的应用程序组件的类:
app.component.ts
import { Component, OnInit, OnDestroy } from '@angular/core'; import { Subscription } from 'rxjs/Subscription'; import { Apollo } from 'apollo-angular'; import gql from 'graphql-tag'; const subscription = gqlsubscription newTodos { Todo(filter: { mutation_in: [CREATED] }) { node { title description completed } } }; interface TodoItem { title: string; name: string; completed: boolean; } @Component({ ... }) export class AppComponent implements OnInit, OnDestroy { todoSubscription: Subscription; todoItems: TodoItem[] = []; constructor(private apollo: Apollo) {} ngOnInit() { this.todoSubscription = this.apollo .subscribe({ query: subscription }) .subscribe(({ data }) => { this.todoItems = [...this.todoItems, data.Todo.node]; }); }
请注意,它与运行常规 GraphQL 查询非常相似。 有了这个,我们的前端应用程序将自动接收新的待办事项。
我们可以在组件的模板中显示我们的待办事项:
app.component.html
<ul> <li *ngFor="let item of todoItems">{{ item.title }} - {{ item.description }}</li> </ul>
watchQuery + 订阅
到目前为止,我们的示例运行良好,但我们的应用程序只获得了新的待办事项。 一旦我们刷新,我们就会再次得到一个空的待办事项列表。
简单的解决方案是首先运行常规查询,然后使用订阅自动接收额外的待办事项。 在初始 watchQuery 上使用 subscribeToMore 方法很容易做到这一点:
app.component.ts
import { Component, OnInit, OnDestroy } from '@angular/core'; import { Subscription } from 'rxjs/Subscription'; import { Apollo, QueryRef } from 'apollo-angular'; import gql from 'graphql-tag'; const subscription = gqlsubscription newTodos { Todo(filter: { mutation_in: [CREATED] }) { node { title description completed } } }; const allTodosQuery = gqlquery getTodos { allTodos { title description completed } }; interface TodoItem { title: string; description: string; completed: boolean; } @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent implements OnInit, OnDestroy { todoSubscription: Subscription; todoItems: TodoItem[] = []; todoQuery: QueryRef<any>; constructor(private apollo: Apollo) {} ngOnInit() { this.todoQuery = this.apollo.watchQuery({ query: allTodosQuery }); this.todoSubscription = this.todoQuery.valueChanges.subscribe( ({ data }) => { this.todoItems = [...data.allTodos]; } ); this.setupSubscription(); } setupSubscription() { this.todoQuery.subscribeToMore({ document: subscription, updateQuery: (prev, { subscriptionData }) => { if (!subscriptionData.data) { return prev; } const newTodo = subscriptionData.data.Todo.node; return Object.assign({}, prev, { allTodos: [...prev['allTodos'], newTodo] }); } }); }
- 我们首先设置一个 watchQuery 并订阅它的 valueChanges observable 以在组件初始化时获取所有待办事项。
- 然后,我们在 watchQuery 上使用 subscribeToMore 设置订阅。
- subscribeToMore 接受一个查询文档(我们的订阅查询)、变量(如果需要)和一个更新查询函数,该函数从上一个查询中获取数据以及一个包含我们订阅数据的对象(subscriptionData) .
- 如果订阅数据为空,我们只返回以前的数据,但如果不是,我们构造并返回一个新对象,其中包含我们以前的数据和我们的新数据。