Book Image

MEAN Blueprints

By : Robert Onodi
Book Image

MEAN Blueprints

By: Robert Onodi

Overview of this book

The MEAN stack is a combination of the most popular web development frameworks available—MongoDB, Angular, Express, and Node.js used together to offer a powerful and comprehensive full stack web development solution. It is the modern day web dev alternative to the old LAMP stack. It works by allowing AngularJS to handle the front end, and selecting Mongo, Express, and Node to handle the back-end development, which makes increasing sense to forward-thinking web developers. The MEAN stack is great if you want to prototype complex web applications. This book will enable you to build a better foundation for your AngularJS apps. Each chapter covers a complete, single, advanced end-to-end project. You’ll learn how to build complex real-life applications with the MEAN stack and few more advanced projects. You will become familiar with WebSockets and build real-time web applications, as well as create auto-destructing entities. Later, we will combine server-side rendering techniques with a single page application approach. You’ll build a fun project and see how to work with monetary data in Mongo. You will also find out how to a build real-time e-commerce application. By the end of this book, you will be a lot more confident in developing real-time, complex web applications using the MEAN stack.
Table of Contents (13 chapters)
MEAN Blueprints
Credits
About the Author
About the Reviewer
www.PacktPub.com
Preface
Index

The Contact module


This module will hold all the necessary files to manage contacts. As we discussed earlier, we are grouping our files by context, related to their domain. The starting point of our module will be the data layer, which means we'll start implementing the necessary service.

Contact service

Our contact service will have basic CRUD operations and Observable streams to subscribe to. This implementation will use the backend API built using Node.js and Express, but it can be converted anytime to a WebSocket-based API with little effort.

Create a new service file called contact-manager/src/contact/contact.service.ts and add the following code:

import { Injectable } from 'angular2/core';
import { Response, Headers } from 'angular2/http';
import { Observable } from 'rxjs/Observable';
import { contentHeaders } from '../common/headers';
import { AuthHttp } from '../auth/auth-http';
import { Contact } from '../contact';

type ObservableContacts = Observable<Array<Contact>>;
type ObservableContact = Observable<Contact>;

const DEFAULT_URL = '/api/contacts';

@Injectable()
export class ContactService {
  public contact: ObservableContact;
  public contacts: ObservableContacts;

  private _authHttp: AuthHttp;
  private _dataStore: { contacts: Array<Contact>, contact: Contact };
  private _contactsObserver: any;
  private _contactObserver: any;
  private _url: string;

  constructor(authHttp: AuthHttp) {
    this._authHttp = authHttp;
    this._url = DEFAULT_URL;
    this._dataStore = { contacts: [], contact: new Contact() };
    this.contacts = new Observable(
      observer => this._contactsObserver = observer
    ).share();
    this.contact = new Observable(
      observer => this._contactObserver = observer
    ).share();
  }
}

In the contact service, we have a few moving parts. First we defined our Observables so that any other component or module can subscribe and start getting the streams of data.

Second, we declared a private data store. This is where we are going to store our contacts. This is good practice as you can easily return all resources from memory.

Also, in our service, we are going to keep private the returned Observers when new instances of Observables are generated. Using the Observers, we can push new data streams to our Observables.

In our public methods, we are going to expose the get all contacts, get one, update, and delete functionalities. To get all contacts, we are going to add the following method to our ContactService:

  public getAll() {
    return this._authHttp
    .get(`${this._url}`, { headers: contentHeaders} )
    .map((res: Response) => res.json())
    .map(data => {
      return data.map(contact => {
        return new Contact(
          contact._id,
          contact.email,
          contact.name,
          contact.city,
          contact.phoneNumber,
          contact.company,
          contact.createdAt
        )
      });
    })
    .subscribe((contacts: Array<Contact>) => {
      this._dataStore.contacts = contacts;
      this._contactsObserver.next(this._dataStore.contacts);
    }, err => console.error(err));
  }

We use our custom build AuthHttp service to load data from our Express application. When a response is received, we transform it into a JSON file, and after that, we just instantiate a new contact for each entity from the dataset.

Instead of returning the whole Observable from the HTTP service, we use our internal data store to persist all the contacts. After we have successfully updated the data store with the new data, we push the changes to our contactsObserver.

Any component that is subscribed to our stream of contacts will get the new values from the Observable data stream. In this way, we always keep our components synced using one single point of entry.

Much of our public method's logic is the same, but we still have a few distinct elements, for example, the update method:

  public update(contact: Contact) {
    return this._authHttp
    .put(
      `${this._url}/${contact._id}`,
      this._serialize(contact),
      { headers: contentHeaders}
    )
    .map((res: Response) => res.json())
    .map(data => {
      return new Contact(
        data._id,
        data.email,
        data.name,
        data.city,
        data.phoneNumber,
        contact.company,
        data.createdAt
      )
    })
    .subscribe((contact: Contact) => {
      // update the current list of contacts
      this._dataStore.contacts.map((c, i) => {
        if (c._id === contact._id) {
          this._dataStore.contacts[i] = contact;
        }
      });
      // update the current contact
      this._dataStore.contact = contact;
      this._contactObserver.next(this._dataStore.contact);
      this._contactsObserver.next(this._dataStore.contacts);
    }, err => console.error(err));
  }

