Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

The Modern Application Stack – Part 4a: Building a Client UI Using Angular 2 (formerly AngularJS) and TypeScript

DZone's Guide to

The Modern Application Stack – Part 4a: Building a Client UI Using Angular 2 (formerly AngularJS) and TypeScript

This is the fourth in a series of blog posts examining technologies such as Angular that are driving the development of modern web and mobile applications.

· Web Dev Zone
Free Resource

Learn how our document data model can map directly to how you program your app, and native database features like secondary indexes, geospatial and text search give you full access to your data. Brought to you in partnership with MongoDB.

Introduction

"Modern Application Stack – Part 1: Introducing The MEAN Stack" introduced the technologies making up the MEAN (MongoDB, Express, Angular, Node.js) and MERN (MongoDB, Express, React, Node.js) Stacks, why you might want to use them, and how to combine them to build your web application (or your native mobile or desktop app).

The remainder of the series is focussed on working through the end to end steps of building a real (albeit simple) application. – MongoPop. Part 2: Using MongoDB With Node.js created an environment where we could work with a MongoDB database from Node.js; it also created a simplified interface to the MongoDB Node.js Driver. Part 3: Building a REST API with Express.js built on Part 2 by using Express.js to add a REST API which will be used by the clients that we implement in the final posts.

This post demonstrates how to use Angular 2 (the evolution of Angular.js) to implement a remote web-app client for the Mongopop application.

Angular 2 (Recap)

Angular, originally created and maintained by Google, runs your JavaScript code within the user's web browsers to implement a reactive user interface (UI). A reactive UI gives the user immediate feedback as they give their input (in contrast to static web forms where you enter all of your data, hit "Submit" and wait.

Reactive Angular 2 application

Version 1 of Angular was called AngularJS, but it was shortened to Angular in Angular 2 after it was completely rewritten in Typescript (a superset of JavaScript) – Typescript is now also the recommended language for Angular apps to use.

You implement your application front-end as a set of components – each of which consists of your JavaScript (Typescript) code and an HTML template that includes hooks to execute and use the results from your Typescript functions. Complex application front-ends can be crafted from many simple (optionally nested) components.

Angular application codes can also be executed on the back-end server rather than in a browser, or as a native desktop or mobile application.

MEAN Stack Architecture

Downloading, Running, and Using the Mongopop Application

The Angular client code is included as part if the Mongopop package installed in Part 2: Using MongoDB With Node.js.

The back-end application should be run in the same way as in parts 2 and 3. The client software needs to be transpiled from Typescript to JavaScript – the client software running in a remote browser can then download the JavaScript files and execute them.

The existing package.json file includes a script for transpiling the Angular 2 code:

  "scripts": {
        ...
    "tsc:w": "cd public && npm run tsc:w",
        ...  
},

That tsc:w delegates the work to a script of the same name defined in public/package.json;

  "scripts": {
        ...
    "tsc:w": "tsc -w",
        ...  
},

tsc -w continually monitors the client app's Typescript files and reruns the transpilation every time they are edited.

To start the continual transpilation of the Angular 2 code:

npm run rsc:w

Component Architecture of the Mongopop Angular UI

Angular applications (both AngularJS and Angular2) are built from one or more, nested components – Mongopop is no exception:

Mongopop Angular2 Components

The main component (AppComponent)contains the HTML and logic for connecting to the database and orchestrating its sub-components. Part of the definition of AppComponent is meta data/decoration to indicate that it should be loaded at the point that a my-appelement (<my-app></my-app>) appears in the index.html file (once the component is running, its output replaces whatever holding content sits between <my-app> and </my-app>). AppComponent is implemented by:

  • A Typescript file containing the AppComponent class (including the data members, initialization code, and member functions).
  • An HTML file containing:
    • HTML layout.
    • Rendering of data members.
    • Elements to be populated by sub-components.
    • Data members to be passed down for use by sub-components.
    • Logic (e.g. what to do when the user changes the value in a form).
  • (Optionally) a CSS file to customize the appearance of the rendered content.

