Creando una API con GraphQL y Apollo | Parte II
Tags: graphql, angular, javascript, apollo
Published: 2019-01-28 19:44:59


En el [tutorial anterior](https://dev.to/gugadev/creando-un-formulario-con-graphql-y-angular---parte-i-1m9k) vimos cómo crear una API GraphQL en Node.js usando [apollo-server](https://github.com/apollographql/apollo-server) y [type-graphql](https://github.com/19majkel94/type-graphql/), entre otros y cómo consumirla mediante el cliente integrado, el cual es [prisma-playground](https://github.com/prisma/graphql-playground). En el presente tutorial, crearemos una aplicación en Angular que consuma el API de manera programática, además de incorporar funcionalidades extras como validación asíncrona. Dicho esto, vamos a ello.

## Preparación

Lo primero es preparar nuestro entorno. Necesitamos la última LTS de Node.js así como la última versión de la [CLI de Angular](). Si no tienes instalado Angular, entonces mira la [documentación]() para saber cómo instalarlo.

Procedemos a crear nuestro proyecto simple, sin routing:

```bash
ng new signup-form --style=scss
```

En unos segundos (o minutos, dependiendo de tu conexión) tendremos un proyecto listo que soporte para Sass.

Lo siguiente es agregar _apollo_ al proyecto. Esto lo hacemos mediante:

```bash
ng add apollo
```

Angular procederá a instalar apollo y sus dependencias y además, a configurar el proyecto por nosotros.

Ahora ya podemos correr el proyecto, para esto solo ejecuta `yarn start`. Una vez hecho esto ya estamos listo para empezar a codear. 😉

## Empezar a codear

Nuestro estilo será _darkie_, como se puede apreciar en el cover de este post 😏. Para ello, vamos a crear nuestro tema por medio de directivas. Si aún no sabes qué son, te invito a ver mi [tutorial de directivas](https://dev.to/gugadev/angular-directives-3n2n).

Vamos a crear una directiva para estilizar nuestras cajas de texto. Para esto, ejecutmaos:

```bash
ng g d ui/input/base
```

Nos creará una directiva `base.directive.ts`. Esta directiva lucirá así:

```javascript
@Directive({
  selector: '[appBaseInput]'
})
export class BaseDirective {

  @HostBinding('class')
  elementClass = 'txt txt-base'
}
```

Lo que haremos con esta directiva es aplicarle las clases `txt` y `txt-base`. Este código lo pondremos en un archivo llamado `base.directive.scss`, en el mismo nivel que la directiva.

```scss
.txt {
  background-color: transparent;
  border: none;
  border-bottom: 2px solid rgba(255,255,255,.15);
  color: rgba(255,255,255,.75);
  font-size: 15px;
  font-weight: bolder;
  height: 50px;
  letter-spacing: 2px;
  transition: border-color 300ms ease;
  width: 100%;

  &:placeholder {
    color: rgba(255,255,255,.4);
    font-size: 15px;
  }

  &:focus {
    border-bottom-color: rgba(255,255,255,.75);
    outline: none;
  }

  &.error {
    border-bottom-color: #f39c12;
    color: #f39c12;
  }
}
```

Sencillo. Su fondo será transparente, y solo tendrá un border inferior, similar a material design, el cual se aclarará cuando se haga foco sobre él.

Ahora, creamos la directiva para nuestro botón. Para esto, generamos una nueva directiva con:

```bash
ng g d ui/button/primary
```

Y la modificamos de la siguiente manera:

```javascript
@Directive({
  selector: '[appPrimaryButton]'
})
export class PrimaryDirective {
  @HostBinding('class')
  elementClass = 'btn primary'
}
```

Y su respectivo código CSS. De nuevo, en un archivo llamado `primary.directive.scss`:

```scss
.btn {
  border: none;
  border-radius: 25px;
  font-family: 'Open Sans';
  font-size: 15px;
  letter-spacing: 1px;
  height: 50px;
  width: 100%;

  &.primary {
    background-color: #333;
    color: rgba(255,255,255,.9);

    &:hover:not(:disabled), &:active:not(:disabled) {
      background-color: darken(#333, 10%);
    }
    &:disabled {
      background-color: #222;
      color: rgba(255, 255, 255, .3);
      cursor: not-allowed;
    }
  }
  &:focus {
    outline: none;
  }
}
```

Una vez que tenemos nuestras directivas, tenemos que crear un módulo para cada una:

```bash
ng g m ui/button/
ng g m ui/input/
```

E importamos y exportamos las directivas en los módulos:

```javascript
@NgModule({
  declarations: [
    BaseDirective
  ],
  imports: [
    CommonModule
  ],
  exports: [
    BaseDirective
  ]
})
export class InputModule { }
```

```javascript
@NgModule({
  declarations: [
    PrimaryDirective
  ],
  imports: [
    CommonModule
  ],
  exports: [
    PrimaryDirective
  ]
})
export class ButtonModule { }
```

Por último, nos dirigimos hacia `src/app/styles.scss` e importamos nuestros dos hojas de estilos:

```sass
@import "./app/ui/input/base.directive";
@import "./app/ui/button/primary.directive";

// Reset CSS
html {
  box-sizing: border-box;
}
*,
*:before,
*:after {
  box-sizing: inherit;
  margin: 0;
  padding: 0;
}
body {
  font-family: 'Open Sans';
}
```

Ahora nuestras directivas ya están listas para usar. 😎

## Creación del formulario

Seguimos con la creación del formulario. Para esto, como es costumbre, hacemos uso de la CLI de Angular:

```bash
ng g c signup
ng g m signup
```

Hemos generado un componente y un módulo. El componente, debemos incluirlo en el módulo, así mismo, debemos incluir los módulos `InputModule` y `ButtonModule`. Veamos como queda `SignupModule`:

```typescript
@NgModule({
  declarations: [
    SignupComponent
  ],
  imports: [
    CommonModule,
    ReactiveFormsModule,
    InputModule,
    ButtonModule
  ],
  exports: [
    SignupComponent
  ]
})
export class SignupModule { }
```

Expliquemos un poco hasta aquí. Lo que hemos hecho es primero crear dos directivas junto con sus módulos. Para que puedan usarse estas directivas, los módulos que las contienen deben de incluirse en el módulo del componente en donde se desea usar.

```
ButtonModule --> ButtonDirective
     |                  |
     ∨                  ∨
SignupModule --> SignupComponent
```

También importamos el módulo `ReactiveFormsModule` para habilitar al componente `SignupComponent` de usar este comportamiento de formularios reactivos, como veremos próximamente. Lo siguiente es exportar `SignupComponent` para que pueda usarse fuera de su módulo, ya que este componente lo usaremos en el módulo principal.

Ahora nos enfocaremos en nuestro componente. Como sabemos, un componente Angular se divide en dos, que son un _template_ y _hoja de estilos_ más su _clase_ controladora. El template es simplemente HTML ligeramente modificado con atributos de Angular. Nuestro template se verá así:

```html
<div class="container">
  <div class="overlay"></div>
  <section class="side">
    <div class="overlay"></div>
  </section>
  <form [formGroup]="suForm" (ngSubmit)="signup()">
      <figure class="image">
        <img src="/assets/img/nike.svg" alt="signup image">
      </figure>
      <article class="controls">
        <div class="form-group">
          <label for="email">correo</label>
          <input type="email" id="email" formControlName="email" placeholder="Ingresa tu correo" autocomplete="off" appBaseInput [ngClass]="{ error: email.dirty && email.errors }">
          <span class="error" *ngIf="email.errors && email.errors.email">Ingrese un correo válido</span>
          <span class="error" *ngIf="email.errors && email.errors.taken">El email ya está registrado</span>
        </div>
        <div class="form-group">
          <label for="password">contraseña</label>
          <input type="password" id="password" formControlName="password" placeholder="Ingresa tu contraseña" autocomplete="off" appBaseInput [ngClass]="{ error: email.dirty && password.errors }">
          <span class="error" *ngIf="password.errors">Ingrese letras y números</span>
        </div>
      </article>
      <footer class="actions">
        <button type="submit" appPrimaryButton [disabled]="suForm.invalid">Empezar</button>
      </footer>
  </form>
</div>
```

> Si no sabes cómo usar Reactive Forms, mira [este tutorial](http://bit.ly/2RTMp1c).

Fíjate que a los elementos `input` le hemos agregado nuestra directiva `appBaseInput` y al `button`, la directiva `appPrimaryButton`. Al hacer esto, les inyectamos las clases CSS que hemos previamente definido.

Ahora veamos nuestro controlador:

```javascript
import { User } from './../models/user';
import { Component, OnInit } from '@angular/core'
import { FormBuilder, FormGroup, Validators, AbstractControl, FormControl } from '@angular/forms'
import { map } from 'rxjs/operators'

@Component({
  selector: 'app-signup',
  templateUrl: './signup.component.html',
  styleUrls: ['./signup.component.scss']
})
export class SignupComponent implements OnInit {
  suForm: FormGroup

  constructor(
    private fb: FormBuilder
  ) {}

  public signup() {
    const user = new User
    user.email = this.email.value
    user.password = this.password.value
    // hacer algo con el usuario
  }

  ngOnInit() {
    this.suForm = this.fb.group({
      email: new FormControl('', [
        Validators.required,
        Validators.email
      ]),
      password: new FormControl('', [
        Validators.pattern('^(?=.*[0-9])(?=.*[a-zA-Z])([a-zA-Z0-9]+)$')
      ])
    })
  }

  get email(): AbstractControl {
    return this.suForm.get('email')
  }

  get password(): AbstractControl {
    return this.suForm.get('password')
  }
}
```

Bastante sencillo como podemos ver. Creamos el `FormGroup`, los campos `FormControl` y les agregamos algunas validaciones, como `required`, `email` y `pattern`.

Finalmente agreguemos el código CSS para que se vea genial 😎

```scss
.container {
  background-color: #0F223F;
  background-color: #131313;
  height: 100vh;
  width: 100%;

  > .overlay {
    background-color: rgba(0,0,0,.6);
  }

  .side,
  &.container {
    background: url('/assets/img/dark-mountains.jpg') no-repeat;
    background-size: cover;
    position: relative;

    > .overlay {
      background-color: rgba(0,0,0,.5);
      height: 100%;
      left: 0;
      position: absolute;
      top: 0;
      width: 100%;
    } 
  }

  form {
    margin: 0 auto;
    max-width: 480px;
    padding: 40px;
    position: relative;
  }

  .image {
    height: 150px;
    margin-top: 30px;
    text-align: center;

    img {
      height: 100%;
    }
  }

  .controls {
    display: grid;
    grid-template-columns: 1fr;
    grid-gap: 40px 0;
    margin-top: 40px;
    padding: 20px 0;

    label {
      color: rgba(255, 255, 255, .9);
      display: block;
      font-family: 'Exo 2';
      font-size: 13px;
      letter-spacing: 3px;
      padding-bottom: 10px;
      text-transform: uppercase;
    }
    span.error {
      color: #f39c12;
      display: block;
      font-family: 'Exo 2';
      font-size: 12px;
      padding: 10px 0 0 0;
      text-transform: uppercase;
    }
  }
  .actions {
    margin-top: 20px;
  }
}

@media only screen and (min-width: 1200px) {
  .container {
    display: flex;

    form {
      background: #131313;
      flex: 0 0 480px;

      .overlay { display: none; }
    }
    .side {
      flex: 1;
    }
  }
}
```

Para terminar, importemos el `SignupModule` en `AppModule` y pongamos nuestro `SignupComponent` en el template de `AppComponent`:

```javascript
@NgModule({
  declarations: [
    AppComponent,
  ],
  imports: [
    BrowserModule,
    SignupModule,
    GraphQLModule,
    HttpClientModule
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }
```

```html
<app-signup></app-signup>
```

¡Y esto es todo! Si ejecutamos con `yarn start` obtendremos algo así:

<p align="center">
  <img src="https://i.imgur.com/TfQOeA3.png" width="480">
</p>

Genial, ¿no? En el próximo tutorial le añadiremos funcionalidad al formulario. 😉