Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

st的graphql数据源 #1992

Open
Diluka opened this issue Apr 30, 2021 · 11 comments
Open

st的graphql数据源 #1992

Diluka opened this issue Apr 30, 2021 · 11 comments

Comments

@Diluka
Copy link

Diluka commented Apr 30, 2021

What problem does this feature solve?

相关issue #1294 ng-alain/delon#1234

虽然新增了customRequest配置,但是graphql在应用层调用并不是基本的http请求,这个配置对于graphql来说没有实质作用。

在ng中使用graphql通常会使用代码生成器,如下:
  • schema.graphql 接口文档
scalar Date

schema {
  query: Query
}

type Query {
  me: User!
  user(id: ID!): User
  allUsers: [User]
  search(term: String!): [SearchResult!]!
  myChats: [Chat!]!
}

enum Role {
  USER,
  ADMIN,
}

interface Node {
  id: ID!
}

union SearchResult = User | Chat | ChatMessage

type User implements Node {
  id: ID!
  username: String!
  email: String!
  role: Role!
}

type Chat implements Node {
  id: ID!
  users: [User!]!
  messages: [ChatMessage!]!
}

type ChatMessage implements Node {
  id: ID!
  content: String!
  time: Date!
  user: User!
}
  • operation.graphql 查询代码,接口调用
query findUser($userId: ID!) {
  user(id: $userId) {
    ...UserFields
  }
}

fragment UserFields on User {
  id
  username
  role
}
  • codegen.yml 生成器配置文件
generates:
  components.ts:
    plugins:
      - typescript
      - typescript-operations
      - typescript-apollo-angular
  • components.ts 生成的代码
import { gql } from 'apollo-angular';
import { Injectable } from '@angular/core';
import * as Apollo from 'apollo-angular';
export type Maybe<T> = T | null;
export type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };
export type MakeOptional<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]?: Maybe<T[SubKey]> };
export type MakeMaybe<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]: Maybe<T[SubKey]> };
/** All built-in and custom scalars, mapped to their actual values */
export type Scalars = {
  ID: string;
  String: string;
  Boolean: boolean;
  Int: number;
  Float: number;
  Date: any;
};




export type Query = {
  __typename?: 'Query';
  me: User;
  user?: Maybe<User>;
  allUsers?: Maybe<Array<Maybe<User>>>;
  search: Array<SearchResult>;
  myChats: Array<Chat>;
};


export type QueryUserArgs = {
  id: Scalars['ID'];
};


export type QuerySearchArgs = {
  term: Scalars['String'];
};

export enum Role {
  User = 'USER',
  Admin = 'ADMIN'
}

export type Node = {
  id: Scalars['ID'];
};

export type SearchResult = User | Chat | ChatMessage;

export type User = Node & {
  __typename?: 'User';
  id: Scalars['ID'];
  username: Scalars['String'];
  email: Scalars['String'];
  role: Role;
};

export type Chat = Node & {
  __typename?: 'Chat';
  id: Scalars['ID'];
  users: Array<User>;
  messages: Array<ChatMessage>;
};

export type ChatMessage = Node & {
  __typename?: 'ChatMessage';
  id: Scalars['ID'];
  content: Scalars['String'];
  time: Scalars['Date'];
  user: User;
};

export type FindUserQueryVariables = Exact<{
  userId: Scalars['ID'];
}>;


export type FindUserQuery = (
  { __typename?: 'Query' }
  & { user?: Maybe<(
    { __typename?: 'User' }
    & UserFieldsFragment
  )> }
);

export type UserFieldsFragment = (
  { __typename?: 'User' }
  & Pick<User, 'id' | 'username' | 'role'>
);

export const UserFieldsFragmentDoc = gql`
    fragment UserFields on User {
  id
  username
  role
}
    `;