Mongopop is a reasonably flat application with only one layer of sub-components below AppComponent, but more complex applications may nest deeper.

Changes to a data value by a parent component will automatically be propagated to a child – it's best practice to have data flow in this direction as much as possible. If a data value is changed by a child and the parent (either directly or as a proxy for one of its other child components) needs to know of the change, then the child triggers an event. That event is processed by a handler registered by the parent – the parent may then explicitly act on the change, but even if it does nothing explicit, the change flows to the other child components.

This table details what data is passed from AppComponent down to each of its children and what data change events are sent back up to AppComponent (and from there, back down to the other children):

Flow of Data Between Angular Components
Child component Data passed down Data changes passed back up
AddComponent
Data service Collection name
Collection name
Mockaroo URL
CountComponent
Data service Collection name
Collection name
UpdateComponent
Data service Collection name
Collection name
SampleComponent
Data service Collection name
Collection name

What Are All of These Files?

To recap, the files and folders covered earlier in this series:

  • package.json: Instructs the Node.js package manager (npm) what it needs to do; including which dependency packages should be installed.
  • node_modues: Directory where npm will install packages.
  • node_modues/mongodb: The MongoDB driver for Node.js.
  • node_modues/mongodb-core: Low-level MongoDB driver library; available for framework developers (application developers should avoid using it directly).
  • javascripts/db.js: A JavaScript module we've created for use by our Node.js apps (in this series, it will be Express) to access MongoDB; this module in turn uses the MongoDB Node.js driver.
  • config.js: Contains the application–specific configuration options.
  • bin/www: The script that starts an Express application; this is invoked by the npm start script within the package.json file. Starts the HTTP server, pointing it to the appmodule in app.js
  • app.js: Defines the main back-end application module (app). Configures:
    • That the application will be run by Express.
    • Which routes there will be and where they are located in the file system (routesdirectory).
    • What view engine to use (Jade in this case).
    • Where to find the views to be used by the view engine (views directory).
    • What middleware to use (e.g. to parse the JSON received in requests).
    • Where the static files (which can be read by the remote client) are located (public directory).
    • Error handler for queries sent to an undefined route.
  • views: Directory containing the templates that will be used by the Jade view engine to create the HTML for any pages generated by the Express application (for this application, this is just the error page that's used in cases such as mistyped routes ["404 Page not found"]).
  • routes: Directory containing one JavaScript file for each Express route.
    • routes/pop.js: Contains the Express application for the /pop route; this is the implementation of the Mongopop REST API. This defines methods for all of the supported route paths.
  • public: Contains all of the static files that must be accessible by a remote client (e.g., our Angular to React apps).

Now for the new files that implement the Angular client (note that because it must be downloaded by a remote browser it is stored under the public folder):

  • public/package.json: Instructs the Node.js package manager (npm) what it needs to do, including which dependency packages should be installed (i.e. the same as /package.json but this is for the Angular client app).
  • public/index.html: Entry point for the application, served up when browsing to http://<backend-server>/. Imports public/system.config.js
  • public/system.config.js: Configuration information for the Angular client app; in particular defining the remainder of the directories and files:
    • public/app: Source files for the client application – including the Typescript files (and the transpiled JavaScript files) together with the HTML and any custom CSS files. Combined, these define the Angular components.
      • public/app/main.ts: Entry point for the Angular app. Bootstraps public/app/app.module.ts
      • public/app/app.module.ts: Imports required modules, declares the application components, and any services. Declares which component to bootstrap (AppComponent which is implemented in public/app/app.component.*).
      • public/app/app.component.html: HTML template for the top-level component. Includes elements that are replaced by sub-components.
      • public/app/app.component.ts: Implements the AppComponent class for the top-level component.
      • public/app/X.component.html: HTML template for sub-component X.
      • public/app/X.component.ts: Implements the class for sub-component X.
      • AddDocsRequest.ts, ClientConfig.ts, CountDocsRequest.ts, MongoResult.ts, MongoReadResult.ts, SampleDocsRequest.ts, & UpdateDocsRequest.ts: Classes that match the request parameters and response formats of the REST API that's used to access the back-end.
      • data.service.ts: Service used to access the back-end REST API (mostly used to access the database).
      • X.js* & *X.js.map: Files which are generated by the transpilation of the Typescript files.
    • public/node-modules: Node.js modules used by the Angular app (as opposed to the Express, server-side Node.js modules).
    • public/styles.css: CSS stylesheet (imported by public/index.html) – applies to all content on the home page, not just content added by the components.
    • public/stylesheets/styles.css: CSS style sheet (imported by public/app/app.component.ts and the other components) – note that each component could have their own, specialized stylesheet instead.

"Boilerplate" Files and How They Get Invoked

This is an imposing number of new files and this is one of the reasons that Angular is often viewed as the more complex layer in the application stack. One of the frustrations for many developers is the number of files that need to be created and edited on the client side before your first line of component/application code is executed. The good news is that there is a consistent pattern and so it's reasonable to fork your app from an existing project – the Mongopop app can be cloned from GitHub or, the Angular QuickStart can be used as your starting point.

As a reminder, here is the relationship between these common files (and our application-specific components):

Angular2 boilerplate files

Contents of the "Boilerplate" Files

This section includes the contents for each of the non-component files and then remarks on some of the key points.

public/package.json

{  
   "name":"MongoPop",
   "version":"0.1.1",
   "description":"Mongopop client - add data sets and run traffic on MongoDB",
   "scripts":{  
      "start":"tsc && concurrently \"tsc -w\" ",
      "postinstall":"typings install",
      "tsc":"tsc",
      "tsc:w":"tsc -w",
      "typings":"typings"
   },
   "keywords":[  

   ],
   "author":"Andrew Morgan",
   "license":"ISC",
   "dependencies":{  
      "@angular/common":"2.0.0-rc.6",
      "@angular/compiler":"2.0.0-rc.6",
      "@angular/core":"2.0.0-rc.6",
      "@angular/forms":"2.0.0-rc.6",
      "@angular/http":"2.0.0-rc.6",
      "@angular/platform-browser":"2.0.0-rc.6",
      "@angular/platform-browser-dynamic":"2.0.0-rc.6",
      "@angular/router":"3.0.0-rc.2",
      "@angular/upgrade":"2.0.0-rc.6",
      "bootstrap":"^3.3.6",
      "core-js":"^2.4.1",
      "gulp":"^3.9.1",
      "reflect-metadata":"^0.1.3",
      "rx":"^4.1.0",
      "rxjs":"5.0.0-beta.11",
      "systemjs":"0.19.27",
      "zone.js":"^0.6.17"
   },
   "devDependencies":{  
      "concurrently":"^2.2.0",
      "typescript":"^1.8.10",
      "typings":"^1.0.4",
      "canonical-path":"0.0.2",
      "http-server":"^0.9.0",
      "lodash":"^4.11.1",
      "rimraf":"^2.5.2"
   },
   "repository":{  

   }
}


The scripts section defines what npm should do when you type npm run <command-name>from the command line. Of most interest is the tsc:w script – this is how the transpiler is launched. After transpiling all of the .ts Typescript files, it watches them for changes – retranspiling as needed.

Note that the dependencies are for this Angular client. They will be installed in public/node_modules when npm install is run (for Mongopop, this is done automatically when building the full project ).

public/index.html

<!DOCTYPE html>
<html>
<!--
This is the file that will be served to anyone browsing to http://server-running-mongopop-back-end/
Then, by loading 'systemjs.config.js', Angular2 will replace the "my-app" element with the Mongopop
client application.
-->


<head>
    <title>MongoPop</title>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="styles.css"> 
    <!-- Polyfill(s) for older browsers -->
    <script src="node_modules/core-js/client/shim.min.js"></script>

    <script src="node_modules/zone.js/dist/zone.min.js"></script>
    <script src="node_modules/reflect-metadata/Reflect.js"></script>
    <script src="node_modules/systemjs/dist/system.src.js"></script>

    <script src="systemjs.config.js"></script>
    <script>
        System.import('app').catch(function(err) {
            console.error(err);
        });
    </script>
</head>


<body>
    <my-app>Loading MongoPop client app...</my-app>
</body>

</html>


Focussing on the key lines, the application is started using the app defined in systemjs.config.js:

<script src="systemjs.config.js"></script>
<script>
    System.import('app').catch(function(err) {
        console.error(err);
    });
</script>


And the output from the application replaces the placeholder text in the my-app element:

<my-app>Loading MongoPop client app...</my-app>

public/systemjs.config.js

/*
System configuration for Mongopop Angular 2 client app
*/
(function(global) {
    System.config({
        paths: {
            // paths serve as alias
            'npm:': 'node_modules/'
        },
        // map tells the System loader where to look for things
        map: {
            // The Mongopop app is within the app folder
            app: 'app',

            // angular bundles
            '@angular/core': 'npm:@angular/core/bundles/core.umd.js',
            '@angular/common': 'npm:@angular/common/bundles/common.umd.js',
            '@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js',
            '@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js',
            '@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js',
            '@angular/http': 'npm:@angular/http/bundles/http.umd.js',
            '@angular/router': 'npm:@angular/router/bundles/router.umd.js',
            '@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js',

            // other libraries
            'rxjs': 'npm:rxjs',
        },
        // packages tells the System loader how to load when no filename and/or no extension
        packages: {
            app: {
                // app/main.js (built from app/main.ts) is the entry point for the Mongopop client app
                main: './main.js',
                defaultExtension: 'js'
            },
            rxjs: {
                defaultExtension: 'js'
            }
        }
    });
})(this);


packages.app.main
is mapped to public/app/main.js – note that main.js is referenced rather than main.ts as it is always the transpiled code that is executed. This is what causes main.ts to be run.public/app/main.ts

// Invoked from `system.config.js`; loads `app.module.js` (built from `app.module.ts`) and
// then bootstraps the module contained in that file (`AppModule`)

import {
    AppModule
} from './app.module';
platformBrowserDynamic().bootstrapModule(AppModule);

This simply imports and bootstraps the AppModule class from public/app/app.module.ts (actually app.module.js)

public/app/app.module.ts

// This is the main Angular2 module for the Mongopop client app. It is bootstrapped
// from `main.js` (built from `main.ts`)

import {
    NgModule
} from '@angular/core';
import {
    BrowserModule
} from '@angular/platform-browser';
import {
    HttpModule
} from '@angular/http';
import {
    FormsModule
} from '@angular/forms';
// Components making up the Mongopop Angular2 client app
import {
    AppComponent
} from './app.component';
import {
    SampleComponent
} from './sample.component';
import {
    AddComponent
} from './add.component';
import {
    CountComponent
} from './count.component';
import {
    UpdateComponent
} from './update.component';
// Service for accessing the Mongopop (Express) server API
import {
    DataService
} from './data.service';
@NgModule({
    imports: [
        BrowserModule,
        FormsModule,
        HttpModule
    ],

    declarations: [
        AppComponent,
        SampleComponent,
        AddComponent,
        CountComponent,
        UpdateComponent
    ],

    providers: [
        DataService
    ],

    // Load the module defined in `app.component.js(ts)`
    bootstrap: [AppComponent]
})
export class AppModule {}



This is the first file to actually reference the components which make up the Mongopop application!

Note that NgModule is the core module for Angular and must always be imported; for this application BrowserModule, HttpModule, and FormsModule are also needed.

The import commands also bring in the (.js) files for each of the components as well as the data service.

Following the imports, the @NgModuledecorator function takes a JSON object that tells Angular how to run the code for this module (AppModule) – including the list of imported modules, components, and services as well as the module/component needed to bootstrap the actual application (AppComponent).

Typescript and Observables (Before Getting Into Component Code)

As a reminder from "Modern Application Stack – Part 1: Introducing The MEAN Stack" - the most recent, widely supported version is ECMAScript 6 – normally referred to as /ES6/. ES6 is supported by recent versions of Chrome, Opera, Safari, and Node.js. Some platforms (e.g. Firefox and Microsoft Edge) do not yet support all features of ES6. These are some of the key features added in ES6:

  • Classes & modules.
  • Promises – a more convenient way to handle completion or failure of synchronous function calls (compared to callbacks).
  • Arrow functions – a concise syntax for writing function expressions.
  • Generators – functions that can yield to allow others to execute.
  • Iterators.
  • Typed arrays.

Typescript is a superset of ES6 (JavaScript), adding static type checking. Angular 2 is written in Typescript and Typescript is the primary language to be used when writing code to run in Angular 2.

Because ES6 and Typescript are not supported in all environments, it is common to transpile the code into an earlier version of JavaScript to make it more portable. tsc is used to transpile Typescript into JavaScript.

And of course, JavaScript is augmented by numerous libraries. The Mongopop Angular 2 client uses Observables from the RxJS reactive libraries which greatly simplify making asynchronous calls to the back-end (a pattern historically referred to as AJAX).

RxJS Observables fulfill a similar role to ES6 promises in that they simplify the code involved with asynchronous function calls (removing the need to explicitly pass callback functions). Promises are more contained than Observables, they make a call and later receive a single signal that the asynchronous activity triggered by the call succeeded or failed. Observables can have a more complex lifecycle, including the caller receiving multiple sets of results and the caller being able to cancel the Observable.

The Mongopop application uses two simple patterns when calling functions that return an Observable; the first is used within the components to digest the results from our own data service:

var _this = this; // Required as `this` is no longer available in the functions invoked
// in response to the Observable returning results or an error

myLibrary.myAsyncFunction(myParameters)
    .subscribe(
        myResults => {
            // If the observable emits succesfull results then the first of the arrow
            // functions passed to the `subscribe` method is invoked.
            // `myResults` is an arbitrary name and it is set to the result data sent back
            // by the observable returned by `myAsyncFunction`.
            // If the observable emits multiple result sets then this function is invoked
            // multiple times.

            doSomething(myResults);
            _this.resultsReceived++;
        },
        myError => {
            // If the observable finds a problem then the second of the arrow functions
            // passed to the `subscribe` method is invoked.
            // `myError` is an arbitrary name and it is set to the error data sent back
            // by the observable returned by `myAsyncFunction`. There will be no further
            // results from the observable after this error.

            console.log("Hit a problem: " + myError.message);
        },
        () => {
            // Invoked when the observable has emitted everything that it plans to (and
            // no errors were found)

            console.log("Finished; received " + _this.resultsReceived + " sets of results");
        }
    )



In Mongopop's use of Observables, we don't have anything to do in the final arrow function and so don't use it (and so it could have used the second pattern instead – but it's interesting to see both).

