在 Angular 5 中使用 GraphQL

本贴最后更新于 1984 天前,其中的信息可能已经物是人非

原文链接:在 Angular 5 中使用 GraphQL

GraphQLFacebook 开发的一种数据查询语言,它在客户端和服务器之间提供了一个公共 API 来获取和操作数据。 它处理单个 URL 端点上的所有请求。 它是 REST 风格、更快的应用程序和改善开发者体验的替代品。下面将介绍如何创建 GraphQL API 服务器并在 Angular5 中使用 GraphQL

在创建 GraphQL API 服务器之前,我们需要了解 GraphQL 的一些重要术语。

类型定义对象的形状,客户机期望和字段是一个类型的基本单位,可以有值的字符串,整数,布尔,浮点数,id,或引用系统中的其他类型,甚至一个自定义标量。

查询指定如何允许客户端查询和检索已定义类型。

函数用于操作数据。 它用于创建、更新和删除操作。

模式将一切联系在一起。 它的类型,查询和函数的收集。

解析器是一组特定于应用程序的函数,它们根据模式中描述的查询和函数操作与底层数据存储进行交互

以下是这个项目的先决条件:

  • Node.js
  • 节点包管理器(NPM)

通过在终端 / 控制台窗口中键入以下命令,确保系统上安装了 Node 和 NPM

node  –v
npm  – v

设置 GraphQL API 服务器

创建一个类似于 GraphQLServer 的目录,并将其更改为工作目录。

mkdir GraphQLServer
cd GraphQLServer

创建 package.json

npm init

此命令提示您一些信息,例如应用程序的名称和版本。 现在,你可以简单的点击 RETURN 来接受大多数选项的默认值。

安装 Express

npm install express --save

安装 graphqlgraphql-toolsexpress-graphqlcors

npm install graphql graphql-tools express-graphql cors --save

让我们创建一个新的文件 schema.js,在这里我们将定义类型、查询和函数。

声明自定义 Date 标量,因为它不是 GraphQL 中的默认标量。

# declare custom scalars for date as GQDate
scalar GQDate

定义注册类型

# registration type

type Registration {

id: ID!

firstName: String

lastName: String

dob: GQDate

email: String

password: String

country: String

}

定义查询

type Query {

# Return a registration by id

Registration(id: ID!): Registration

# Return all registrations

Registrations(limit: Int): [Registration]

}

定义创建、更新和删除的函数

type Mutation {

# Create a registration

createRegistration (firstName: String,lastName: String, dob: GQDate, email: String, password: String, country: String): Registration

# Update a registration

updateRegistration (id: ID!, firstName: String,lastName: String, dob: GQDate, email: String, password: String, country: String): Registration

# Delete a registration

deleteRegistration(id: ID!): Registration

}

最终的 schema.js 文件将如下所示

// schema.js

const schema = `
# declare custom scalars for date as GQDate
scalar GQDate

# registration type
type Registration {
    id: ID!
    firstName: String
    lastName: String
    dob: GQDate
    email: String
    password: String
    country: String
}

type Query {
    # Return a registration by id
    Registration(id: ID!): Registration
    # Return all registrations
    Registrations(limit: Int): [Registration]
}

type Mutation {
    # Create a registration
    createRegistration (firstName: String,lastName: String, dob: GQDate, email: String, password: String, country: String): Registration
    # Update a registration
    updateRegistration (id: ID!, firstName: String,lastName: String, dob: GQDate, email: String, password: String, country: String): Registration
    # Delete a registration
    deleteRegistration(id: ID!): Registration
}
`;

module.exports.Schema = schema;

让我们创建一个 resolver.js 文件,我们将根据模式中描述的查询和函数操作来定义 CRUD 功能。

// resolvers.js

const { GraphQLScalarType } = require("graphql");

function convertDate(inputFormat) {
  function pad(s) {
    return s < 10 ? "0" + s : s;
  }
  var d = new Date(inputFormat);
  return [pad(d.getDate()), pad(d.getMonth()), d.getFullYear()].join("/");
}

// Define Date scalar type.

const GQDate = new GraphQLScalarType({
  name: "GQDate",
  description: "Date type",
  parseValue(value) {
    // value comes from the client
    return value; // sent to resolvers
  },
  serialize(value) {
    // value comes from resolvers
    return value; // sent to the client
  },
  parseLiteral(ast) {
    // value comes from the client
    return new Date(ast.value); // sent to resolvers
  }
});