export const FindUserDocument = gql`
    query findUser($userId: ID!) {
  user(id: $userId) {
    ...UserFields
  }
}
    ${UserFieldsFragmentDoc}`;

  @Injectable({
    providedIn: 'root'
  })
  export class FindUserGQL extends Apollo.Query<FindUserQuery, FindUserQueryVariables> {
    document = FindUserDocument;
    
    constructor(apollo: Apollo.Apollo) {
      super(apollo);
    }
  }
  • dashboard.component.ts 项目组件,使用
@Component({
  selector: 'app-dashboard',
  templateUrl: './dashboard.component.html',
})
export class DashboardComponent implements OnInit {
  constructor(private findUserGQL: FindUserGQL) {}

  ngOnInit(): void {}
}

生成器官网 https://www.graphql-code-generator.com/

所以FindUserGQL这个service是没法传给st使用customRequest的,需要一个更加灵活的配置

What does the proposed API look like?

让data属性可以接收GQL service,继续扩展customRequest

@alain-bot
Copy link

alain-bot bot commented Apr 30, 2021

Translation of this issue:

ST GRAPHQL data source

What proBLEES THIS Feature SOLVE?

Related Issue #1294 ng-alain/delon#1234

Although new CustomRequest configuration, GRAPHQL is not a basic HTTP request in the application layer call, this configuration has no substantive effect on Graphql.

Using graphql in NG usually uses the code generator as follows:

  • Schema.graphql interface documentation
    `` `graphql
    Scalar Date

Schema {
Query: query
}

TYPE Query {
ME: user!
User (ID: ID!): User
AllUsers: [USER]
Search (TERM: STRING!): [SearchResult!]!
Mychats: [chat!]!
}

ENUM ROLE {
User,
Admin,
}

Interface node {
ID: ID!
}

Union searchResult = user | chat | chatmessage

TYPE User IMPLEMENTS NODE {
ID: ID!
Username: String!
Email: String!
Role: role!
}

TYPE CHAT IMPLEMENTS NODE {
ID: ID!
Users: [user!]!
Messages: [chatmessage!]!
}

TYPE CHATMESSAGE IMPLEMENTS NODE {
ID: ID!
Content: String!
Time: Date!
User: user!
}
`` `

  • Operation.graphql query code, interface call
    `` `graphql
    Query Finduser ($ UserID: ID!) {
    User (ID: $ UserId) {
    ... Userfields
    }
    }

Fragment Userfields on User {
id
Username
Role
}
`` `

  • codegen.yml generator configuration file
    `YML Generates: Components.ts: Plugins: - TypeScript - TypeScript-Operations - TypeScript-Apollo-Angular `
    Components.ts generated code

    Import {gql} from 'apollo-angular';
    Import {incjectable} from '@ Angular / Core';
    Import * as apollo from 'apollo-angular';
    Export Type Maybe = T | NULL;
    Exports {[key: string]: unknown}> = {[k in keyof t]: t [k]};
    Export Type Makeoptional <T, K Extends KeyOf T> = OMIT <T, K> & {[Subkey IN K] ?: Maybe <T [Subkey]>};
    Export Type Makemaybe <T, K Extends KeyOf T> = OMIT <T, K> & {[Subkey IN K]: Maybe <T [Subkey]>};
    / ** All Built-in and customos scalars, mapped to their actual value * /
    Export type scalars = {
    ID: STRING;
    String: string;
    Boolean: boolean;
    Int: Number;
    FLOAT: NUMBER;
    Date: any;
    }

Export Type Query = {
__typename ?: 'query';
ME: User;
User ?: Maybe ;
ALLUSERS ?: Maybe <array <maybe >>
Search: Array ;
MYCHATS: ARRAY ;
}

Export type queryUserargs = {
ID: Scalars ['ID'];
}

EXPORT TYPE Querysearchargs = {
Term: Scalars ['String'];
}

Export enum role {
User = 'user',
Admin = 'admin'
}

EXPORT TYPE NODE = {
ID: Scalars ['ID'];
}