The second pattern is used within the data service when making calls to the Angular 2 http module (this example also shows how we return an Observable back to the components):

fetchServerIP(): Observable < string > {
    // This method returns an Observable which resolves to a string

    // Make a http call which returns an Observable
    return this.http.get(this.baseURL + "ip")
        .map(
            // This is called if/when the Observable returned by `http.get` has results for us.
            // Map the response so that this method's returned Observable only supplies the part
            // of the result that the caller cares about.
            response => response.json().ip
        )
        .catch(
            // This is invoked if/when the Observable returned by `http.get` flags an error.
            // Throw an error to the subscriber of the Observable returned by this method.
            (error: any) => Observable.throw(error.json().error || 'Server error')
        )
}



Calling the REST API

The DataService class hides the communication with the back-end REST API; serving two purposes:

  • Simplifying all of the components' code.
  • Shielding the components' code from any changes in the REST API signature or behavior – that can all be handled within the DataService.

By adding the @Injectable decorator to the class definition, any member variables defined in the arguments to the class constructor function will be automatically instantiated (i.e. there is no need to explicitly request a new Http object):

import {
    Injectable,
    OnInit
} from '@angular/core';
import {
    Http,
    Response,
    Headers,
    RequestOptions
} from '@angular/http';
import {
    Observable,
    Subscription
} from 'rxjs/Rx';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';
import {
    MongoResult
} from './MongoResult';
import {
    ClientConfig
} from './ClientConfig';
import {
    AddDocsRequest
} from './AddDocsRequest';
import {
    SampleDocsRequest
} from './SampleDocsRequest';
import {
    MongoReadResult
} from './MongoReadResult';
import {
    UpdateDocsRequest
} from './UpdateDocsRequest';
import {
    CountDocsRequest
} from './CountDocsRequest';
@Injectable()
export class DataService {
    private MongoDBURI: string; // The URI to use when accessing the MongoDB database
    private baseURL: string = "http://localhost:3000/pop/"; // The URL for the Mongopop service

