1

So I am trying to create a new project ( a object in my project ), I am using Apollo Client, Angular 5 and graphcool framework.

I just want to mention that I the project is created and saved in the data base, but I want than my app to redirect me to the page where all projects are. It is redirecting me, but the new project is not shown in the list. So I spent time reading tutorial on internet and found out that I need to subscribe to event createProject. So I did that, but still the project is not displayed in the list after redirect, even dough it is created and saved to the database. Also I am getting the following error:

Error: Can't find field allProjects({"first":5,"skip":0,"orderBy":"updatedAt_DESC"}) on object (ROOT_QUERY) {
  "allProjects({})": [
    {
      "type": "id",
      "generated": false,
      "id": "Project:cjfji3yntbbjh0193m2ijcbzi",
      "typename": "Project"
    },
    {
      "type": "id",
      "generated": false,
      "id": "Project:cjfjjai46blgw0103koujxlex",
      "typename": "Project"
    },
    {
      "type": "id",
      "generated": false,
      "id": "Project:cjfjjezdwbq220193z4bwrgnl",
      "typename": "Project"
    },
    {
      "type": "id",
      "generated": false,
      "id": "Project:cjfjxnqj21fsa01642klbrhsu",
      "typename": "Project"
    }
  ],
  "_allProjectsMeta": {
    "type": "id",
    "generated": true,
    "id": "$ROOT_QUERY._allProjectsMeta",
    "typename": "_QueryMeta"
  },
  "allSkills({\"filter\":{\"name_contains\":\"a\"}})": [
    {
      "type": "id",
      "generated": false,
      "id": "Skill:cjee75u5ocggc0169lncmboub",
      "typename": "Skill"
    },
    {
      "type": "id",
      "generated": false,
      "id": "Skill:cjee761gnvzde0195pfemfo05",
      "typename": "Skill"
    },
    {
      "type": "id",
      "generated": false,
      "id": "Skill:cjee767ufb5zx0114yfnj8qnk",
      "typename": "Skill"
    },
    {
      "type": "id",
      "generated": false,
      "id": "Skill:cjee7b9su7jv70145vsbe9g2p",
      "typename": "Skill"
    },
    {
      "type": "id",
      "generated": false,
      "id": "Skill:cjee7bi5rb80k0114dm3dznrx",
      "typename": "Skill"
    },
    {
      "type": "id",
      "generated": false,
      "id": "Skill:cjee7bpwkb82r01148rsqfj0m",
      "typename": "Skill"
    },
    {
      "type": "id",
      "generated": false,
      "id": "Skill:cjee7by8aamj50195fp7n836n",
      "typename": "Skill"
    },
    {
      "type": "id",
      "generated": false,
      "id": "Skill:cjee7c368b87o0114nltmfxey",
      "typename": "Skill"
    },
    {
      "type": "id",
      "generated": false,
      "id": "Skill:cjee7ccpdb8c10114xhea0g4s",
      "typename": "Skill"
    },
    {
      "type": "id",
      "generated": false,
      "id": "Skill:cjefg99anpihz0114wxyz1rk9",
      "typename": "Skill"
    },
    {
      "type": "id",
      "generated": false,
      "id": "Skill:cjefg9vhxohew0191fzyahz88",
      "typename": "Skill"
    },
    {
      "type": "id",
      "generated": false,
      "id": "Skill:cjefgawohp1e00195q18bf7lo",
      "typename": "Skill"
    },
    {
      "type": "id",
      "generated": false,
      "id": "Skill:cjefgb8q9pj430114b7c8nmhb",
      "typename": "Skill"
    },
    {
      "type": "id",
      "generated": false,
      "id": "Skill:cjefgbhhcp1kn0195ewwbfjxy",
      "typename": "Skill"
    },
    {
      "type": "id",
      "generated": false,
      "id": "Skill:cjefgc9wqoi2o0191i2tfnzyd",
      "typename": "Skill"
    },
    {
      "type": "id",
      "generated": false,
      "id": "Skill:cjefgcj4dqthc0115y0nx0vn7",
      "typename": "Skill"
    },
    {
      "type": "id",
      "generated": false,
      "id": "Skill:cjefgcqak7fgl0139phkcm7hj",
      "typename": "Skill"
    },
    {
      "type": "id",
      "generated": false,
      "id": "Skill:cjegyq4dr9auf0182diw0it6b",
      "typename": "Skill"
    },
    {
      "type": "id",
      "generated": false,
      "id": "Skill:cjegyqjb3al1s01204v40lqlj",
      "typename": "Skill"
    },
    {
      "type": "id",
      "generated": false,
      "id": "Skill:cjegyqqazd5xp0121y5mewf8o",
      "typename": "Skill"
    },
    {
      "type": "id",
      "generated": false,
      "id": "Skill:cjegyqvqg58fz0160oh5d3s5p",
      "typename": "Skill"
    },
    {
      "type": "id",
      "generated": false,
      "id": "Skill:cjegyracbd1b50174pbw32u0h",
      "typename": "Skill"
    }
  ],
  "allSkills({\"filter\":{\"name_contains\":\"ad\"}})": [
    {
      "type": "id",
      "generated": false,
      "id": "Skill:cjee75u5ocggc0169lncmboub",
      "typename": "Skill"
    },
    {
      "type": "id",
      "generated": false,
      "id": "Skill:cjee761gnvzde0195pfemfo05",
      "typename": "Skill"
    }
  ]
}.
    at readStoreResolver (readFromStore.js:71)
    at executeField (graphql.js:90)
    at eval (graphql.js:46)
    at Array.forEach (<anonymous>)
    at executeSelectionSet (graphql.js:40)
    at graphql (graphql.js:35)
    at diffQueryAgainstStore (readFromStore.js:124)
    at readQueryFromStore (readFromStore.js:37)
    at InMemoryCache.read (inMemoryCache.js:84)
    at InMemoryCache.readQuery (inMemoryCache.js:181)

