Creando una API con GraphQL y Apollo | Parte II

gugadev

gugadev

Posted on January 28, 2019

Creando una API con GraphQL y Apollo | Parte II

En el tutorial anterior vimos cómo crear una API GraphQL en Node.js usando apollo-server y type-graphql, entre otros y cómo consumirla mediante el cliente integrado, el cual es prisma-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:

ng new signup-form --style=scss
Enter fullscreen mode Exit fullscreen mode

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:

ng add apollo
Enter fullscreen mode Exit fullscreen mode

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.

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

ng g d ui/input/base
Enter fullscreen mode Exit fullscreen mode

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

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

  @HostBinding('class')
  elementClass = 'txt txt-base'
}
Enter fullscreen mode Exit fullscreen mode

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.

.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;
  }
}
Enter fullscreen mode Exit fullscreen mode

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:

ng g d ui/button/primary
Enter fullscreen mode Exit fullscreen mode

Y la modificamos de la siguiente manera:

@Directive({
  selector: '[appPrimaryButton]'
})
export class PrimaryDirective {
  @HostBinding('class')
  elementClass = 'btn primary'
}
Enter fullscreen mode Exit fullscreen mode

Y su respectivo código CSS. De nuevo, en un archivo llamado primary.directive.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;
  }
}
Enter fullscreen mode Exit fullscreen mode

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

ng g m ui/button/
ng g m ui/input/
Enter fullscreen mode Exit fullscreen mode

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

@NgModule({
  declarations: [
    BaseDirective
  ],
  imports: [
    CommonModule
  ],
  exports: [
    BaseDirective
  ]
})
export class InputModule { }
Enter fullscreen mode Exit fullscreen mode
@NgModule({
  declarations: [
    PrimaryDirective
  ],
  imports: [
    CommonModule
  ],
  exports: [
    PrimaryDirective
  ]
})
export class ButtonModule { }
Enter fullscreen mode Exit fullscreen mode

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

@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';
}
Enter fullscreen mode Exit fullscreen mode

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:

ng g c signup
ng g m signup
Enter fullscreen mode Exit fullscreen mode

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:

@NgModule({
  declarations: [
    SignupComponent
  ],
  imports: [
    CommonModule,
    ReactiveFormsModule,
    InputModule,
    ButtonModule
  ],
  exports: [
    SignupComponent
  ]
})
export class SignupModule { }
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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í:

<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>
Enter fullscreen mode Exit fullscreen mode

Si no sabes cómo usar Reactive Forms, mira este tutorial.

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:

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')
  }
}
Enter fullscreen mode Exit fullscreen mode

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 😎

.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;
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

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

@NgModule({
  declarations: [
    AppComponent,
  ],
  imports: [
    BrowserModule,
    SignupModule,
    GraphQLModule,
    HttpClientModule
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }
Enter fullscreen mode Exit fullscreen mode
<app-signup></app-signup>
Enter fullscreen mode Exit fullscreen mode

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

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

💖 💪 🙅 🚩
gugadev
gugadev

Posted on January 28, 2019

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related