The update method is almost the same as the create() method, however it takes the contact's ID as the URL param. Instead of pushing new values down a data stream, we return the Observable from the Http service, in order to apply operations from the caller module.

Now, if we would like to make changes directly on the datastore and push the new values through the contacts data stream, we could showcase this in the remove contact method:

  public remove(contactId: string) {
    this._authHttp
    .delete(`${this._url}/${contactId}`)
    .subscribe(() => {
      this._dataStore.contacts.map((c, i) => {
        if (c._id === contactId) {
          this._dataStore.contacts.splice(i, 1);
        }
      });
      this._contactsObserver.next(this._dataStore.contacts);
    }, err => console.error(err));
  }

We simply use the map() function to find the contact we deleted and remove it from the internal store. Afterwards, we send new data to the subscribers.

Contact component

As we have moved everything related to the contact domain, we can define a main component in our module. Let's call it contact-manager/public/src/contact/contact.component.ts. Add the following lines of code:

import { Component } from 'angular2/core';
import { RouteConfig, RouterOutlet } from 'angular2/router';
import { ContactListComponent } from './contact-list.component';
import { ContactCreateComponent } from './contact-create.component';
import { ContactEditComponent } from './contact-edit.component';

@RouteConfig([
  { path: '/', as: 'ContactList', component: ContactListComponent, useAsDefault: true },
  { path: '/:id', as: 'ContactEdit', component: ContactEditComponent },
  { path: '/create', as: 'ContactCreate', component: ContactCreateComponent }
])
@Component({
    selector: 'contact',
    directives: [
      ContactListComponent,
      RouterOutlet
    ],
    template: `
      <router-outlet></router-outlet>
    `
})
export class ContactComponent {
  constructor() {}
}

Our component has no logic associated with it, but we used the RouterConfig annotation. The route config decorator takes an array of routes. Each path specified in the config will match the browser's URL. Each route will load the mounted component. In order to reference routes in the template, we need to give them a name.

Now, the most appealing part is that we can take this component with the configured routes and mount it on another component to have Child/Parent routes. In this case, it becomes nested routing, which is a very powerful feature added to Angular 2.

Our application's routes will have a tree-like structure; other components load components with their configured routes. I was pretty amazed by this feature because it enables us to truly modularize our application and create amazing, reusable modules.

List contacts component

In the previous component, we used three different components and mounted them on different routes. We are not going to discuss each of them, so we will choose one. As we have already worked with forms in the Signin component, let's try something different and implement the list contacts functionality.

Create a new file called contact-manager/public/src/contact/contact-list.component.ts and add the following code for your component:

import { Component, OnInit } from 'angular2/core';
import { RouterLink } from 'angular2/router';
import { ContactService } from '../contact.service';
import { Contact } from '../contact';

@Component({
    selector: 'contact-list',
    directives: [RouterLink],
    template: `
      <div class="row">
        <h4>
          Total contacts: <span class="muted">({{contacts.length}})</span>
          <a href="#" [routerLink]="['ContactCreate']">add new</a>
        </h4>
        <div class="contact-list">
          <div class="card-item col col-25 contact-item"
            *ngFor="#contact of contacts">
            <img src="{{ contact.image }}" />
            <h3>
              <a href="#" [routerLink]="['ContactEdit', { id: contact._id }]">
                {{ contact.name }}
              </a>
            </h3>
            <p>
              <span>{{ contact.city }}</span>
              <span>·</span>
              <span>{{ contact.company }}</span>
            </p>
            <p><span>{{ contact.email }}</span></p>
            <p><span>{{ contact.phoneNumber }}</span></p>
          </div>
        </div>
      </div>
    `
})
export class ContactListComponent implements OnInit {
  public contacts: Array<Contact> = [];
  private _contactService: ContactService;

  constructor(contactService: ContactService) {
    this._contactService = contactService;
  }

  ngOnInit() {
    this._contactService.contacts.subscribe(contacts => {
      this.contacts = contacts;
    });
    this._contactService.getAll();
  }
}

In our component's ngOnInit(), we subscribe to the contacts data stream. Afterwards, we retrieve all the contacts from the backend. In the template, we use ngFor to iterate over the dataset and display each contact.

Creating a contact component

Now that we can list contacts in our application, we should also be able to add new entries. Remember that earlier we used the RouterLink to be able to navigate to the CreateContact route.

The preceding route will load the CreateContactComponent, which will enable us to add new contact entries into our database, through the Express API. Let's create a new component file public/src/contact/components/contact-create.component.ts:

import { Component, OnInit } from 'angular2/core';
import { Router, RouterLink } from 'angular2/router';
import { ContactService } from '../contact.service';
import { Contact } from '../contact';

@Component({
    selector: 'contact-create,
    directives: [RouterLink],
    templateUrl: 'src/contact/components/contact-form.html'
})
export class ContactCreateComponent implements OnInit {
  public contact: Contact;
  private _router: Router;
  private _contactService: ContactService;

  constructor(
    contactService: ContactService,
    router: Router
  ) {
    this._contactService = contactService;
    this._router = router;
  }

  ngOnInit() {
    this.contact = new Contact();
  }

  onSubmit(event) {
    event.preventDefault();

    this._contactService
    .create(this.contact)
    .subscribe((contact) => {
      this._router.navigate(['ContactList']);
    }, err => console.error(err));
  }
}

Instead of using an embedded template, we are using an external template file that is configured using the templateUrl property in the component annotation. There are pros and cons for each situation. The benefits of using an external template file would be that you can reuse the same file for more than one component.

The downfall, at the moment of writing the book, in Angular 2 is that it's hard to use relative paths to your template files, so this would make your components less portable. Also I like to keep my templates short, so they can fit easily inside the component, so in most cases I'll probably use embedded templates.

Let's take a look at the template before further discussing the component, public/src/contact/components/contact-form.html:

<div class="row contact-form-wrapper">
  <a href="#" [routerLink]="['ContactList']">&lt; back to contacts</a>
  <h2>Add new contact</h2>
  <form role="form"
    (submit)="onSubmit($event)">

    <div class="form-group">
      <label for="name">Full name</label>
      <input type="text" [(ngModel)]="contact.name"
        class="form-control" id="name" placeholder="Jane Doe">
    </div>
    <div class="form-group">
      <label for="email">E-mail</label>
      <input type="text" [(ngModel)]="contact.email"
        class="form-control" id="email" placeholder="[email protected]">
    </div>
    <div class="form-group">
      <label for="city">City</label>
      <input type="text"
        [(ngModel)]="contact.city"
        class="form-control" id="city" placeholder="a nice place ...">
    </div>
    <div class="form-group">
      <label for="company">Company</label>
      <input type="text"
        [(ngModel)]="contact.company"
        class="form-control" id="company" placeholder="working at ...">
    </div>
    <div class="form-group">
      <label for="phoneNumber">Phone</label>
      <input type="text"
        [(ngModel)]="contact.phoneNumber"
        class="form-control" id="phoneNumber" placeholder="mobile or landline">
    </div>

    <button type="submit" class="button">Submit</button>
  </form>
</div>

In the template we are using a onSubmit() method from the component to piggyback the form submission and in this case create a new contact and store the data in MongoDB. When we successfully create the contact we want to navigate to the ContactList route.

We are not using local variables, instead we are using two-way data binding with the ngModel for each input, mapped to the properties of the contact object. Now, each time the user changes the inputs value, this is stored in the contact object and on submit it's sent across the wire to the backend.

The RouterLink is used to construct the navigation to the ContactList component from the template. I've left a small improvement, the view title will be the same both for creating and editing, more precisely "Add new contact", and I'll let you figure it out.

Editing an existing contact

When editing a contact, we want to load a specific resource by ID from the backend API and make changes for that contact. Lucky for us this is quite simple to achieve in Angular. Create a new file public/src/contact/components/contact-edit.component.ts:

import { Component, OnInit } from 'angular2/core';
import { RouteParams, RouterLink } from 'angular2/router';
import { ContactService } from '../contact.service';
import { Contact } from '../contact';

@Component({
    selector: 'contact-edit',
    directives: [RouterLink],
    templateUrl: 'src/contact/components/contact-form.html'
})
export class ContactEditComponent implements OnInit {
  public contact: Contact;
  private _contactService: ContactService;
  private _routeParams: RouteParams;

  constructor(
    contactService: ContactService,
    routerParams: RouteParams
  ) {
    this._contactService = contactService;
    this._routeParams = routerParams;
  }

  ngOnInit() {
    const id: string = this._routeParams.get('id');
    this.contact = new Contact();
    this._contactService
    .contact.subscribe((contact) => {
      this.contact = contact;
    });
    this._contactService.getOne(id);
  }

  onSubmit(event) {
    event.preventDefault();

    this._contactService
    .update(this.contact)
    .subscribe((contact) => {
      this.contact = contact;
    }, err => console.error(err));
  }
}

We are not so far away from the ContactCreateComponent, the structure of the class is almost the same. Instead of the Router, we are using RouteParams to load the ID from the URL and retrieve the desired contact from the Express application.

We subscribe to the contact Observable returned by the ContactService to get the new data. In other words our component will react to the data stream and when the data is available it will display it to the user.

When submitting the form, we update the contact persisted in MongoDB and change the view's contact object with the freshly received data from the backend.