// data store with default data
const registrations = [
  {
    id: 1,
    firstName: "Johan",
    lastName: "Peter",
    dob: new Date("2014-08-31"),
    email: "johan@gmail.com",
    password: "johan123",
    country: "UK"
  },
  {
    id: 2,
    firstName: "Mohamed",
    lastName: "Tariq",
    dob: new Date("1981-11-24"),
    email: "tariq@gmail.com",
    password: "tariq123",
    country: "UAE"
  },
  {
    id: 3,
    firstName: "Nirmal",
    lastName: "Kumar",
    dob: new Date("1991-09-02"),
    email: "nirmal@gmail.com",
    password: "nirmal123",
    country: "India"
  }
];

const resolvers = {
  Query: {
    Registrations: () => registrations, // return all registrations
    Registration: (_, { id }) =>
      registrations.find(registration => registration.id == id) // return registration by id
  },
  Mutation: {
    // create a new registration
    createRegistration: (root, args) => {
      // get next registration id
      const nextId =
        registrations.reduce((id, registration) => {
          return Math.max(id, registration.id);
        }, -1) + 1;
      const newRegistration = {
        id: nextId,
        firstName: args.firstName,
        lastName: args.lastName,
        dob: args.dob,
        email: args.email,
        password: args.password,
        country: args.country
      };
      // add registration to collection
      registrations.push(newRegistration);
      return newRegistration;
    }, // delete registration by id
    deleteRegistration: (root, args) => {
      // find index by id
      const index = registrations.findIndex(
        registration => registration.id == args.id
      );
      // remove registration by index
      registrations.splice(index, 1);
    }, // update registration
    updateRegistration: (root, args) => {
      // find index by id
      const index = registrations.findIndex(
        registration => registration.id == args.id
      );
      registrations[index].firstName = args.firstName;
      registrations[index].lastName = args.lastName;
      registrations[index].dob = args.dob;
      registrations[index].email = args.email;
      registrations[index].password = args.password;
      registrations[index].country = args.country;
      return registrations[index];
    }
  },
  GQDate
};

module.exports.Resolvers = resolvers;

最后,让我们创建一个 server.js 文件,在这个文件中我们将传递 typedef 和解析器来创建可执行模式。

// server.js

const express = require("express");
const cors = require("cors");
const graphqlHTTP = require("express-graphql");
const { makeExecutableSchema } = require("graphql-tools");

const typeDefs = require("./schema").Schema;
const resolvers = require("./resolvers").Resolvers;

const schema = makeExecutableSchema({
  typeDefs,
  resolvers,
  logger: {
    log: e => console.log(e)
  }
});

var app = express();

app.use(cors());

app.use(function(req, res, next) {
  res.header("Access-Control-Allow-Origin", "*");
  res.header(
    "Access-Control-Allow-Headers",
    "Origin, X-Requested-With, Content-Type, Accept"
  );
  next();
});

app.use(
  "/graphql",
  graphqlHTTP(request => ({
    schema: schema,
    graphiql: true
  }))
);

app.listen(4000);

console.log("Running a GraphQL API server at http://localhost:4000/graphql");

运行 GraphQL API 服务器

npm start

现在服务器已经启动,可以在 http://localhost:4200/graphql 上访问应用程序。 让我们在 graphhiql 上查询数据,我们应该得到下面的结果。

1_e8YHzDFBzLoGt14q2hL_egpng

在客户端应用程序中使用 GraphQL API。

在父目录下创建一个作为 AngularClient 的目录,并将其更改为工作目录。

cd ..
mkdir AngularClient
cd AngularClient

克隆 github 中的项目到该目录

git clone https://github.com/bahurudeen/ng5bootstrap4.git

安装该项目

npm install

安装 Angular 命令行工具

npm install --save-dev @angular/cli@latest

为 Angular 安装 Apollo 客户端

npm install apollo-angular apollo-angular-link-http apollo-client apollo-cache-inmemory graphql-tag graphql --save

让我们创建一个 GraphQL.module.ts 文件,在该文件中,我们将创建到 GraphQL API 服务器的连接。

// src/app/graphql.module.ts

import { NgModule } from "@angular/core";
import { HttpClientModule } from "@angular/common/http";

// Apollo
import { ApolloModule, Apollo } from "apollo-angular";
import { HttpLinkModule, HttpLink } from "apollo-angular-link-http";
import { InMemoryCache } from "apollo-cache-inmemory";