I was following a tutorial on howtographql and they are using this method so it got to possible to do it.

My schemas:

export const CREATE_PROJECT_MUTATION = gql`
  mutation CreateProjectMutation($name: String!,$description: String!, $skillsIds: [ID!],$startedAt: DateTime!, $endingAt: DateTime!,$type: PROJECT_TYPE, $currency: Currency,$createdById: ID!) {
    createProject(
      name: $name,
      description: $description,
      skillsIds: $skillsIds,
      startedAt: $startedAt,
      endingAt: $endingAt,
      type: $type,
      currency: $currency,
      createdById: $createdById
    ){
      id,
      name,
      createdBy{
        id,
        firstName,
        lastName
      },
      updatedAt,
      description,
      currency,
      skills{
        id,
        name
      },
      startedAt,
      endingAt,
      type
    }
  }
`;

export interface CreateProjectMutationResponse {
  loading: boolean;
  createProject: Project;
}

export const ALL_PROJECTS_QUERY = gql`
  query AllProjectsQuery($first: Int, $skip: Int, $orderBy: ProjectOrderBy){
    allProjects(first: $first, skip: $skip, orderBy: $orderBy){
      id,
      name,
      createdBy{
        id,
        firstName,
        lastName
      },
      updatedAt,
      description,
      currency,
      skills{
        id,
        name
      },
      startedAt,
      endingAt,
      type
    }
    _allProjectsMeta {
      count
    }
  }
`;

export interface AllProjectsQueryResponse {
  allProjects: Project[];
  _allProjectsMeta: { count: number };
  loading: boolean;
}

// Subscription when project is created
export const NEW_PROJECTS_SUBSCRIPTION = gql`
  subscription {
    Project(filter: {
      mutation_in: [CREATED]
    }) {
      node {
        id
        name
        createdBy{
          id
          firstName
          lastName
        }
        updatedAt
        description
        currency
        skills{
          id,
          name
        }
        startedAt
        endingAt
        type
      }
    }
  }
`;

export interface NewProjectSubcriptionResponse {
  node: Project;
}

I will post you my class just in case there is something wrong:

export class Project {
  id: String;
  name: String;
  description: String;
  createdBy: AppUserDisplay;
  updatedAt: Date;
  skills: Skill[];
  startedAt: Date;
  endingAt: Date;
  currency: Currency;
  type: PROJECT_TYPE;
}

export class AppUserDisplay {
  id: String;
  firstName: String;
  lastName: String;
}