    constructor(private http: Http) {}
        ...
}


After the constructor has been called, methods within the class can safely make use of the http data member.

As a reminder from Part 3: Building a REST API with Express.js, this is the REST API we have to interact with:

Express routes implemented for the Mongopop REST API
Route Path HTTP Method Parameters Response Purpose
/pop/
GET
{
"AppName": "MongoPop",
"Version": 1.0
}

Returns the version of the API.
/pop/ip
GET
{"ip": string}
Fetches the IP Address of the server running the Mongopop backend.
/pop/config
GET
{
mongodb: {
    defaultDatabase: string,
    defaultCollection: string,
    defaultUri: string
},
mockarooUrl: string
}

Fetches client-side defaults from the back-end config file.
/pop/addDocs
POST
{
MongoDBURI: string;
collectionName: string;
dataSource: string;
numberDocs: number;
unique: boolean;
}

{
success: boolean;
count: number;
error: string;
}

Add numberDocs batches of documents, using documents fetched from dataSource
/pop/sampleDocs
POST
{
MongoDBURI: string;
collectionName: string;
numberDocs: number;
}

{
success: boolean;documents: string;
error: string;
}

Read a sample of the documents from a collection.
/pop/countDocs
POST
{
MongoDBURI: string; 
collectionName: string;
}