Export Type SearchResult = User | Chat | ChatMessage;

Export Type User = Node & {
__typename ?: 'user';
ID: Scalars ['ID'];
UserName: Scalars ['String'];
Email: Scalars ['String'];
Role: role;
}

Export Type chat = node & {
__typename ?: 'chat';
ID: Scalars ['ID'];
Users: array ;
Merses: array ;
}

Export type chatmessage = node& {
__typename ?: 'chatmessage';
ID: Scalars ['ID'];
Content: Scalars ['String'];
Time: Scalars ['Date'];
User: user;
}

Export Type FindUserQueryvariables = Exact <{
Userid: scalrs ['id'];
}>;

Export Type FinduserQuery =
{__typename ?: 'query'}
& {user ?: Maybe <
{__typename ?: 'user'}
& Userfieldsfragment

}
);

Export Type Userfieldsfragment =
{__typename ?: 'user'}
& Pick <user, 'id' | 'username' | 'role'>
);

Export constterfieldsfragmentdoc = gqlFragment Userfields on User { id Username Role };
Export const finduserdocument = gqlQuery Finduser ($ UserID: ID!) { User (ID: $ UserId) { ... Userfields } } $ {Userfieldsfragmentdoc};

@Injectable ({
Providedin: 'root'
})
Export Class Findusergql Extends Apollo.query <FindUserQuery, FindUserQueryvariables> {
Document = finduserdocument;

Constructor (apollo: apollo.apollo) {
  Super (apollo);
}

}
` Dashboard.component.ts project components, use ``
@component ({
Selector: 'app-dashboard',
TemplateURL:'./dashboard.component.html ',
})
Export Class DashboardComponent IMplements Oninit {
Constructor (private Findusergql: Findusergql) {}

ngoninit (): void {}
}
`` `

Generator official website https://www.graphql-code-generator.com/

So findusergql This service is not passing to ST using CustomRequest, you need a more flexible configuration

What does The proposed API Look Like?

Let the Data property receive GQL Service, continue to extend CustomRequest

@Diluka
Copy link
Author

Diluka commented Apr 30, 2021

customRequest的参数应该是表格期望怎么加载数据的参数,如查询条件,分页条件,排序条件等

@Diluka
Copy link
Author

Diluka commented Jun 3, 2021

customRequest的参数应该是表格期望怎么加载数据的参数,如查询条件,分页条件,排序条件等

@cipchk 关于这个st数据加载行为重载的问题呢?

现实中graphql不是这样用的

this.http.post(endpoint,{body:{query:'query{currentUser{username nickname}}'}});

所以只是把原始的http请求拿出来也没法修改,就算修改了,也会导致apollo client处理出错

现实中graphql是这样用的

this.currentUserGQL.fetch()

graphql客户端会根据查询和参数在内部生成相应的http请求,然后对返回值进行缓存等操作

@cipchk
Copy link
Member

cipchk commented Jun 3, 2021

@Diluka 我在 https://apollo-angular.com/docs/data/queries#basic-queries 中找到一个关于 Fetch 的查询请求,这里的直接返回一个:

this.apollo.watchQuery<any>({ query: GET_POSTS }).valueChanges

customRequest 理论上应该可以完成一次有效的请求。

@Diluka
Copy link
Author

Diluka commented Jun 3, 2021

@cipchk 但是分页问题怎么解决呢?这个请求怎么知道该翻到第二页了?还有就是能不能很好的适配export功能,目前都只能自己写个循环查询,把结果丢给export

@Diluka
Copy link
Author

Diluka commented Jun 3, 2021

@cipchk 还有一点,关于返回这个

this.apollo.watchQuery<any>({ query: GET_POSTS }).valueChanges

还不如返回这个吧

this.apollo.watchQuery<any>({ query: GET_POSTS })

QueryRef可以订阅,可以修改参数,也可以控制数据刷新。通过调用发现,无论订阅多次,内部都是一套处理,数据变化时会反馈到所有的订阅上。

