0
votes

My type-ahead search was working great with REST but I'm converting to GraphQL, which has its challenges.

As the user types a last name into a form field the suggested results display in a data table below. Each letter is handled by the RxJS Subject.

The var searchTerm$ is a type of RXJS observable called a Subject binds to the HTML. The following is called from the OnViewInit lifecycle hook in an Angular app. The search is by the database column last_name.

However, this results in a Bad Request 400 error as the view loads and search doesn't work. I thought maybe this calls for a subscription but everything I find on those is about using web sockets to connect to a remote URL and server. Where do I go from here?

I'm using the Angular Apollo client with Apollo Express but I would be happy with any JS solution and try to figure it out from there. The server side is Nestjs which just wraps Apollo Server.

const lastNameSearch = gql `
        query ($input: String!) {
          lastNameSearch(input: $input) {
            first_name
            last_name
            user_name
            pitch
            main_skill_title
            skills_comments
            member_status
          }
    }`;
this.apollo
      .watchQuery({
        query: lastNameSearch,
        variables: {
          last_name: searchTerm$, // Trying to use the observable here.
        },
      })
      .valueChanges
      .subscribe(result => {
        console.log('data in lastNameSearch: ', result);
      }),

The schema on the server:

lastNameSearch(input: String!): [Member]

The resolver:

@Query()
  async lastNameSearch(@Args('input') input: String) {
    const response = await this.membersService.lastNameSearch(input);
    return await response;
  }

Edit:

The error from the Network panel in dev tools. Console message worthless.

{"errors":[{"message":"Variable \"$input\" of required type \"String!\" was not provided.","locations":[{"line":1,"column":8}],"extensions":{"code":"INTERNAL_SERVER_ERROR","exception":{"stacktrace":["GraphQLError: Variable \"$input\" of required type \"String!\" was not provided.","    at getVariableValues

And this goes on showing properties and methods in the app for another 300 lines or so.

1
Bad request usually means a syntax or validation error in your query. Examine the full response from the server for details or update your question with the query and the complete error if you need help figuring it outDaniel Rearden
Daniel, I'm not ignoring your comment! I started to get what you asked for and my Mac locked up. Recovery didn't go well and then two days of upgrade dependency hell for my full stack. Back to you when all is working again.Preston
OK Daniel, I've added what seems to be the most important part of the error message to my post. I don't see how to figure out a problem from it.Preston
Instead of grabbing the error from the console, open up the network tab of your developer console and look at the actual response from the server. That should contain a more detailed message, usually down to the line number and column if it's a syntax error.Daniel Rearden
Oops, sorry, getting tired. I fixed a typo in my question and posted the error. "was not provided" is the problem but I can't figure out how to provide it. Say I enter 'p' in the input form. My searchTerm$ var is bound to the form and that should provide the var. However, the form crashes due to GraphQL as it is rendered. I have the Angular code in the constructor because that worked best with REST. Confusing.Preston

1 Answers

1
votes

First, a big thank you to the amazing Daniel Rearden for his help on various questions as I and lots of others on SO learn GraphQL! He has patience!

As Daniel pointed out in comments I had a simple mistake. I'll point it out in the commented code below. However, the big issue was trying to use an observable, subject, or similar method as a variable. Even if the RxJS subject is emitting a string GraphQL will hate trying to use a large object as a var. So I had to use a little reactive programming to solve this.

Setup the observable:

public searchTerm$ = new Subject<string>(); // Binds to the html text box element.

Second, let's set this up in a lifecycle hook where we subscribe to the observable so it will emit letters one at a time as they are typed into an input box.

ngAfterViewInit() {

      let nextLetter: string;

      // -------- For Last Name Incremental Query --------- //
      this.searchTerm$.subscribe(result => {
        nextLetter = result;  // Setup a normal variable.
        this.queryLastName(nextLetter); // Call the GraphQL query below.
      });
}

Last step we have the GraphQL query and consuming the returned data object. This works perfect to say type a 'p' into the form and get back from a db all the last names starting with 'p' or 'P'. Type 'r' and the results narrow to last names starting with 'pr', and so on.

private queryLastName(nextLetter) {
    const lastNameSearch = gql`
        query ($input: String!) {
            lastNameSearch(input: $input) {
                first_name
                last_name
                user_name
                pitch
                main_skill_title
                skills_comments
                member_status
            }
        }`;

    this.apollo
      .watchQuery({
        query: lastNameSearch,
        variables: {
          input: nextLetter, // Notice I had used last_name here instead of input.
        },
      })
      .valueChanges
      .subscribe(result => {
          // Put the data into some UI in your app, in this case
          //    an Angular Material data table.
          // Notice how we get the data from the returning object.
          // The avoids the dreaded "null" error when the shape of the 
          //    returned data doesn't match the query. This put an array
          //    of objects into the UI.

          this.dataSource.data = result.data['lastNameSearch'];
        },
      );
}