使用Apollo2.0在Angular中订阅GraphQL

来自菜鸟教程
跳转至:导航、​搜索

订阅是一个强大的 GraphQL 功能,它可以使用前端的 WebSockets 等技术轻松地从后端服务器实时接收更新。 在这篇快速文章中,我们将介绍如何使用 Apollo Client 2.0 在 Angular 中设置订阅前端。

对于本文的示例,我们假设您已经启动并运行了 GraphQL 服务器,并且在服务器端正确设置了订阅。 您可以使用 graphql-yoga 等工具轻松设置您自己的 GraphQL 服务器,或者您可以使用 Graphcool 框架 并让 Graphcool 托管您的 GraphQL 服务。

所需的包

我们假设您要构建一个同时具有 GraphQL 订阅以及常规查询和突变的应用程序,因此我们将使用常规 HTTP 链接和 WebSocket 链接进行设置。 splitapollo-link 包中的一个实用程序,可以轻松地将请求定向到正确的链接。

首先,我们需要一大堆包来让一切正常工作:apollo-angularapollo-angular-link-http、apollo-cache-inmemory[X154X ], apollo-link, apollo-link-ws, apollo-utilities, graphql, graphql-tag[X268X ]、apollo-clientsubscriptions-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) .
  • 如果订阅数据为空,我们只返回以前的数据,但如果不是,我们构造并返回一个新对象,其中包含我们以前的数据和我们的新数据。