Preston
Preston

Reputation: 3526

Apollo GraphQL - How do I use an RxJS Subject as a variable with Apollo Client?

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.

Upvotes: 1

Views: 3676

Answers (1)

Preston
Preston

Reputation: 3526

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'];
        },
      );
}

Upvotes: 1

Related Questions