<commit_before>---
layout: post
title: "Real-World Angular Series - Part 6: Reactive Forms and Custom Validation"
description: "Build and deploy a real-world app with MongoDB, Express, Angular, and Node (MEAN): reactive forms and custom validation."
date: 2017-07-13 8:30
category: Technical guide, Angular, Angular 4
banner:
  text: "Auth0 makes it easy to add authentication to your Angular application."
author:
  name: "<NAME>"
  url: "https://twitter.com/KimMaida"
  mail: "<EMAIL>"
  avatar: "https://en.gravatar.com/userimage/20807150/4c9e5bd34750ec1dcedd71cb40b4a9ba.png"
design:
  image: https://cdn.auth0.com/blog/angular/logo3.png
  bg_color: "#012C6C"
tags:
- javascript
- angular
- node
- mongodb
- express
- mean
related:
- 2017-07-05-real-world-angular-series-part-3
- 2017-07-06-real-world-angular-series-part-4
- 2017-07-11-real-world-angular-series-part-5

---

**TL;DR:** This 8-part tutorial series covers building and deploying a full-stack JavaScript application from the ground up with hosted [MongoDB](https://www.mongodb.com/), [Express](https://expressjs.com/), [Angular (v2+)](https://angular.io), and [Node.js](https://nodejs.org) (MEAN stack). The completed code is available in the [mean-rsvp-auth0 GitHub repo](https://github.com/auth0-blog/mean-rsvp-auth0/) and a deployed sample app is available at [https://rsvp.kmaida.net](https://rsvp.kmaida.net).  **Part 6 of the tutorial series covers posting data with reactive forms and implementing custom validation.**

---

## Part 6: Reactive Forms and Custom Validation

The [fifth part of this tutorial](https://auth0.com/blog/real-world-angular-series-part-5) covered simple animation and using a template-driven form to add and update data.

The sixth installment in the series covers posting data with reactive forms and implementing custom validation in Angular.

1. <a href="#api-events" target="_self">API: Create, Update, and Delete Events</a>
2. <a href="#angular-events-api" target="_self">Angular: Add Event Admin Endpoints to API Service</a>
3. <a href="#angular-event-admin-pages" target="_self">Angular: Event Admin Components</a>
4. <a href="#angular-event-form-setup" target="_self">Angular: Reactive Event Form Setup</a>
5. <a href="#angular-custom-validation" target="_self">Angular: Custom Form Validation</a>
6. <a href="#angular-event-form" target="_self">Angular: Event Form</a>
7. <a href="#angular-group-validation" target="_self">Angular: Custom Form Group Validation</a>

---

## <span id="api-events"></span>API: Create, Update, and Delete Events

Let's pick up right where we left off [last time](https://auth0.com/blog/real-world-angular-series-part-5). Let's add the API endpoints our app's administrator needs in order to create, update, and delete events.

### POST New Event

In order to add a new event in our RSVP app, we'll create a new `/api/event/new` endpoint in the Node API.

Open the server `api.js` file and add the following route:

```js
// server/api.js
...
/*
 |--------------------------------------
 | API Routes
 |--------------------------------------
 */
  ...
  // POST a new event
  app.post('/api/event/new', jwtCheck, adminCheck, (req, res) => {
    Event.findOne({
      title: req.body.title,
      location: req.body.location,
      startDatetime: req.body.startDatetime}, (err, existingEvent) => {
      if (err) {
        return res.status(500).send({message: err.message});
      }
      if (existingEvent) {
        return res.status(409).send({message: 'You have already created an event with this title, location, and start date/time.'});
      }
      const event = new Event({
        title: req.body.title,
        location: req.body.location,
        startDatetime: req.body.startDatetime,
        endDatetime: req.body.endDatetime,
        description: req.body.description,
        viewPublic: req.body.viewPublic
      });
      event.save((err) => {
        if (err) {
          return res.status(500).send({message: err.message});
        }
        res.send(event);
      });
    });
  });
  
  ...
```

Only admin users should be able to add, update or delete events. This endpoint needs `jwtCheck` _and_ `adminCheck` middleware functions. Then we'll use the `find()` method to look for an event with the request's `title`, `location`, and `startDatetime`. If an event exists that matches all these fields, it's safe to say that we're trying to create a duplicate of an existing event and we should send an error.

If no `existingEvent` can be found, then we can create a `new Event()` with the data from the request body and `save()` it to MongoDB, handling errors if necessary and sending the new `event` data back in the response.

### PUT (Edit) Existing Event

Now add the following `PUT` route to edit events: `/api/event/:id`.

```js
// server/api.js
...
  // PUT (edit) an existing event
  app.put('/api/event/:id', jwtCheck, adminCheck, (req, res) => {
    Event.findById(req.params.id, (err, event) => {
      if (err) {
        return res.status(500).send({message: err.message});
      }
      if (!event) {
        return res.status(400).send({message: 'Event not found.'});
      }
      event.title = req.body.title;
      event.location = req.body.location;
      event.startDatetime = req.body.startDatetime;
      event.endDatetime = req.body.endDatetime;
      event.viewPublic = req.body.viewPublic;
      event.description = req.body.description;

      event.save(err => {
        if (err) {
          return res.status(500).send({message: err.message});
        }
        res.send(event);
      });
    });
  });
  
  ...
```

Again, we need the authentication and admin middleware to secure our route. We'll pass the event ID as a route parameter and use it to fetch the event with `findById()`. We'll handle errors, then update this event's properties with data sent with the `PUT` request. After updating, we'll `save()` our changes and handle any errors, sending the updated event data back in the response.

### DELETE an Event and its RSVPs

Our final events operation will be to delete events. In doing so, we'll also delete all the RSVPs associated with that event. There's no point in keeping them around if the event is gone.

Add the following `/api/event/:id` `DELETE` API route to the `api.js` file:

```js
// server/api.js
...
  // DELETE an event and all associated RSVPs
  app.delete('/api/event/:id', jwtCheck, adminCheck, (req, res) => {
    Event.findById(req.params.id, (err, event) => {
      if (err) {
        return res.status(500).send({message: err.message});
      }
      if (!event) {
        return res.status(400).send({message: 'Event not found.'});
      }
      Rsvp.find({eventId: req.params.id}, (err, rsvps) => {
        if (rsvps) {
          rsvps.forEach(rsvp => {
            rsvp.remove();
          });
        }
        event.remove(err => {
          if (err) {
            return res.status(500).send({message: err.message});
          }
          res.status(200).send({message: 'Event and RSVPs successfully deleted.'});
        });
      });
    });
  });
  
  ...
```

We'll pass the event ID as a route parameter, verify the user is authenticated and an admin, then `findById()` to fetch the event. If the event is found, we'll `find()` all RSVPs with an `eventId` property matching the event being deleted. We'll remove these associated RSVPs, and then remove the event and handle any errors. On successful deletion, a simple confirmation message is sent in the response.

---

## <span id="angular-events-api"></span>Angular: Add Event Admin Endpoints to API Service

We'll now add the corresponding methods to our `ApiService` to call the new API endpoints we just added.

Open the `api.service.ts` file and add these three methods:

```typescript
// src/app/core/api.service.ts
...
  // POST new event (admin only)
  postEvent$(event: EventModel): Observable<EventModel> {
    return this.authHttp
      .post(`${ENV.BASE_API}event/new`, event)
      .map(this._handleSuccess)
      .catch(this._handleError);
  }

  // PUT existing event (admin only)
  editEvent$(id: string, event: EventModel): Observable<EventModel> {
    return this.authHttp
      .put(`${ENV.BASE_API}event/${id}`, event)
      .map(this._handleSuccess)
      .catch(this._handleError);
  }

  // DELETE existing event and all associated RSVPs (admin only)
  deleteEvent$(id: string): Observable<any> {
    return this.authHttp
      .delete(`${ENV.BASE_API}event/${id}`)
      .map(this._handleSuccess)
      .catch(this._handleError);
  }
  
  ...
```

The `POST` and `PUT` methods should be type `Observable<EventModel>`, since the stream will return the new or updated event. The `DELETE` method returns a JSON message, so we'll add a type annotation of `Observable<any>`. We'll then pass the appropriate event object and/or event ID as parameters to the call the API endpoints.
<commit_msg>Update part 6 HttpClient, 4.3<commit_after>---
layout: post
title: "Real-World Angular Series - Part 6: Reactive Forms and Custom Validation"
description: "Build and deploy a real-world app with MongoDB, Express, Angular, and Node (MEAN): reactive forms and custom validation."
date: 2017-07-13 8:30
category: Technical guide, Angular, Angular 4
banner:
  text: "Auth0 makes it easy to add authentication to your Angular application."
author:
  name: "<NAME>"
  url: "https://twitter.com/KimMaida"
  mail: "<EMAIL>"
  avatar: "https://en.gravatar.com/userimage/20807150/4c9e5bd34750ec1dcedd71cb40b4a9ba.png"
design:
  image: https://cdn.auth0.com/blog/angular/logo3.png
  bg_color: "#012C6C"
tags:
- javascript
- angular
- node
- mongodb
- express
- mean
related:
- 2017-07-05-real-world-angular-series-part-3
- 2017-07-06-real-world-angular-series-part-4
- 2017-07-11-real-world-angular-series-part-5

---

**TL;DR:** This 8-part tutorial series covers building and deploying a full-stack JavaScript application from the ground up with hosted [MongoDB](https://www.mongodb.com/), [Express](https://expressjs.com/), [Angular (v2+)](https://angular.io), and [Node.js](https://nodejs.org) (MEAN stack). The completed code is available in the [mean-rsvp-auth0 GitHub repo](https://github.com/auth0-blog/mean-rsvp-auth0/) and a deployed sample app is available at [https://rsvp.kmaida.net](https://rsvp.kmaida.net).  **Part 6 of the tutorial series covers posting data with reactive forms and implementing custom validation.**

---

## Part 6: Reactive Forms and Custom Validation

The [fifth part of this tutorial](https://auth0.com/blog/real-world-angular-series-part-5) covered simple animation and using a template-driven form to add and update data.

The sixth installment in the series covers posting data with reactive forms and implementing custom validation in Angular.

1. <a href="#api-events" target="_self">API: Create, Update, and Delete Events</a>
2. <a href="#angular-events-api" target="_self">Angular: Add Event Admin Endpoints to API Service</a>
3. <a href="#angular-event-admin-pages" target="_self">Angular: Event Admin Components</a>
4. <a href="#angular-event-form-setup" target="_self">Angular: Reactive Event Form Setup</a>
5. <a href="#angular-custom-validation" target="_self">Angular: Custom Form Validation</a>
6. <a href="#angular-event-form" target="_self">Angular: Event Form</a>
7. <a href="#angular-group-validation" target="_self">Angular: Custom Form Group Validation</a>

---

## <span id="api-events"></span>API: Create, Update, and Delete Events

Let's pick up right where we left off [last time](https://auth0.com/blog/real-world-angular-series-part-5). Let's add the API endpoints our app's administrator needs in order to create, update, and delete events.

### POST New Event

In order to add a new event in our RSVP app, we'll create a new `/api/event/new` endpoint in the Node API.

Open the server `api.js` file and add the following route:

```js
// server/api.js
...
/*
 |--------------------------------------
 | API Routes
 |--------------------------------------
 */
  ...
  // POST a new event
  app.post('/api/event/new', jwtCheck, adminCheck, (req, res) => {
    Event.findOne({
      title: req.body.title,
      location: req.body.location,
      startDatetime: req.body.startDatetime}, (err, existingEvent) => {
      if (err) {
        return res.status(500).send({message: err.message});
      }
      if (existingEvent) {
        return res.status(409).send({message: 'You have already created an event with this title, location, and start date/time.'});
      }
      const event = new Event({
        title: req.body.title,
        location: req.body.location,
        startDatetime: req.body.startDatetime,
        endDatetime: req.body.endDatetime,
        description: req.body.description,
        viewPublic: req.body.viewPublic
      });
      event.save((err) => {
        if (err) {
          return res.status(500).send({message: err.message});
        }
        res.send(event);
      });
    });
  });
  
  ...
```

Only admin users should be able to add, update or delete events. This endpoint needs `jwtCheck` _and_ `adminCheck` middleware functions. Then we'll use the `find()` method to look for an event with the request's `title`, `location`, and `startDatetime`. If an event exists that matches all these fields, it's safe to say that we're trying to create a duplicate of an existing event and we should send an error.

If no `existingEvent` can be found, then we can create a `new Event()` with the data from the request body and `save()` it to MongoDB, handling errors if necessary and sending the new `event` data back in the response.

### PUT (Edit) Existing Event

Now add the following `PUT` route to edit events: `/api/event/:id`.

```js
// server/api.js
...
  // PUT (edit) an existing event
  app.put('/api/event/:id', jwtCheck, adminCheck, (req, res) => {
    Event.findById(req.params.id, (err, event) => {
      if (err) {
        return res.status(500).send({message: err.message});
      }
      if (!event) {
        return res.status(400).send({message: 'Event not found.'});
      }
      event.title = req.body.title;
      event.location = req.body.location;
      event.startDatetime = req.body.startDatetime;
      event.endDatetime = req.body.endDatetime;
      event.viewPublic = req.body.viewPublic;
      event.description = req.body.description;

      event.save(err => {
        if (err) {
          return res.status(500).send({message: err.message});
        }
        res.send(event);
      });
    });
  });
  
  ...
```

Again, we need the authentication and admin middleware to secure our route. We'll pass the event ID as a route parameter and use it to fetch the event with `findById()`. We'll handle errors, then update this event's properties with data sent with the `PUT` request. After updating, we'll `save()` our changes and handle any errors, sending the updated event data back in the response.

### DELETE an Event and its RSVPs

Our final events operation will be to delete events. In doing so, we'll also delete all the RSVPs associated with that event. There's no point in keeping them around if the event is gone.

Add the following `/api/event/:id` `DELETE` API route to the `api.js` file:

```js
// server/api.js
...
  // DELETE an event and all associated RSVPs
  app.delete('/api/event/:id', jwtCheck, adminCheck, (req, res) => {
    Event.findById(req.params.id, (err, event) => {
      if (err) {
        return res.status(500).send({message: err.message});
      }
      if (!event) {
        return res.status(400).send({message: 'Event not found.'});
      }
      Rsvp.find({eventId: req.params.id}, (err, rsvps) => {
        if (rsvps) {
          rsvps.forEach(rsvp => {
            rsvp.remove();
          });
        }
        event.remove(err => {
          if (err) {
            return res.status(500).send({message: err.message});
          }
          res.status(200).send({message: 'Event and RSVPs successfully deleted.'});
        });
      });
    });
  });
  
  ...
```

We'll pass the event ID as a route parameter, verify the user is authenticated and an admin, then `findById()` to fetch the event. If the event is found, we'll `find()` all RSVPs with an `eventId` property matching the event being deleted. We'll remove these associated RSVPs, and then remove the event and handle any errors. On successful deletion, a simple confirmation message is sent in the response.

---

## <span id="angular-events-api"></span>Angular: Add Event Admin Endpoints to API Service

We'll now add the corresponding methods to our `ApiService` to call the new API endpoints we just added.

Open the `api.service.ts` file and add these three methods:

```typescript
// src/app/core/api.service.ts
...
  // POST new event (admin only)
  postEvent$(event: EventModel): Observable<EventModel> {
    return this.http
      .post(`${ENV.BASE_API}event/new`, event, {
        headers: new HttpHeaders().set('Authorization', this._authHeader)
      })
      .catch(this._handleError);
  }

  // PUT existing event (admin only)
  editEvent$(id: string, event: EventModel): Observable<EventModel> {
    return this.http
      .put(`${ENV.BASE_API}event/${id}`, event, {
        headers: new HttpHeaders().set('Authorization', this._authHeader)
      })
      .catch(this._handleError);
  }

  // DELETE existing event and all associated RSVPs (admin only)
  deleteEvent$(id: string): Observable<any> {
    return this.http
      .delete(`${ENV.BASE_API}event/${id}`, {
        headers: new HttpHeaders().set('Authorization', this._authHeader)
      })
      .catch(this._handleError);
  }
  
  ...
```

The `POST` and `PUT` methods should be type `Observable<EventModel>`, since the stream will return the new or updated event. The `DELETE` method returns a JSON message, so we'll add a type annotation of `Observable<any>`. We'll then pass the appropriate event object and/or event ID as parameters to the call the API endpoints.

{'fraction_non_alphanumeric': 0.13935749679861922, 'fraction_numerical': 0.01330660876343188, 'mean_word_length': 3.1569081231196483, 'pattern_counts': {'":': 0, '<': 53, '<?xml version=': 0, '>': 75, 'https://': 24, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2140261', 'n_tokens_mistral': 5563, 'n_tokens_neox': 5281, 'n_words': 1837}