@NgModule({
  exports: [HttpClientModule, ApolloModule, HttpLinkModule]
})
export class GraphQLModule {
  constructor(apollo: Apollo, httpLink: HttpLink) {
    apollo.create({
      link: httpLink.create({ uri: "http://localhost:4000/graphql" }),
      cache: new InMemoryCache()
    });
  }
}

app.module.ts 中导入 GraphQL 模块

// src/app/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 { NgbModule } from "@ng-bootstrap/ng-bootstrap";
import { NavbarComponent } from "./navbar/navbar.component";
import { HomeComponent } from "./home/home.component";
import { RegistrationComponent } from "./registration/registration.component";
import { FormsModule } from "@angular/forms";

// Apollo
import { GraphQLModule } from "./graphql.module";

@NgModule({
  declarations: [
    AppComponent,
    NavbarComponent,
    HomeComponent,
    RegistrationComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    NgbModule.forRoot(),
    FormsModule,
    GraphQLModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule {}

registration.component.html 文件中将以下代码:

{{ registration.dob.day + '/' + registration.dob.month + '/' + registration.dob.year}}

替换为:

{{ registration.dob | date:”dd/MM/yyyy”}}

替换后的文件 registration.component.html 代码如下:

#
          First Name
          Last Name
          DOB
          Email
          Country
          
          
        
      
      
        
          {{ i + 1 }}
          {{ registration.firstName }}
          {{ registration.lastName }}
          {{ registration.dob | date:"dd/MM/yyyy"}}
          {{ registration.email }}
          {{ registration.country }}
          
            Edit
          
          
            Delete
          
        
      
    
    
      New
    
  
  
  
    
      
        First Name
        
          
        
      
      
        Last Name
        
          
        
      
      
        DOB
        
          
          
            
          
        
      
      
        Email
        
          
        
      
      
        Password
        
          
        
      
      
        Country
        
          {{regModel.country}}
          
            {{country}}
          
        
      
      {{submitType}}
      Cancel

让我们通过在 registration.com ponent.ts 中使用 GraphQL API 来实现新的、编辑、删除和显示注册信息。

更新 registration.component.ts 文件:

// src/app/registration/registration.component.ts

import { Component, OnInit } from "@angular/core";
import { NgbDateStruct } from "@ng-bootstrap/ng-bootstrap";
import { Apollo } from "apollo-angular";
import gql from "graphql-tag";
import { Observable } from "rxjs/Observable";
import "rxjs/add/operator/map";

class Registration {
  constructor(
    public firstName: string = "",
    public lastName: string = "",
    public dob: NgbDateStruct = null,
    public email: string = "",
    public password: string = "",
    public country: string = "Select country"
  ) {}
}

@Component({
  selector: "app-registration",
  templateUrl: "./registration.component.html",
  styleUrls: ["./registration.component.css"]
})
export class RegistrationComponent implements OnInit {
  // It maintains list of Registrations
  registrations: Array = [];
  // It maintains registration Model
  regModel: Registration;
  // It maintains registration form display status. By default it will be false.
  showNew: Boolean = false;
  // It will be either 'Save' or 'Update' based on operation.
  submitType: string = "Save";
  // It maintains table row index based on selection.
  selectedRow: number;
  // It maintains Array of countries.
  countries: string[] = ["US", "UK", "India", "UAE"];

  registrationList: Array = []; // List of Users

  comments: Observable;

  constructor(private apollo: Apollo) {}

  ngOnInit() {
    this.displayRegistrations();
  }

  // Get all registrations
  displayRegistrations() {
    const getRegistrations = gql`
      {
        Registrations {
          id
          firstName
          lastName
          dob
          email
          country
        }
      }
    `;

    this.apollo
      .watchQuery({
        query: getRegistrations,
        fetchPolicy: "network-only"
      })
      .valueChanges.map((result: any) => result.data.Registrations)
      .subscribe(data => {
        this.registrations = data;
      });
  }

  // This method associate to New Button.
  onNew() {
    // Initiate new registration.
    this.regModel = new Registration();
    // Change submitType to 'Save'.
    this.submitType = "Save";
    // display registration entry section.
    this.showNew = true;
  }

  // This method associate to Save Button.
  onSave() {
    var dateVal =
      this.regModel.dob.year.toString() +
      "-" +
      this.regModel.dob.month.toString() +
      "-" +
      this.regModel.dob.day.toString();
    if (this.submitType === "Save") {
      const saveRegistration = gql`
        mutation createRegistration(
          $firstName: String!
          $lastName: String!
          $dob: GQDate!
          $email: String!
          $password: String!
          $country: String!
        ) {
          createRegistration(
            firstName: $firstName
            lastName: $lastName
            dob: $dob
            email: $email
            password: $password
            country: $country
          ) {
            id
            dob
          }
        }
      `;
      this.apollo
        .mutate({
          mutation: saveRegistration,
          variables: {
            firstName: this.regModel.firstName,
            lastName: this.regModel.lastName,
            dob: new Date(dateVal),
            email: this.regModel.email,
            password: this.regModel.password,
            country: this.regModel.country
          }
        })
        .subscribe(
          ({ data }) => {
            this.displayRegistrations();
          },
          error => {
            console.log("there was an error sending the query", error);
          }
        );

      // Push registration model object into registration list.
      // this.registrations.push(this.regModel);
    } else {
      const updateRegistration = gql`
        mutation updateRegistration(
          $id: ID!
          $firstName: String!
          $lastName: String!
          $dob: GQDate!
          $email: String!
          $password: String!
          $country: String!
        ) {
          updateRegistration(
            id: $id
            firstName: $firstName
            lastName: $lastName
            dob: $dob
            email: $email
            password: $password
            country: $country
          ) {
            id
            country
          }
        }
      `;
      this.apollo
        .mutate({
          mutation: updateRegistration,
          variables: {
            id: this.selectedRow + 1,
            firstName: this.regModel.firstName,
            lastName: this.regModel.lastName,
            dob: new Date(dateVal),
            email: this.regModel.email,
            password: this.regModel.password,
            country: this.regModel.country
          }
        })
        .subscribe(
          ({ data }) => {
            console.log("got editdata", data);
            this.displayRegistrations();
          },
          error => {
            console.log("there was an error sending the query", error);
          }
        );
    }
    // Hide registration entry section.
    this.showNew = false;
  }

  // This method associate to Edit Button.
  onEdit(index: number) {
    // Assign selected table row index.
    this.selectedRow = index;
    // Initiate new registration.
    this.regModel = new Registration();
    // Retrieve selected registration from list and assign to model.
    this.regModel = Object.assign({}, this.registrations[this.selectedRow]);
    const dob = new Date(this.registrations[this.selectedRow].dob);

    this.regModel.dob = {
      day: dob.getDate(),
      month: dob.getMonth() + 1,
      year: dob.getFullYear()
    };

    // Change submitType to Update.
    this.submitType = "Update";
    // Display registration entry section.
    this.showNew = true;
  }

  // This method associate to Delete Button.
  onDelete(index: number) {
    const deleteRegistration = gql`
      mutation deleteRegistration($id: ID!) {
        deleteRegistration(id: $id) {
          id
        }
      }
    `;
    this.apollo
      .mutate({
        mutation: deleteRegistration,
        variables: {
          id: index + 1
        }
      })
      .subscribe(
        ({ data }) => {
          console.log("got editdata", data);
          this.displayRegistrations();
        },
        error => {
          console.log("there was an error sending the query", error);
        }
      );
  }

  // This method associate toCancel Button.
  onCancel() {
    // Hide registration entry section.
    this.showNew = false;
  }

  // This method associate to Bootstrap dropdown selection change.
  onChangeCountry(country: string) {
    // Assign corresponding selected country to model.
    this.regModel.country = country;
  }
}

运行应用程序。 在运行之前,确保 GraphQL API 服务器正在运行。

ng serve

现在 web 服务器已经启动,注册应用程序可以在 http://localhost:4200/上访问,正如你在下面屏幕截图中看到的那样。

1_4LKKkXReKlOPvtw4PxleQpng

英文原文:Creating a GraphQL API server and consuming in Angular 5 application

  • GraphQL

    GraphQL 是一个用于 API 的查询语言,是一个使用基于类型系统来执行查询的服务端运行时(类型系统由你的数据定义)。GraphQL 并没有和任何特定数据库或者存储引擎绑定,而是依靠你现有的代码和数据支撑。

    4 引用 • 3 回帖 • 22 关注
  • API

    应用程序编程接口(Application Programming Interface)是一些预先定义的函数,目的是提供应用程序与开发人员基于某软件或硬件得以访问一组例程的能力,而又无需访问源码,或理解内部工作机制的细节。

    76 引用 • 421 回帖 • 3 关注
  • Angular

    AngularAngularJS 的新版本。

    26 引用 • 66 回帖 • 509 关注

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...