使用Aurelia和GraphQL创建教程中的Todo应用程序
首先
在Aurelia的官方网站上,有一个关于创建Todo应用的教程(https://aurelia.io/docs/tutorials/creating-a-todo-app/)。
这次,我想要通过GraphQL + Apollo来自定义管理Todo在数据库中的能力。
基本结构与官方教程相同。
我已将完成系列的源代码放在Github上。
环境
环境建设
奥蕾利亚项目
使用aurelia-cli创建一个用于Todo应用的项目。
如果尚未安装aurelia-cli,则进行安装。
npm install -g aurelia-cli
使用au new命令创建项目。
这次,项目名称为aurelia-apollo-todo,并且其他选项将使用默认值创建。
au new
? Please enter a name for your new project: aurelia-apollo-todo
? Would you like to use the default setup or customize your choices? …
▸ Default ESNext App
? Would you like to install all the npm dependencies? …
▸ Yes, use Yarn
完成项目后,让我们启动一下。
cd aurelia-apollo-todo
au run
当你访问 http://localhost:8080,会显示出“你好,世界!”。
GraphQL服务器
我们将使用Hasura作为GraphQL服务器。
为了在本地环境中运行Hasura,提供了docker-compose.yml文件。
wget https://raw.githubusercontent.com/hasura/graphql-engine/master/install-manifests/docker-compose/docker-compose.yaml
顺便说一下,我们将在Docker Compose中运行刚刚创建的Aurelia项目。
FROM node:10.17.0-alpine
WORKDIR /app
COPY ./package.json .
COPY ./yarn.lock .
RUN apk update && \
yarn global add aurelia-cli && \
yarn
version: '3.6'
services:
web:
build: .
ports:
- 8000:8000
volumes:
- .:/app
stdin_open: true
tty: true
command: yarn start --host 0.0.0.0
postgres:
image: postgres
volumes:
- db_data:/var/lib/postgresql/data
graphql-engine:
image: hasura/graphql-engine:v1.0.0-beta.10
ports:
- "8080:8080"
depends_on:
- "postgres"
environment:
HASURA_GRAPHQL_DATABASE_URL: postgres://postgres:@postgres:5432/postgres
HASURA_GRAPHQL_ENABLE_CONSOLE: "true" # set to "false" to disable console
HASURA_GRAPHQL_ENABLED_LOG_TYPES: startup, http-log, webhook-log, websocket-log, query-log
## uncomment next line to set an admin secret
# HASURA_GRAPHQL_ADMIN_SECRET: myadminsecretkey
volumes:
db_data:
"platform": {
"hmr": false,
"open": false,
- "port": 8000,
+ "port": 8000,
"host": "localhost",
"output": "dist"
},
由于Aurelia的项目和Hasura具有相同的端口号,因此将Aurelia的端口号更改为8000。
一旦创建了Dockerfile和docker-compose.yml,就可以启动Docker Compose。
docker-compose up -d --build
当访问 http://localhost:8000 时,会显示与之前相同的『Hello World!』。
当访问 http://localhost:8080 时,会显示 Hasura 的界面。

创建todos表
创建一个用于注册Todo的表格。
-
- uuid ・・・一意になるID
-
- description ・・・Todoの内容
- done・・・完了したかどうか
为了注册内容和记录是否完成,准备一个名为“description”的列和一个名为“done”的列。
选择“DATA – Create Table”,实际创建一个todos表。


当您输入必要的项目并点击”Add Table”时,将会创建一个名为”todos”的表格。
咱们先往任务应用中添加一些样本数据,以便展示。
选择“插入行”选项卡,然后即可进行数据录入。

无论内容是什么,但在描述中输入“示例任务”,并选择假设为False。
完成输入后,点击保存即可将数据注册。

创建待办事项应用程序
我们将创建一个待办事项应用程序。
首先安装所需的库。
docker-compose exec web yarn add apollo-client apollo-cache-inmemory apollo-link-http graphql-tag graphql
我将在webpack.config.js中添加loader的配置。
devServer: {
contentBase: outDir,
// serve index.html for all 404 (required for push-state)
historyApiFallback: true,
hot: hmr || project.platform.hmr,
port: port || project.platform.port,
host: host,
+ watchOptions: {
+ aggregateTimeout: 300,
+ poll: 1000
+ }
},
devtool: production ? 'nosources-source-map' : 'cheap-module-eval-source-map',
module: {
rules: [
(略)
+ {
+ test: /\.(graphql|gql)$/,
+ exclude: /node_modules/,
+ loader: 'graphql-tag/loader'
+ }
]
}
由于更改了设置,我们将重新启动Web容器。
docker-compose restart web
显示Todo列表
我们将从项目创建时就存在的src/app.js和src/app.html文件进行以下更改。
import { ApolloClient } from 'apollo-client';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { HttpLink } from 'apollo-link-http';
import TODOS from './graphql/Todos.gql';
const cache = new InMemoryCache();
const link = new HttpLink({
uri: 'http://localhost:8080/v1/graphql'
});
const client = new ApolloClient({
cache,
link
});
export class App {
heading = 'Todos';
todos = [];
constructor() {
client.query({
query: TODOS
}).then(data => this.todos = data.data.todos);
}
}
<template>
<h1>${heading}</h1>
<ul>
<li repeat.for="todo of todos">
<input type="checkbox" checked.bind="todo.done">
<span>
${todo.description}
</span>
</li>
</ul>
</template>
从’./graphql/Todos.gql’导入TODOS的文件是定义GraphQL查询的文件。
这是一个查询,用于获取todos表中注册的所有Todo。
query Todos{
todos {
uuid
description
done
}
}
当访问http://localhost:8000时,应该显示先前注册的数据。

在src/app.js的第20至22行,执行查询并获取数据。
数据以数组形式存储在todos中,在src/app.html的第5行的repeat.for=”todo of todos”中逐一取出,并生成li元素。
添加新的Todo
为了能够添加新的待办事项,我们需要添加文本框和按钮。
<template>
<h1>${heading}</h1>
+ <form submit.trigger="addTodo()">
+ <input type="text" value.bind="todoDescription">
+ <button type="submit" disabled.bind="!todoDescription">Add Todo</button>
+ </form>
<ul>
<li repeat.for="todo of todos">
<input type="checkbox" checked.bind="todo.done">
<span>
${todo.description}
</span>
</li>
</ul>
</template>
mutation ($description: String) {
insert_todos(objects: {description: $description, done: false}) {
returning {
uuid
description
done
}
}
}
+ import INSERT_TODO from './graphql/InsertTodo.gql';
(略)
export class App {
heading = 'Todos';
todos = [];
+ todoDescription = ''
(略)
+ addTodo() {
+ if (this.todoDescription) {
+ client.mutate({
+ mutation: INSERT_TODO,
+ variables: { description: this.todoDescription },
+ update: (store, { data }) => {
+ let { todos } = store.readQuery({ query: TODOS });
+ todos.push(data.insert_todos.returning[0]);
+ store.writeQuery({
+ query: TODOS,
+ data: { 'Todos': todos }
+ });
+ }
+ });
+ this.todoDescription = '';
+ }
+ }
}

以本地语言精准翻译,仅提供一个选项:
文本框的value与src/app.js中的todoDescription实现双向绑定,通过input type=”text” value.bind=”todoDescription”实现。这样,输入文本框的内容会同步反映到todoDescription中。
除了bind之外,还有to-view和from-view等等,但是使用bind可以自动判断绑定模式。
因为在 src/app.html 中写了 submit.trigger=”addTodo()”,所以当点击按钮(submit)时将执行 addTodo 方法。
在 addTodo 方法中,将执行一个 Mutation 来注册 todoDescription 的内容。

将Todo标记为已完成。
当您对已完成的待办事项进行勾选时,会将todos表中的done列更新为true。
此外,勾选后会显示删除线。
<ul>
<li repeat.for="todo of todos">
- <input type="checkbox" checked.bind="todo.done">
- <span>
+ <input type="checkbox" checked.bind="todo.done" change.delegate="updateTodo(todo)">
+ <span css="text-decoration: ${todo.done ? 'line-through' : 'none'}">
${todo.description}
</span>
</li>
</ul>
mutation ($uuid: uuid, $done: Boolean) {
update_todos(where: {uuid: {_eq: $uuid}}, _set: {done: $done}) {
returning {
uuid
done
description
}
}
}
+import UPDATE_TODO from './graphql/UpdateTodo.gql';
export class App {
(略)
+ updateTodo(todo) {
+ client.mutate({
+ mutation: UPDATE_TODO,
+ variables: { uuid: todo.uuid, done: todo.done }
+ });
+ }
}
当todo.done为true时(复选框被选中),text-decoration: line-through样式将应用于其中,并显示删除线。
此外,当复选框的状态发生更改时,将执行updateTodo方法。
在updateTodo方法中,将执行用于更新的mutation,并更新todos表的done列。
(当取消勾选时,将以false值进行更新)
现在我能够将已完成的任务标记为“完成”。
删除Todo
<input type="checkbox" checked.bind="todo.done" change.delegate="updateTodo(todo)">
<span css="text-decoration: ${todo.done ? 'line-through' : 'none'}">
${todo.description}
</span>
+ <button click.trigger="removeTodo(todo)">Remove</button>
mutation ($uuid: uuid) {
delete_todos(where: {uuid: {_eq: $uuid}}) {
returning {
description
done
uuid
}
}
}
+ import REMOVE_TODO from './graphql/RemoveTodo.gql';
export class App {
(略)
+ removeTodo(todo) {
+ client.mutate({
+ mutation: REMOVE_TODO,
+ variables: { uuid: todo.uuid },
+ update: (store, { data }) => {
+ let { todos } = store.readQuery({ query: TODOS });
+ const index = todos.findIndex(t => t.uuid === data.delete_todos.returning[0].uuid);
+ if (index > -1) {
+ todos.splice(index, 1);
+ }
+ store.writeQuery({
+ query: TODOS,
+ data: { 'Todos': todos }
+ });
+ }
+ });
+ }
}
我添加了一个删除按钮,并通过click.trigger在点击时执行removeTodo方法。
removeTodo方法执行了一个用于删除的mutation。
这样,即使错误地注册也可以删除!
总结
大约有一半的内容似乎与Aurelia无关。我通常使用Vue.js的机会很多,因为它的氛围相似,所以很容易理解。正好趁机多尝试一些其他东西。
请看以下:
请用中文原生地改述以上内容,只需要一种选项:
参考
https://github.com/hasura/graphql-engine –> https://github.com/hasura/graphql-engine
https://aurelia.io/ –> https://aurelia.io/