mrajanikanth / nx-apollo-angular-example

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Nx Apollo Angular Example

This project was generated using Nx.

🔎 Nx is a set of Extensible Dev Tools for Monorepos.

Run demo

GraphQL API

  • npm start api

Angular

  • npm start nx-apollo

What you’ll create

In this article, you will build a simple GraphQL API that tracks some information about Lego sets. You'll create this API using NestJS, and it will be consumed by an Angular application. You'll have this all inside of an Nx Workspace in a single repository.

What you’ll learn

In this article, you'll learn how to:

  • Create an Nx workspace for both frontend and backend applications
  • Create a GraphQL API using NestJS
  • Autogenerate frontend code based on your GraphQL schema
  • Create an Angular application to consume your GraphQL api

Create a new workspace

Start by creating an Nx workspace:

npx create-nx-workspace@latest nx-apollo-angular-example

When prompted, answer the prompts as follows:

? What to create in the new workspace angular-nest      [a workspace with a 
full stack application (Angular + Nest)]
? Application name                    nx-apollo
? Default stylesheet format           CSS

Create GraphQL API

The angular-nest preset for your workspace has already created a Nest app for you named api. Install the GraphQL modules needed for Nest:

npm install @nestjs/graphql apollo-server-express graphql-tools graphql

You need a GraphQL schema to create the API, so write a very simple one with a single query and a single mutation. Create a file named schema.graphql in the api application:

# apps/api/src/app/schema.graphql

type Set {
    id: Int!
    name: String
    year: Int
    numParts: Int
}

type Query {
    allSets: [Set]
}

type Mutation {
    addSet(name: String, year: String, numParts: Int): Set
}

Import the GraphQLModule and use that schema in NestJS.

// apps/api/src/app/app.module.ts

import { Module } from '@nestjs/common';
import { GraphQLModule } from '@nestjs/graphql';

import { AppController } from './app.controller';
import { AppService } from './app.service';

@Module({
  imports: [
    GraphQLModule.forRoot({
      typePaths: ['./**/*.graphql']
    })
  ],
  controllers: [AppController],
  providers: [AppService]
})
export class AppModule {}

This is already enough to see some progress when you run the api application.

npm start api

When the application is running, bring up the GraphQL playground in your browser at http://localhost:3333/graphql

Here you can inspect your GraphQL schema as well as submit queries. The queries don’t return anything right now because no data has been provided. You need a resolver to do that. Create a new file in your api project called set.resolver.ts. Then add this code:

// apps/api/src/app/set.resolver.ts

import { Args, Mutation, Query, Resolver } from '@nestjs/graphql';

export interface SetEntity {
  id: number;
  name: string;
  numParts: number;
  year: string;
}

@Resolver('Set')
export class SetResolver {
  private sets: SetEntity[] = [
    {
      id: 1,
      name: 'Voltron',
      numParts: 2300,
      year: '2019'
    },
    {
      id: 2,
      name: 'Ship in a Bottle',
      numParts: 900,
      year: '2019'
    }
  ];

  @Query('allSets')
  getAllSets(): SetEntity[] {
    return this.sets;
  }

  @Mutation()
  addSet(
    @Args('name') name: string,
    @Args('year') year: string,
    @Args('numParts') numParts: number
  ) {
    const newSet = {
      id: this.sets.length + 1,
      name,
      year,
      numParts: +numParts
    };

    this.sets.push(newSet);

    return newSet;
  }
}

This is a very simple resolver that holds data in memory. It returns the current contents of the sets array for the allSets query and allows users to add a new set using the addSet mutation. Add this resolver to the providers array in your app module:

// apps/api/src/app/app.module.ts

import { Module } from '@nestjs/common';
import { GraphQLModule } from '@nestjs/graphql';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { SetResolver } from './set.resolver';

@Module({
  imports: [
    GraphQLModule.forRoot({
      typePaths: ['./**/*.graphql']
    })
  ],
  controllers: [AppController],
  providers: [AppService, SetResolver]
})
export class AppModule {}

Go back to your GraphQL Playground and see if your queries return any data now. Try a query and a mutation:

query allSets {
  allSets{
    id,
    name,
    numParts
  }
}

mutation addSet {
  addSet(name: "My New Set", numParts: 200, year: "2020") {
    id
 }
}

Now that the API is working, you’re ready to build a frontend to access this.

Add Apollo client to Angular App

The Apollo client makes it easy to consume your GraphQL API. The Apollo team has made it easy to install by supporting the Angular CLI’s add command:

ng add apollo-angular