但是分页参数如何传这可能会稍微复杂一点,目前是pips吧,举个栗子,QueryRef的话,pi设置成paging.offsetps设置成paging.first。最终生成查询参数variables: {"paging":{"first":10,"offset":0}}

update 1

不过这样侵入度太高了,还不如最开始说的,一个回调或者适配器接口,提供分页查询等相关参数,返回一个st接受的数据结果(提供一个结果接口)

update 2

从最初的设想上来说,目前不是可以把一个restful api传给data么?

<st [data]="url"></st>

相当于内部有一个StRESTFulDataAdapter

如果按着思路提供一个StAbstractDataAdapter接口

<st [data]="listGQL" [adapter]="stApolloDataAdapter"></st>

岂不是就可以解决graphql数据加载问题了,而且stApolloDataAdapter不需要集成在ng-alain里面,由用户自己准备即可。

基于这样的数据适配器接口,各种各样的数据加载都可以在st上实现了。

@cipchk
Copy link
Member

cipchk commented Jun 3, 2021

事实上,这个问题更像是 Hot 与 Cold 的 Observable 处理,st 内置为了兼容本地数据与HTTP请求数据,始终都是构造一个 Observable 并订阅一次结果然后扔掉它。

而这里 Apollo 更像是一种推送数据,例如使用 watchQuery 构造一个 Observable 在使用上可以利用 fetchMore 等再一次推着数据,站在 Graphql 角度上的确更舒服。但对于 st 而言,基本上很难保持两种模式的兼容。

基于上述,我建议 Apollo 作为数据源,与 st 配合直接使用本地数据的方式,这样会更合理。一段伪代码:

feedQuery: QueryRef<any>;
feedQuery = this.apollo.watchQuery<any>({ query: GET_POSTS });
this.feed = this.feedQuery
      .valueChanges
      .subscribe(({data}) => {
        this.list = data.feed;
        this.total = data.total;
      });
// 对于分页处理配合 change 事件完成
this.feedQuery.fetchMore(...);
// 最终
<st [data]="list" [total]="total"></st>

若配合 customRequest 可以对代码进一步精简,每一次函数触发都会包含所有参数包含 pi 等。

@Diluka
Copy link
Author

Diluka commented Jun 3, 2021 via email

@cipchk
Copy link
Member

cipchk commented Jun 4, 2021

即便非本地数据源,也只会导出当前页,默认情况下,如果需要全量数据,还是需要手动提供相应的数据源的。

@Diluka
Copy link
Author

Diluka commented Jun 4, 2021

即便非本地数据源,也只会导出当前页,默认情况下,如果需要全量数据,还是需要手动提供相应的数据源的。

呃,原来是这样,emmm,看来新增一个st根据某个最大分页循环加载所有数据或者某个上限的导出功能看起来会很香,关于导出这个我建个新的issue

@Diluka
Copy link
Author

Diluka commented Jun 4, 2021

@cipchk

之前尝试使用过fetchMore,发现它的本意是加载并累加到已有数据并一并返回。关于这点st倒是不很需要,毕竟st每次也只是显示一页的数据,我倒是比较喜欢简单的fetch(用完即弃),fetchMore的数据处理会稍微复杂一些。而且fetchMore的这种处理对于可能会发生条目变化的数据来说并不好使,而管理端经常会面临这种问题。

再者,关于graphql客户端,虽然apollo客户端目前看起来使用广泛,但是客户端并不局限这一种。分页方式虽然有relay的规格说明,但是对于graphql本身来说也不是强制的。所以我们也不能着眼于适配某个具体客户端的api,而之前提到的适配器的引入,让st的用户自行开发相应的处理程序,其实对整体代码来说才是更加精简。或者全局再加上一个决策器,对于适配器的选择会更加精简,根据data入参判断如何选择适配器。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants