Creando una API con GraphQL y Apollo | Parte III
gugadev
Posted on January 28, 2019
En el tutorial anterior creamos una aplicación Angular y creamos un formulario. Vimos cómo por medio de directivas modularizamos nuestro código y le dimos un aspecto elegante mediante un poco de CSS. Sin embargo, como eso no es suficiente, en esta tercera y última parte de la serie, veremos cómo hacer que nuestra app se comunique con nuestra API GraphQL.
Bien, empecemos. Ya tenemos nuestro formulario, ahora, necesitamos darle alguna funcionalidad. En concreto, nuestra tarea para hoy serán dos cosas:
- Añadir validación al email para asegurarnos que no esté en uso.
- Registrar al nuevo usuario si el punto anterior fue pasado con éxito.
Antes de empezar: ve hacia
src/app/graphql.module.ts
y asigna a la variableuri
la dirección en donde corre nuestra API, la cual es: http://localhost:3000.
Validando que el email no esté registrado.
Si recuerdas mi tutorial pasado acerca de Reactive Forms, recordarás que hablamos de validadores asíncronos. Estos, tienen la particularidad de ser validaciones que retornan una promesa o una instancia de Observable. Por medio de este tipo de validadores podemos realizar validaciones personalizadas. En este tutorial, veremos cómo realizar una validación personalizada con nuestra API GraphQL.
Creación del servicio
Nuestro primer paso será generar un servicio. Le llamaremos signup
:
ng g s signup/
Y agregamos el siguiente método checkForExists
:
import { Injectable } from '@angular/core'
import { Apollo } from 'apollo-angular'
import { Observable, Subscriber, Observer } from 'rxjs'
import gql from 'graphql-tag'
import { ApolloQueryResult } from 'apollo-client'
import { User } from '../models/user'
@Injectable({
providedIn: 'root'
})
export class SignupService {
constructor(private apollo: Apollo) { }
/**
* Search an user by his email address
* @param email | string user's email who's looking for
* @returns boolean if the user exists or not
*/
public checkForExists(email: string): Observable<boolean> {
return Observable.create((sub: Subscriber<boolean>) => {
this.apollo.query({
query: gql`
query Find($email: String!) {
user(email: $email) {
id
}
}
`,
variables: { email }
})
.subscribe((value: ApolloQueryResult<any>) => {
const found: User | null = value.data.user
sub.next(found !== null)
sub.complete()
})
})
}
}
Veámoslo un poco en detalle. Lo primero es inyectar la dependencia de Apollo
en nuestro constructor. Esta dependencia nos permitirá hacer las consultas a nuestra API.
Segundo, nuestro método checkForExists
recibe un parámetro que es el email. Este método devuelve un Observable que almacenará un booleano. Dentro del Observable.create
hacemos uso del método query
de Apollo. Este método recibe una propiedad query
y una variables
opcional. En la propiedad query
procedemos a realizar nuestra consulta. Vemos que declaramos una variable en GraphQL llamada $email
, a esta variable le vamos a dar un valor en la propiedad variables
:
variables: { email }
variable cuyo valor no es nada más que el email recibido por parámetro. Esta consulta devolverá un Observable al cual nos subscribimos para obtener data.user
que es en donde estará la respuesta que obtengamos.
Esta consulta buscará un usuario por su email. Si lo encuentra, retornará al usuario, caso contrario, retornará null.
Importación del servicio
Ahora procedemos a importar el servicio en SignupModule
:
@NgModule({
declarations: [
SignupComponent
],
imports: [
CommonModule,
ReactiveFormsModule,
InputModule,
ButtonModule
],
exports: [
SignupComponent
],
providers: [SignupService] // <--- aquí
})
export class SignupModule { }
Y por último, lo inyectamos en nuestro SignupComponent
:
constructor(
private fb: FormBuilder,
private ss: SignupService // nuevo parámetro
) {}
Y eso es todo. Ahora estamos listo para usar el servicio. 😉
Consumiendo nuestra API
Una vez que tenemos nuestro servicio listo, procedemos a usar su método checkForExists
para la validación. Para esto, creamos un método llamado validateEmailNotTaken
, el cual será nuestro validador.
validateEmailNotTaken(ctrl: AbstractControl) {
return (
this
.ss
.checkForExists(ctrl.value)
.pipe(map(taken => taken ? { taken: true } : null))
)
}
Este método, como toda función validadora, acepta un argumento de tipo AbstractControl
, el cual hace referencia al control que controla, en este caso email
. Ejecutamos el método checkForExists
pasándole el email que se ha ingresado en el textbox. Una vez que lo ejecutamos, hacemos un map del Observable, con el fin de transformar la respuesta por una personalizada. En este punto ya tenemos la respuesta booleana, es decir, si existe el usuario o no. Finalmente, si existe retornamos un objeto { taken: true }
, el cual será añadido al objeto errors del FormControl y que podrá ser accedido por el template. En caso contrario, retorna simplemente null.
Finalmente, agregamos el validador al array de validadores asíncronos del control email
:
ngOnInit() {
this.suForm = this.fb.group({
email: new FormControl('', [
Validators.required,
Validators.email
], [ // lo agregamos aquí
this.validateEmailNotTaken.bind(this)
]),
password: new FormControl('', [
Validators.required,
Validators.pattern('^(?=.*[0-9])(?=.*[a-zA-Z])([a-zA-Z0-9]+)$')
])
})
}
Si ejecutamos la aplicación e ingresamos un email que previamente hemos guardado, veremos el siguiente mensaje de error:
¡Genial! Ya tenemos nuestro formulario completamente validado. 😎
Registrando el usuario
Ya tenemos las validaciones, ahora nos falta registrar al usuario si las ha pasado todas. Para esto, vayamos a signup.service.ts
y añadamos el siguiente método:
/**
*
* @param data | User information of the user
* @returns User the recently created user
*/
public register(data: User): Observable<User> {
return Observable.create((sub: Subscriber<User>) => {
this.apollo.mutate({
mutation: gql`
mutation Register($data: UserInput!) {
createUser(data: $data) {
id,
email
}
}
`,
variables: { data }
})
.subscribe((value: ApolloQueryResult<any>) => {
const created: User = value.data.createUser
sub.next(created)
sub.complete()
})
})
}
Este método es parecido a nuestra consulta anterior. Recibe un argumento de tipo User
y retornamos un Observable<User>
. Dentro del Observable.create
ejecutamos el método mutate
de Apollo para ejecutar una mutation y le pasamos como variable $data
, el objeto User
que hemos recibido. Finalmente, nos subscribimos al Observable, obtenemos la información del usuario creado y la despachamos.
Este método lo llamamos desde el método signup
de SignupComponent
, el cual se disparará en el evento submit
del formulario. El nuevo método signup
queda así:
public signup() {
const user = new User
user.email = this.email.value
user.password = this.password.value
// agregamos esto
this.ss.register(user).subscribe((created: User) => {
alert('Registro exitoso')
this.suForm.reset()
})
}
Una vez que hemos establecido los datos del usuario, se lo pasamos a register
, este usará Apollo para ejecutar la mutación createUser
, retornará la respuesta, la guardamos en un objeto User
y la retornamos al Observer. Si nos subscribimos, tendremos a disposición al usuario recientemente creado. Para terminar, hacemos uso del método FormGroup#reset
para restablecer los valores de los controles.
Y eso es todo. Ahora ya tenemos nuestro formulario completamente funcional. 😉
Recuerda que el código está disponible en Github. ¡Nos vemos!
Posted on January 28, 2019
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.