You now have a file in your Angular application named graph.module.ts. Open it up, and add the URI of your GraphQL api at the top of this file.

// apps/nx-apollo/src/app/graphql.module.ts
const uri = 'http://localhost:3333/graphql'; // <-- add the URL of the GraphQL server here

Create Angular libraries

Nx helps you break down your code into well-organized libraries for consumption by apps, so create a couple of Angular libraries to organize your work. Create a data-access library that handles communication with the backend and a feature-sets library that includes container components for displaying the Lego set data. In a real app, you might also create a ui library that includes reusable presentational components, but that is not part of this example. For more information on how to organize your Angular monorepo using Nx, read our book Enterprise Angular Monorepo Patterns by registering at Nrwl Connect.

To create the described libraries, we run these commands:

ng generate @nrwl/angular:library data-access --style css

ng generate @nrwl/angular:library feature-sets --style css

Setup Angular Code Generation

A tool called GraphQL Code Generator makes the development of your data-access library faster. As always, install dependencies first:

npm install --save-dev @graphql-codegen/cli @graphql-codegen/typescript-operations @graphql-codegen/typescript-apollo-angular

You need to create some GraphQL queries and mutations for the frontend to consume. Create a folder named graphql in your data-access library with a file inside called operations.graphql:

# libs/data-access/src/lib/graphql/operations.graphql

query setList {
  allSets{
    id
    name
    numParts
    year
  }
}


mutation addSet($name: String!, $year: String!, $numParts: Int!) {
  addSet(name: $name, year: $year, numParts: $numParts) {
    id
    name
    numParts
    year
  }
}

Create a file named codegen.yml in the data-access library to configure the code generator:

# libs/data-access/codegen.yml
overwrite: true
schema: "apps/api/src/app/schema.graphql"
generates:
  libs/data-access/src/lib/generated/generated.ts:
    documents: "libs/data-access/src/lib/graphql/**/*.graphql"
    plugins:
      - "typescript"
      - "typescript-operations"
      - "typescript-apollo-angular"

This configuration grabs all of your GraphQL files and generates all of the needed types and services to consume the API.

Add a new task in angular.json to run this code generator:

// angular.json