export class Skill {
  id: String;
  name: String;
}

My angular component for creating the project:

CreateProject(projForm: NgForm) {
    const createdById = localStorage.getItem(GC_USER_ID);
    if (!createdById) {
      console.error('No user logged in');
      return;
    }
    // get the selected project type and replace " " with "_" in the name
    this.project_type = this.project_types[Number.parseInt(this.selected_project_type.toString())].replace(/\s/g, '_');

    // Formating the date
    this.startedAt.setFullYear(this.fromDate.year, this.fromDate.month - 1, this.fromDate.day);
    this.endingAt.setFullYear(this.toDate.year, this.toDate.month - 1, this.toDate.day);

    // Create the project and save it to the database
    const createMutationSubscription = this.apollo.mutate<CreateProjectMutationResponse>({
      mutation: CREATE_PROJECT_MUTATION,
      variables: {
        name: projForm.form.controls.name.value,
        description: projForm.form.controls.description.value,
        skillsIds: this.choosenSkillsIds,
        startedAt: this.startedAt,
        endingAt: this.endingAt,
        type: this.project_type,
        currency: this.currencies[Number.parseInt(this.selected_currency.toString())],
        createdById: createdById
      },
      update: (store, { data: { createProject } }) => {
        this.updateStoreAfterCreateProject(store, createProject);
      },
    }).subscribe((result) => {
      // console.log(result);
      this.router.navigate(['/all-projects']);
    }, (error) => {
      alert(error);
    });

    this.subscriptions = [...this.subscriptions, createMutationSubscription];
  }



updateStoreAfterCreateProject (store, createProject): UpdateStoreAfterCreateProjectCallback {

const data = store.readQuery({
  query: ALL_PROJECTS_QUERY,
  variables: {
    id: createProject.id,
    first: PROJECTS_PER_PAGE,
    skip: 0,
    orderBy: 'updatedAt_DESC'
  }
});

const allProjects = data.allProjects.slice();
allProjects.splice(0, 0, createProject);
allProjects.pop();
data.allProjects = allProjects;

store.writeQuery({ query: ALL_PROJECTS_QUERY,
  variables: {
    id: createProject.id,
    first: PROJECTS_PER_PAGE,
    skip: 0,
    orderBy: 'updatedAt_DESC'
  }, data });

  return;


 }

And here is my component for displaying the projects

// When Developer access projects page he can see all projects from db
    const getQuery = (variables): Observable<ApolloQueryResult<AllProjectsQueryResponse>> => {
      const query = this.apollo.watchQuery<AllProjectsQueryResponse>({
        query: ALL_PROJECTS_QUERY,
        variables: {
          id: localStorage.getItem(GC_USER_ID)
        }
      });

      query
        .subscribeToMore({
          document: NEW_PROJECTS_SUBSCRIPTION,
          updateQuery: (previous: AllProjectsQueryResponse, { subscriptionData }) => {
            // Casting to any because typings are not updated
            // console.log(<any>subscriptionData);
            const newAllProjects = [
              (<any>subscriptionData).data.Project.node,
              ...previous.allProjects
            ];
            return {
              ...previous,
              allLinks: newAllProjects
            };
          }
        });

      return query.valueChanges;
    };

    // 6
    const allProjectsQuery: Observable<ApolloQueryResult<AllProjectsQueryResponse>> = Observable
      // 6
      .combineLatest(this.first$, this.skip$, this.orderBy$, (first, skip, orderBy) => ({ first, skip, orderBy }))
      // 7
      .switchMap((variables: any) =>  getQuery(variables));
    // 8
    const querySubscription = allProjectsQuery.subscribe((response) => {
      this.projects = response.data.allProjects;
      this.count = response.data._allProjectsMeta.count;
      this.loading = response.data.loading;
    });

This is how I am subscribing to the createProject event, but for some reason it is not working. Here is my config file for apollo:

export class GraphQLModule {
  // Inject Apollo and HttpLink
  constructor(apollo: Apollo, httpLink: HttpLink) {

    const token = localStorage.getItem(GC_AUTH_TOKEN);
    const authorization = token ? `Bearer ${token}` : null;
    const headers = new HttpHeaders().append('Authorization', authorization);

    // Provide the GraphQL endpoint
    const uri = 'https://api.graph.cool/simple/v1/cjeb3hlm13hxw017104lj3x5c';
    const http = httpLink.create({ uri, headers });

    // 1
    const ws = new WebSocketLink({
      uri: `wss://subscriptions.graph.cool/v1/cjeb3hlm13hxw017104lj3x5c`,
      options: {
        reconnect: true,
        connectionParams: {
          authToken: localStorage.getItem(GC_AUTH_TOKEN),
        }
      }
    });

    apollo.create({
      // 2
      link: ApolloLink.split(
        // 3
        operation => {
          const operationAST = getOperationAST(operation.query, operation.operationName);
          return !!operationAST && operationAST.operation === 'subscription';
        },
        ws,
        http,
      ),
      cache: new InMemoryCache()
    });
  }
}

Sorry that my post is so long, I tried to explain and to show you as best as I can what my problem is. Hope someone can help me. Thank you for your time.

P3P5
  • 923
  • 6
  • 15
  • 30

1 Answers1

2

Well I found the solution to my problem, but I have to say that I don't really understand why it's like that.

First my solution:

It looks like that the problem was that for some reason apollo can't find the query with arguments if they do not have a default value. So all I did is that I changed the schema like this:

export const ALL_PROJECTS_QUERY = gql`
  query AllProjectsQuery($first: Int = 5, $skip: Int = 0){
    allProjects(first: $first, skip: $skip, orderBy: updatedAt_DESC){
      id,
      name,
      createdBy{
        id,
        firstName,
        lastName
      },
      updatedAt,
      description,
      currency,
      skills{
        id,
        name
      },
      startedAt,
      endingAt,
      type
    }
    _allProjectsMeta {
      count
    }
  }
`;

As you can see I deleted the argument orderBy because I do not know how to set a default value for it. So if anyone knows how to do it, please do share your knowledge. So instead I set orderBy to be a fix value.

My createProject mutation and update function look like this now:

// Create the project and save it to the database
    const createMutationSubscription = this.apollo.mutate<CreateProjectMutationResponse>({
      mutation: CREATE_PROJECT_MUTATION,
      variables: {
        name: projForm.form.controls.name.value,
        description: projForm.form.controls.description.value,
        skillsIds: this.choosenSkillsIds,
        startedAt: this.startedAt,
        endingAt: this.endingAt,
        type: this.project_type,
        currency: this.currencies[Number.parseInt(this.selected_currency.toString())],
        createdById: createdById
      },
      update: (store, { data: { createProject } }) => {
        this.updateStoreAfterCreateProject(store, createProject);
      },
    }).subscribe((result) => {
      // console.log(result);
      this.router.navigate(['/all-projects']);
    }, (error) => {
      alert(error);
    });

    this.subscriptions = [...this.subscriptions, createMutationSubscription];

updateStoreAfterCreateProject (store, createProject): UpdateStoreAfterCreateProjectCallback {

    const data: any = store.readQuery({
      query: ALL_PROJECTS_QUERY,
      variables: {
        searchText: '',
        first: PROJECTS_PER_PAGE,
        skip: 0,
        orderBy: 'updatedAt_DESC'
      }
    });

    console.log(data);
    const allProjects = data.allProjects.slice();
    allProjects.splice(0, 0, createProject);
    allProjects.pop();
    data.allProjects = allProjects;

    store.writeQuery({ query: ALL_PROJECTS_QUERY,
      variables: {
        searchText: '',
        first: PROJECTS_PER_PAGE,
        skip: 0,
        orderBy: 'updatedAt_DESC'
      }, data });

    return;
  }

Yes, I know that in variables it is useless to add orderBy: 'updatedAt_DESC' variable. Without it, it would also work, the list is updated after a project is created, but I still get the error that the field

allProjects(first: $first, skip: $skip, orderBy: updatedAt_DESC).......

cannot be found. So I decided to leave it their for now.

Well, I hope that this will help someone that is stuck on the same bug as I am. I want to mention that this is not the best solution maybe, but I think that on github is opened a thread as bug for this problem. So until it is not fixed, you can try my workaround solution.

P3P5
  • 923
  • 6
  • 15
  • 30