订阅是一项强大的GraphQL功能,使用前端的WebSockets等技术,可以轻松地实时接收来自后端服务器的更新。在这篇快速帖子中,我们将介绍如何使用Apollo Client 2.0在Apollo中设置订阅前端。
对于本文中的示例,我们将假设您已经启动并运行了一个GraphQL服务器,并在服务器端正确设置了订阅。您可以使用graphql-yoga,之类的工具轻松设置您自己的GraphQL服务器,也可以使用GraphCool framework并让GraphCool托管您的GraphQL服务。
所需的包
我们假设您想要构建一个既有GraphQL订阅又有常规查询和突变的应用程序,因此我们将使用常规的HTTP链接和WebSocket链接进行设置。Split是Apollo-link包中的一个实用程序,它可以很容易地将请求定向到正确的链接。
首先,我们需要一大堆包来让一切正常运行:Apollo-Angel、Apollo-Angel-link-http、Apollo-缓存-in Memory、Apollo-link、Apollo-link-ws、Apollo-实用程序、GraphQL、GraphQL-Tag、Apollo-Client和订阅-Transport-ws。
让我们使用NPM或Yarn一次性安装它们:
1$ 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
2
3# or, using Yarn:
4$ 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
设置
我们将创建一个模块与我们的阿波罗配置和链接。让我们称之为GraphQLConfigModule:
1[label apollo.config.ts]
2import { NgModule } from '@angular/core';
3import { HttpClientModule, HttpClient } from '@angular/common/http';
4
5import { Apollo, ApolloModule } from 'apollo-angular';
6import { HttpLinkModule, HttpLink } from 'apollo-angular-link-http';
7import { InMemoryCache } from 'apollo-cache-inmemory';
8import { split } from 'apollo-link';
9import { WebSocketLink } from 'apollo-link-ws';
10import { getMainDefinition } from 'apollo-utilities';
11@NgModule({
12 exports: [HttpClientModule, ApolloModule, HttpLinkModule]
13})
14export class GraphQLConfigModule {
15 constructor(apollo: Apollo, private httpClient: HttpClient) {
16 const httpLink = new HttpLink(httpClient).create({
17 uri: 'REGULAR_ENDPOINT'
18 });
19const subscriptionLink = new WebSocketLink({
20 uri:
21 '___SUBSCRIPTION_ENDPOINT___',
22 options: {
23 reconnect: true,
24 connectionParams: {
25 authToken: localStorage.getItem('token') || null
26 }
27 }
28});
29
30const link = split(
31 ({ query }) => {
32 const { kind, operation } = getMainDefinition(query);
33 return kind === 'OperationDefinition' && operation === 'subscription';
34 },
35 subscriptionLink,
36 httpLink
37);
38
39apollo.create({
40 link,
41 cache: new InMemoryCache()
42});
以下是关于我们的配置需要注意的几点:
- 在我们的模块的构造函数中,我们首先使用appollo-arting-link-http包定义一个HTTP链接。在内部,我们的链接使用ANGLE‘s HttpClient.
- 然后我们还定义了与我们的GraphQL服务器的订阅终结点的WebSocket链接。在这里您可以看到,我们还从本地存储获得了一个授权令牌来验证我们的WebSocket连接。如果您的服务器只接受经过身份验证的订阅请求,则需要此选项。
- 接下来,我们使用拆分实用程序,它接受查询并返回一个布尔值。在这里,我们使用另一个名为getMainDefinition的实用程序来检查查询,以提取查询类型和操作名称。从那里,我们可以检查查询是否为订阅,如果是,则使用订阅链接。否则,该请求将使用该HTTP链接。
- 最后,我们只需创建链接,并使用InMory缓存进行缓存。
此时,如果TypeScript编译器出现类似Cannot find name 'AsyncIterator'
的问题,您可以将esnext添加到 tsconfig.json 文件中的lib列表中:
1[label tsconfig.json]
2{
3 "compileOnSave": false,
4 "compilerOptions": {
5 "outDir": "./dist/out-tsc",
6 ...,
7 "lib": [
8 "es2017",
9 "dom",
10 "esnext"
11 ]
12 }
13}
有了这个配置模块,剩下的设置工作就是在我们的主应用程序模块中导入该模块:
1[label app.module.ts]
2import { BrowserModule } from '@angular/platform-browser';
3import { NgModule } from '@angular/core';
4
5import { GraphQLConfigModule } from './apollo.config';
6import { AppComponent } from './app.component';
♪简单订阅
我们现在准备好在前台订阅不同的活动。下面是一个简单的订阅查询,我们将运行它来自动接收在服务器上创建的新的待办事项:
1subscription newTodos {
2 Todo(filter: { mutation_in: [CREATED] }) {
3 node {
4 title
5 description
6 completed
7 }
8 }
9}
对于一个真正的应用程序,您可能希望在服务中分离您的订阅逻辑,但为了简单起见,我们在这里将做所有事情都是我们的应用程序组件的类:
1[label app.component.ts]
2import { Component, OnInit, OnDestroy } from '@angular/core';
3import { Subscription } from 'rxjs/Subscription';
4
5import { Apollo } from 'apollo-angular';
6import gql from 'graphql-tag';
7const subscription = gqlsubscription newTodos {
8 Todo(filter: { mutation_in: [CREATED] }) {
9 node {
10 title
11 description
12 completed
13 }
14 }
15 };
16interface TodoItem {
17 title: string;
18 name: string;
19 completed: boolean;
20}
21@Component({ ... })
22export class AppComponent implements OnInit, OnDestroy {
23 todoSubscription: Subscription;
24 todoItems: TodoItem[] = [];
25 constructor(private apollo: Apollo) {}
26 ngOnInit() {
27 this.todoSubscription = this.apollo
28 .subscribe({
29 query: subscription
30 })
31 .subscribe(({ data }) => {
32 this.todoItems = [...this.todoItems, data.Todo.node];
33 });
34 }
请注意它与运行常规GraphQL查询非常相似。有了这个,我们的前端应用程序将自动接收新的待办事项。
我们可以在组件的模板中显示待办事项,如下所示:
1[label app.component.html]
2<ul>
3 <li *ngFor="let item of todoItems">{{ item.title }} - {{ item.description }}</li>
4</ul>
WatchQuery+订阅
到目前为止,我们的示例运行良好,但我们的应用程序只获得新的待办事项。我们一刷新,就会再次得到一个空的待办事项列表。
简单的解决方案是首先运行常规查询,然后使用订阅自动接收额外的todos。在初始watchQuery上使用subscribeToMore方法很容易做到:
1[label app.component.ts]
2import { Component, OnInit, OnDestroy } from '@angular/core';
3import { Subscription } from 'rxjs/Subscription';
4
5import { Apollo, QueryRef } from 'apollo-angular';
6import gql from 'graphql-tag';
7const subscription = gqlsubscription newTodos {
8 Todo(filter: { mutation_in: [CREATED] }) {
9 node {
10 title
11 description
12 completed
13 }
14 }
15 };
16const allTodosQuery = gqlquery getTodos {
17 allTodos {
18 title
19 description
20 completed
21 }
22 };
23interface TodoItem {
24 title: string;
25 description: string;
26 completed: boolean;
27}
28@Component({
29 selector: 'app-root',
30 templateUrl: './app.component.html',
31 styleUrls: ['./app.component.css']
32})
33export class AppComponent implements OnInit, OnDestroy {
34 todoSubscription: Subscription;
35 todoItems: TodoItem[] = [];
36 todoQuery: QueryRef<any>;
37 constructor(private apollo: Apollo) {}
38 ngOnInit() {
39 this.todoQuery = this.apollo.watchQuery({
40 query: allTodosQuery
41 });
42this.todoSubscription = this.todoQuery.valueChanges.subscribe(
43 ({ data }) => {
44 this.todoItems = [...data.allTodos];
45 }
46);
47
48this.setupSubscription(); }
49 setupSubscription() {
50 this.todoQuery.subscribeToMore({
51 document: subscription,
52 updateQuery: (prev, { subscriptionData }) => {
53 if (!subscriptionData.data) {
54 return prev;
55 }
56 const newTodo = subscriptionData.data.Todo.node;
57
58 return Object.assign({}, prev, {
59 allTodos: [...prev['allTodos'], newTodo]
60 });
61 }
62}); }
- 我们首先设置一个WatchQuery并订阅它的值Changes Observable,以在组件初始化时获取所有的Todo项。
- 然后我们通过在WatchQuery上使用SubscribeToMore来设置订阅。
- 订阅到更多 获取一个查询文档(我们的订阅查询)、变量(如果需要)、一个更新查询函数(从上一个查询中获取数据)和一个包含我们的订阅数据的对象(** 订阅数据** )。
- 如果订阅数据为空,我们只返回以前的数据,如果不是,我们构造并返回一个新对象,该对象包含我们以前的数据和我们的新数据。