{
  "version": 1,
  "projects": {
    "data-access": {
      ...
      "architect": {
        ...
        "generate": {
          "builder": "@nrwl/workspace:run-commands",
          "options": {
            "commands": [
              {
                "command": "npx graphql-codegen --config libs/data-access/codegen.yml"
              }
            ]
          }
        }
      }
    },
    ...
}

Now you can run that task using the Angular CLI:

ng run data-access:generate

You should now have a folder called generated in your data-access library with a file named generated.ts. It contains typing information about the GraphQL schema and the operations you defined. It even has some services that make consuming this API super-fast.

To make these available to consumers, export them in the index.ts file of the data-access library:

// libs/data-access/src/index.ts

export * from './lib/data-access.module';
export * from './lib/generated/generated';

Create Angular components

You now have everything needed to start building your Angular components. Create two components: a list of Lego sets and a form to add a Lego set. Use the Angular CLI to scaffold these:

ng generate @schematics/angular:component --name=SetList --project=feature-sets --export

ng generate @schematics/angular:component --name=SetForm --project=feature-sets --export

The SetForm component needs the ReactiveFormsModule, remember to import that into your module. Your file should look like this now:

// libs/feature-sets/src/lib/feature-sets.module.ts

import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { ReactiveFormsModule } from '@angular/forms';
import { SetFormComponent } from './set-form/set-form.component';
import { SetListComponent } from './set-list/set-list.component';

@NgModule({
  imports: [CommonModule, ReactiveFormsModule],
  declarations: [SetListComponent, SetFormComponent],
  exports: [SetListComponent, SetFormComponent]
})
export class FeatureSetsModule {}

In the SetList component, add the following:

<!-- libs/feature-sets/src/lib/set-list/set-list.component.html -->

<ul>
  <li *ngFor="let set of sets$ | async">
    {{ set.year }} <strong>{{ set.name }}</strong> ({{ set.numParts }} parts)
  </li>
</ul>
/* libs/feature-sets/src/lib/set-list/set-list.component.css */

:host {
  font-family: sans-serif;
}

ul {
  list-style: none;
  margin: 0;
}

li {
  padding: 8px;
}

li:nth-child(2n) {
  background-color: #eee;
}

span.year {
  display: block;
  width: 20%;
}
// libs/feature-sets/src/lib/set-list/set-list.component.ts

import { Component } from '@angular/core';
import { Observable } from 'rxjs';
import { Set, SetListGQL } from '@nx-apollo-angular-example/data-access';
import { map } from 'rxjs/operators';

@Component({
  selector: 'nx-apollo-angular-example-set-list',
  templateUrl: './set-list.component.html',
  styleUrls: ['./set-list.component.css']
})
export class SetListComponent {
  sets$: Observable<Set[]>;

  constructor(private setListGQL: SetListGQL) {
    this.sets$ = this.setListGQL.watch().valueChanges.pipe(map((result) => result.data.allSets));
  }
}

Notice how SetListGQL is imported from the data-access library. This is a service generated by GraphQL Code Generator that provides the results of the SetList query. Your component watches the results of this query and maps them to get the list of sets. This entire pipeline is type-safe, using the types generated by GraphQL Code Generator.

In the SetForm component, add the following:

<!-- libs/feature-sets/src/lib/set-form/set-form.component.html -->

<form [formGroup]="newSetForm" (submit)="createSet()">
  <label for="name">Name</label><br />
  <input formControlName="name" /><br />

  <label for="year">Year of Release</label><br />
  <input formControlName="year" /><br />

  <label for="numParts">Number of Parts</label><br />
  <input formControlName="numParts" /><br />

  <button>Create new set</button>
</form>
/* libs/feature-sets/src/lib/set-form/set-form.component.css */

form {
    font-family: sans-serif;
    border: solid 1px #eee;
    max-width: 240px;
    padding: 24px;
}

input {
    display: block;
    margin-bottom: 8px;
}
// libs//feature-sets/src/lib/set-form/set-form.component.ts

import { Component } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { AddSetGQL, SetListDocument, SetListQuery } from '@nx-apollo-angular-example/data-access';
@Component({
  selector: 'nx-apollo-angular-example-set-form',
  templateUrl: './set-form.component.html',
  styleUrls: ['./set-form.component.css']
})
export class SetFormComponent {
  newSetForm: FormGroup;

  constructor(private addSetGQL: AddSetGQL, private fb: FormBuilder) {

    this.newSetForm = this.fb.group(
      {
        name: ['', Validators.required],
        year: ['', Validators.required],
        numParts: [100, Validators.required]
      }
    )
  }

  createSet() {
    if (this.newSetForm.valid) {
      const newSet = { name: this.newSetForm.get('name').value, year: this.newSetForm.get('year').value, numParts: +this.newSetForm.get('numParts').value };

      this.addSetGQL.mutate(newSet)

      this.addSetGQL.mutate(newSet, {
        update: (store, result) => {
          const data: SetListQuery = store.readQuery({ query: SetListDocument });
          data.allSets = [...data.allSets, result.data.addSet];
          // Write our data back to the cache.
          store.writeQuery({ query: SetListDocument, data });
        }
      }).subscribe(() => {
        this.newSetForm.reset();
      });
    }

  }
}

Again, notice that the component imports services, queries, and typing information from the data-access library to accomplish this.

Integrate components into app

Final steps: import your modules, bring those new components into the app component, and add a little styling:

// apps/nx-apollo/src/app/app.module.ts

import { HttpClientModule } from '@angular/common/http';
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FeatureSetsModule } from '@nx-apollo-angular-example/feature-sets';
import { AppComponent } from './app.component';
import { GraphQLModule } from './graphql.module';


@NgModule({
  declarations: [AppComponent],
  imports: [BrowserModule, HttpClientModule, GraphQLModule, FeatureSetsModule],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule {}
<!-- apps/nx-apollo/src/app/app.component.html -->
<h1>My Lego Sets</h1>
<div class="flex">
  <nx-apollo-angular-example-set-form></nx-apollo-angular-example-set-form>
  <nx-apollo-angular-example-set-list></nx-apollo-angular-example-set-list>
</div>
/* apps/nx-apollo/src/app/app.component.css */
h1 {
  font-family: sans-serif;
  text-align: center;
}

.flex {
  display: flex;
}

nx-apollo-example-set-list {
  flex: 1;
  padding: 8px;
}

If your API isn’t running already, go ahead and start it:

npm start api

And now start your Angular app: npm start nx-apollo

Browse to http://localhost:4200 and see the results of your work!

Further Reading

NestJS

Apollo Angular

GraphQL Code Generator

Interested in using React?

This same example can be implemented in React. The repo for that can be found here: https://github.com/nrwl/nx-apollo-react-example

About


Languages

Language:TypeScript 72.6%Language:JavaScript 20.7%Language:HTML 4.3%Language:CSS 2.4%