Brian Wagner | Blog

Working with Data in Angular2 and ElasticSearch

Oct 18, 2016 | Last edit: Dec 11, 2017

Getting hands on data is crucial to developing any new web project. Incomplete data sets, small data sets, even bad data sets are preferable to nothing. Without mock data like this, we cannot answer many important questions. How will data be structured? What sort of queries will be required? What should the final presentation look like?

Mock data can be even more important when developing for use with APIs, in part to avoid putting too much traffic on a live service. Hitting a third-party API hundreds of times over a debugging session could ring up excess charges or get us blocked outright. Also, relying on a live connection rules out being able to work on development while offline.

Angular 2 provides a few ways to bring data sets into our development environment to begin testing out of the Angular-cli box. With the addition of one small Angular-team module, in-memory-web-api, we can also test the very same API queries that will be used in production. 

Here, I'm going to take one final step and integrate with ElasticSearch to show how the in-memory-web-api module can work seamlessly with an outside service. 

Angular CLI

Version: 1.0.0-beta.16

Angular 2 was something of a moving target for some time, until the 'official' release in September. The dev team has been known to move fast and break things, so the warranty on this may be limited. As of October 2016, this should work with:

  • Angular Core version 2.0.0
  • Angular CLI version 1.0.0-beta.16
  • Angular-in-memory-web-api version 0.1.7

I'm going to explain the various methods of adding mock data to a project, assuming we start from a new barebones Angular-Cli project. So after installing angular-cli, type ng new [project name]. CD into the project folder. Type ng serve and the project should be available at http://localhost:4200/. 

Mock Data in a Component

The easiest way to add data to a component is to put it inside the very same component file.

ng generate component people

Inside the people.component.ts file:

let const People = [
        {"name": "Larry", "age": "35", "color": "red"},
        {"name": "Sally", "age": "14", "color": "navy"},
        {"name": "Megan", "age": "45", "color": "purple"},
        {"name": "Charles", "age": "50", "color": "green"}
    ]
 
@Component({
...
})
 
export class PeopleComponent implements OnInit {
 
let people = People
...
}

Now we should be able to create some elements in the component template, and iterate over the array of People. 

Mock Data in a Separate file

The next step would be to move this data into its own file, and simply import it. 

people-data.ts

export const People = [
        {"name": "Larry", "age": "35", "color": "red"},
        {"name": "Sally", "age": "14", "color": "navy"},
        {"name": "Megan", "age": "45", "color": "purple"},
        {"name": "Charles", "age": "50", "color": "green"}
    ]

people.component.ts

...
import { People } from 'path_to_file';
...

Moving Data Gathering into a Service

Everything above is fine for getting data into the project in the most direct way possible. This may be sufficient for providing static data, even in a production setting. 

But Angular expects all the data-gathering to happen outside of a Component, and inside a Service. That way it's re-usable, and it satisfies expectations for isolating the responsibilities of the various structures in the Angular 2 architecture. Components are like the basic building blocks that use and present the data we have. But, in the case of an API query, the Component shouldn't care how we get that data. That problem falls to the Service, whose job it is to provision that data, ideally as Promise objects. 

Angular cli will create the Service for us, just type:

ng generate service get-people

Personally, I like to have those Services in a services dir, so I create that now and move the file in there. Also, we'll have to fix the path in the app.module.ts file.

A Service can be injected individually to a component, or globally for all. The CLI leaves that decision to us, so after generating the service file, we will need to include it in the global providers list.

app.module.ts

...
  providers: [GetPeopleService]
...

Now we can shift the getting of that data into the Service. So in the GetPeopleService, we import the raw People data, and export it for the PeopleComponent. And the PeopleComponent will need to import the GetPeopleService, and assign it to a private instance. Let's start building the functions we'll need once we start using the real API calls.

get-people.service.ts

import { People } from './people-data';
...
@Injectable()
export class GetPeopleService {
 
  private people = People;
  ...
getPeople() {
    return this.people
}

Now this data is reusable and, once we add API services shortly, will be a self-contained data provider. Let's modify the component to begin using it.

people.component.ts

import { GetPeopleService } ...
 
...
 
export class PeopleComponent implements OnInit {
 
  constructor(
    private getpeople: GetPeopleService
  )
 
...
 
ngOnInit() {
    this.people = getpeople.getPeople();
}

Aside: Building a Class for Data Objects

If we want to wrap those data objects into a class, we can do that now.

person.ts

export class Person {
    name: string;
    age: string;
    color: string;
}

get-people.service.ts

import { Person } from './person';
 
...
 
export class GetPeopleService {
 
...
 
    getPeople() {
        return this.people.map(toPerson)
    }
}
 
function toPerson(d:any): Person {
    let person = ({
        name: d.name,
        age: d.age,
        color: d.color
    })
    return person;
}

Aside: ElasticSearch Data Formats

ElasticSearch has its own unique way of returning data which we will have to accommodate to use it for local development or in a production setting. A typical response looks like this:

{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "failed" : 0
  },
  "hits" : {
    "total" : 1,
    "max_score" : 1.0,
    "hits" : [ {
      "_index" : "persons",
      "_type" : "person",
      "_id" : "1",
      "_score" : 1.0,
      "_source" : {
        "name" : "Larry",
        "age" : "35",
        "color" : "red"
      }
    } ]
  }
}

Two things to note:

  • there is no .data property on the response, typical of many async calls. This will be a complicating factor later on with Angular's in memory web api
  • the response is a json object in the format {"hits": {"hits: [ {"_source": { ... }} ... ] } }

So from repsonse.hits.hits, we can extract the array of data we're looking for. And each item in the array will have a _source property with the original inputs. We can edit the mock data to emulate this format, and modify the service methods to recognize it.

people-data.ts

export const People = { "hits": { "hits": 
    [
        {"_source": {"name": "Larry", "age": "35", "color": "red"} },
        ...
    ]
}}

get-people.service.ts

getPeople() {
    return this.people.hits.hits.map(toPerson)
}
 
...
 
function toPerson(d:any): Person {
    let person = ({
        name: d._source.name,
        age: d._source.age,
        color: d._source.color
    })
    return person;
}

Mocking Data with Angular's Own In-Memory Web API

Members of the Angular 2 team have built a data-mocking tool that integrates easily with projects built from Angular-CLI, even though it's not included by default. To pull it in, we can add it to our package.json list, and run npm install to grab the bundle. After that we include it in the app.module file and create a new file with the data. The project has instructions on their Github page. For us, the data will appear something like below:

in-memory-data.service.ts

  createDb() {
    let people = {"hits": {"hits": 
    [
        {"_source": {"name": "Larry", "age": "35", "color": "red"}},
        ...
    ]
  }}

If you plan NOT to further integrate with ElasticSearch, it's wise to get rid of the response.hits.hits structure. The In-Memory Web API tool has some additional data filtering options which you may find useful and will probably not work properly with this format. But as a step on the path to ElasticSearch, this may be helpful.

Setting up ElasticSearch

Better explanations on installing and setting up ElasticSearch have been written by others, so here's a good one. ES 101.