{
success: boolean;count: number;
error: string;
}

Counts the number of documents in the collection.
/pop/updateDocs
POST
{
MongoDBURI: string;
collectionName: string;
matchPattern: Object;
dataChange: Object;
threads: number;
}

{
success: boolean;
count: number;
error: string;
}

Apply an update to all documents in a collection which match a given pattern

Apply an update to all documents in a collection which match a given pattern.

Most of the methods follow a very similar pattern and so only a few are explained here; refer to the DataService class to review the remainder.

The simplest method retrieves a count of the documents for a given collection:

sendCountDocs(CollName: string): Observable < MongoResult > {
    /*
    Use the Mongopop API to count the number of documents in the specified collection.
    It returns an Observable that delivers objects of type MongoResult.
    */

    // Need to indicate that the request parameters will be in the form
    // of a JSON document
    let headers = new Headers({
        'Content-Type': 'application/json'
    });
    let options = new RequestOptions({
        headers: headers
    });
    // The CountDocsRequest class contains the same elements as the
    // `pop/count` REST API POST method expects to receive
    let countDocsRequest = new CountDocsRequest(this.MongoDBURI, CollName);
    let url: string = this.baseURL + "countDocs";
    return this.http.post(url, countDocsRequest, options)
        .timeout(360000, new Error('Timeout exceeded'))
        .map(response => response.json())
        .catch((error: any) => {
            return Observable.throw(error.toString() || ' Server error')
        });
};



