使用 Apollo 2.0 在 Angular 中创建 GraphQL 订阅

订阅是一项强大的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来设置订阅。
  • 订阅到更多 获取一个查询文档(我们的订阅查询)、变量(如果需要)、一个更新查询函数(从上一个查询中获取数据)和一个包含我们的订阅数据的对象(** 订阅数据** )。
  • 如果订阅数据为空,我们只返回以前的数据,如果不是,我们构造并返回一个新对象,该对象包含我们以前的数据和我们的新数据。
Published At
Categories with 技术
Tagged with
comments powered by Disqus