This method returns an Observable, which in turn delivers an object of type MongoResult. MongoResult is defined in MongoResult.ts

export class MongoResult {
    success: boolean;
    count: number;
    error: string;
    constructor(success: boolean, count ? : number, error ? : string) {
        this.success = success;
        this.count = count;
        this.error = error;
    }
}



The pop/count PUT method expects the request parameters to be in a specific format (see earlier table); to avoid coding errors, another Typescript class is used to ensure that the correct parameters are always included – CountDocsRequest.

export class CountDocsRequest {
    MongoDBURI: string;
    collectionName: string;
    constructor(MongoDBURI ? : string, collectionName ? : string) {
        this.MongoDBURI = MongoDBURI;
        this.collectionName = collectionName;
    }




http.post returns an Observable. If the Observable achieves a positive outcome then the map method is invoked to convert the resulting data (in this case, simply parsing the result from a JSON string into a Typescript/JavaScript object) before automatically passing that updated result through this method's own returned Observable.

The timeout method causes an error if the HTTP request doesn't succeed or fail within 6 minutes.

The catch method passes on any error from the HTTP request (or a generic error if error.toString() is null) if none exists.

The updateDBDocs method is a little more complex – before sending the request, it must first parse the user-provided strings representing:

  • The pattern identifying which documents should be updated
  • The change that should be applied to each of the matching documents

This helper function is used to parse the (hopefully) JSON string:

tryParseJSON (jsonString:string):Object{  

/* 
Attempts to build an object from the supplied string. Raises an error if 
the conversion fails (e.g. if it isn't valid JSON format). 
*/ 

try   {  
      let myObject = JSON.parse(jsonString); 

if (myObject && typeof myObject === "object")      {  
         return myObject;
      }
   }   catch (error)   {  
      let errorString = "Not valid JSON:"      + error.message; 
console.log(errorString); 
new Error(errorString);
   }   return   {  

   }   ;
};



If the string is a valid JSON document then tryParseJSON returns an object representation of it; if not then it returns an error.

A new class (UpdateDocsRequest) is used for the update request:

export class UpdateDocsRequest {
    MongoDBURI: string;
    collectionName: string;
    matchPattern: Object;
    dataChange: Object;
    threads: number;
    constructor(MongoDBURI ? : string, collectionName ? : string, matchPattern ? : Object, dataChange ? : Object, threads ? : number) {
        this.MongoDBURI = MongoDBURI;
        this.collectionName = collectionName;
        this.matchPattern = matchPattern;
        this.dataChange = dataChange;
        this.threads = threads;
    }
}


updateDBDocs is the method that is invoked from the component code:

updateDBDocs(collName: string, matchPattern: string, dataChange: string,
    threads: number): Observable < MongoResult > {
    /*
    Apply an update to all documents in a collection
    which match a given pattern. Uses the MongoPop API.
    Returns an Observable which either resolves to the results of the operation
    or throws an error.
    */
    let matchObject: Object;
    let changeObject: Object;
    try {
        matchObject = this.tryParseJSON(matchPattern);
    } catch (error) {
        let errorString = "Match pattern: " + error.message;
        console.log(errorString);
        return Observable.throw(errorString);
    }
    try {
        changeObject = this.tryParseJSON(dataChange);
    } catch (error) {
        let errorString = "Data change: " + error.message;
        console.log(errorString);
        return Observable.throw(errorString);
    }
    let updateDocsRequest = new UpdateDocsRequest(this.MongoDBURI, collName, matchObject, changeObject, threads);
    return this.sendUpdateDocs(updateDocsRequest)
        .map(results => {
            return results
        })
        .catch((error: any) => {
            return Observable.throw(error.toString() || ' Server error')
        })
}



After converting the received string into objects, it delegates the actual sending of the HTTP request to sendUpdateDocs:

sendUpdateDocs(doc: UpdateDocsRequest): Observable < MongoResult > {
    let headers = new Headers({
        'Content-Type': 'application/json'
    });
    let options = new RequestOptions({
        headers: headers
    });
    let url: string = this.baseURL + "updateDocs";
    return this.http.post(url, doc, options)
        .timeout(360000000, new Error('Timeout exceeded'))
        .map(response => response.json())
        .catch((error: any) => {
            return Observable.throw(error.toString() || " Server error")
        });
};


Continue following this blog series to step through building the remaining stages of the Mongopop application:

Sorry to cut you off, but we'll have the second part of this article up tomorrow! 

The Series: 

Discover when your data grows or your application performance demands increase, MongoDB Atlas allows you to scale out your deployment with an automated sharding process that ensures zero application downtime. Brought to you in partnership with MongoDB.

Topics:
mongodb ,angular 2 ,web dev ,js

Published at DZone with permission of Andrew Morgan, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

THE DZONE NEWSLETTER

Dev Resources & Solutions Straight to Your Inbox

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.

X

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}