0

Behavior-Driven Angular – Part 2: Inserting REST Data as “innerHTML” into a Web Application


Today, we will extend the behavior-driven development example of the previous blog post and add the blog content to the document. Like last time, we will retrieve the HTML content from the WordPress API. Sounds easy, right? We will see that the challenge is to display the HTML content correctly, so we do not see escaped HTML like “<p>…” on the page.

As in part 1, we will follow a “test first” strategy: we will create the e2e test specification before we implement the actual code.

Within the Protractor/Jasmine framework, we will learn how to match the text and the inner HTML of browser DOM elements with functions like expect(...).toEqual("..."), .toContain("...") and .toMatch(/regex/) functions. The latter gives us the full flexibility of regular expressions.

Check out this book on Amazon: Angular Test-Driven Development

Plan for Today

Today, we plan to complement the blog title we have shown last time with the blog content, similar to the blog post Angular 4 Hello World Quickstart, which we will uses as our data mine. We will only show the title and the content as follows:

Before we start coding, we will add an e2e test that defines our expectation.

Step 0: Clone the GIT Repository and install the Application

This step can be skipped if you have followed part 1 of this series.

I am assuming that you have a Docker host available with 1.5GB or more RAM, GIT is installed on that host.

alias cli='docker run -it --rm -w /app -v $(pwd):/app -p 4200:4200 oveits/angular-cli:1.4.3 $@'
alias protractor='docker run -it --privileged --rm --net=host -v /dev/shm:/dev/shm -v $(pwd):/protractor webnicer/protractor-headless $@'
git clone https://github.com/oveits/consuming-a-restful-web-service-with-angular.git
cd consuming-a-restful-web-service-with-angular
git checkout -b 320ae88
cli npm i
chown -R $(whoami) .
cli ng serve --host 0.0.0.0

Phase 1: Create an e2e Test

Step 1.1: Create a GIT Feature Branch

As always with a new feature, let us create a feature branch (on a second terminal):

$ cd /vagrant/consuming-a-restful-web-service-with-angular/

$ protractor
[20:24:22] I/direct - Using ChromeDriver directly...
[20:24:22] I/launcher - Running 1 instances of WebDriver
Jasmine started

  consuming-a-restful-web-service-with-angular App
    ✓ should display the title

Executed 1 of 1 spec SUCCESS in 0.756 sec.
[20:24:27] I/launcher - 0 instance(s) of WebDriver still running
[20:24:27] I/launcher - chrome #01 passed

$ git checkout -b feature/0004-add-blog-content

You might need to adapt the path to your project. The protractor command is optional, but it will ensure that the e2e tests have worked on your machine before you start changing the code. I have seen some permissions topic described in the Appendices, which have made me cautious.

Step 1.2 (optional): Apply new Test Functions to the Blog Title

We would like to add a test that checks, whether the blog content is showing on the page. There are many Jasmine specification examples out there. Somehow, I have stumbled over this example. In order to verify that the functions I found there work fine, I thought it would be a good idea to write a new test similar to the ones in the example, but apply the test to the blog title before we write a new test for the blog content. This way, we can verify that we apply the correct syntax.

I have kept the original specification code, but I have added following code to the spec:

// e2e/app.e2e-spec.ts
import { browser, by, element } from 'protractor';
import { AppPage } from './app.po';

describe('consuming-a-restful-web-service-with-angular App', () => {
  let page: AppPage;

  beforeEach(() => {
    page = new AppPage();
  });

  it('should display the title', () => {
    page.navigateTo();
    expect(page.getParagraphText()).toContain('Angular 4 Hello World Quickstart');
  });
});

describe('Blog', () => {

  beforeEach(() => {
    browser.get('/');
  });

  it('should display the blog title as header 1 and id="blog_title"', () => {
    expect(element(by.css('h1')).getText()).toEqual('Angular 4 Hello World Quickstart');
  });
});

Both protractor e2e tests are successful without changing the code:

$ protractor
[20:59:51] I/direct - Using ChromeDriver directly...
[20:59:51] I/launcher - Running 1 instances of WebDriver
Jasmine started

  consuming-a-restful-web-service-with-angular App
    ✓ should display the title

  Blog
    ✓ should display the blog title as header 1 and id="blog_title"

Executed 2 of 2 specs SUCCESS in 2 secs.
[20:59:57] I/launcher - 0 instance(s) of WebDriver still running
[20:59:57] I/launcher - chrome #01 passed

Save it. Note: for pushing the changes to Github, you will need to fork my project and work with your fork. Otherwise, you can keep the git backups locally only.

git commit -am'1.2 added an addional test for the title looking for the first H1 header (successful test)'
git push

Step 1.3 (optional): Refine the Test

Step 1.3.1 Create a Test looking for a specific Element per ID

Since the blog content will not be a header, we will need to look for something, which is unique on the page. We use an ID for fetching the correct element from the page:

import { browser, by, element } from 'protractor';

...

describe('Blog', () => {

  beforeEach(() => {
    browser.get('/');
  });

  const blog_title = element(by.id('blog_title'));

  it('should display the blog title as header 1 and id="blog_title"', () => {
    expect(element(by.css('h1')).getText()).toEqual('Angular 4 Hello World Quickstart');
    expect(blog_title.getText()).toEqual('Angular 4 Hello World Quickstart');
  });
});

Now the protractor test will fail. This is because we have not set the ID on the HTML template yet:

$ protractor
[21:07:51] I/direct - Using ChromeDriver directly...
[21:07:51] I/launcher - Running 1 instances of WebDriver
Jasmine started

  consuming-a-restful-web-service-with-angular App
    ✓ should display the title

  Blog
    ✗ should display the blog title as header 1 and id="blog_title"
      - Failed: No element found using locator: By(css selector, *[id="blog_title"])
          at WebDriverError (/usr/local/lib/node_modules/protractor/node_modules/selenium-webdriver/lib/error.js:27:5)
          at NoSuchElementError (/usr/local/lib/node_modules/protractor/node_modules/selenium-webdriver/lib/error.js:242:5)
          at /usr/local/lib/node_modules/protractor/built/element.js:808:27
          at ManagedPromise.invokeCallback_ (/usr/local/lib/node_modules/protractor/node_modules/selenium-webdriver/lib/promise.js:1379:14)
          at TaskQueue.execute_ (/usr/local/lib/node_modules/protractor/node_modules/selenium-webdriver/lib/promise.js:2913:14)
          at TaskQueue.executeNext_ (/usr/local/lib/node_modules/protractor/node_modules/selenium-webdriver/lib/promise.js:2896:21)
          at asyncRun (/usr/local/lib/node_modules/protractor/node_modules/selenium-webdriver/lib/promise.js:2775:27)
          at /usr/local/lib/node_modules/protractor/node_modules/selenium-webdriver/lib/promise.js:639:7
          at process._tickCallback (internal/process/next_tick.js:103:7)Error
          at ElementArrayFinder.applyAction_ (/usr/local/lib/node_modules/protractor/built/element.js:461:27)
          at ElementArrayFinder._this.(anonymous function) [as getText] (/usr/local/lib/node_modules/protractor/built/element.js:103:30)
          at ElementFinder.(anonymous function) [as getText] (/usr/local/lib/node_modules/protractor/built/element.js:829:22)
          at Object. (/protractor/e2e/app.e2e-spec.ts:28:23)
          at /usr/local/lib/node_modules/protractor/node_modules/jasminewd2/index.js:94:23
          at new ManagedPromise (/usr/local/lib/node_modules/protractor/node_modules/selenium-webdriver/lib/promise.js:1082:7)
          at controlFlowExecute (/usr/local/lib/node_modules/protractor/node_modules/jasminewd2/index.js:80:18)
          at TaskQueue.execute_ (/usr/local/lib/node_modules/protractor/node_modules/selenium-webdriver/lib/promise.js:2913:14)
          at TaskQueue.executeNext_ (/usr/local/lib/node_modules/protractor/node_modules/selenium-webdriver/lib/promise.js:2896:21)
          at asyncRun (/usr/local/lib/node_modules/protractor/node_modules/selenium-webdriver/lib/promise.js:2820:25)
      From: Task: Run it("should display the blog title as header 1 and id="blog_title"") in control flow
          at Object. (/usr/local/lib/node_modules/protractor/node_modules/jasminewd2/index.js:79:14)
          at /usr/local/lib/node_modules/protractor/node_modules/jasminewd2/index.js:16:5
          at ManagedPromise.invokeCallback_ (/usr/local/lib/node_modules/protractor/node_modules/selenium-webdriver/lib/promise.js:1379:14)
          at TaskQueue.execute_ (/usr/local/lib/node_modules/protractor/node_modules/selenium-webdriver/lib/promise.js:2913:14)
          at TaskQueue.executeNext_ (/usr/local/lib/node_modules/protractor/node_modules/selenium-webdriver/lib/promise.js:2896:21)
          at asyncRun (/usr/local/lib/node_modules/protractor/node_modules/selenium-webdriver/lib/promise.js:2775:27)
      From asynchronous test:
      Error
          at Suite. (/protractor/e2e/app.e2e-spec.ts:26:3)
          at Object. (/protractor/e2e/app.e2e-spec.ts:18:1)
          at Module._compile (module.js:570:32)
          at Module.m._compile (/protractor/node_modules/ts-node/src/index.ts:392:23)
          at Module._extensions..js (module.js:579:10)
          at Object.require.extensions.(anonymous function) [as .ts] (/protractor/node_modules/ts-node/src/index.ts:395:12)

**************************************************
*                    Failures                    *
**************************************************

1) Blog should display the blog title as header 1 and id="blog_title"
  - Failed: No element found using locator: By(css selector, *[id="blog_title"])

Executed 2 of 2 specs (1 FAILED) in 2 secs.
[21:07:58] I/launcher - 0 instance(s) of WebDriver still running
[21:07:58] I/launcher - chrome #01 failed 1 test(s)
[21:07:58] I/launcher - overall: 1 failed spec(s)
[21:07:58] E/launcher - Process exited with error code 1

To save the change:

git commit -am'1.3.1 search title by element id (failed e2e test)'

Step 1.3.2 Fix the Test

Let us fix the failed test like follows: In the HTML template src/app/app.component.html, we specify the element ID:

<h1 id="blog_title">{{title}}</h1>

Now the protractor test is successful again:

$ protractor
[21:14:27] I/direct - Using ChromeDriver directly...
[21:14:27] I/launcher - Running 1 instances of WebDriver
Jasmine started

  consuming-a-restful-web-service-with-angular App
    ✓ should display the title

  Blog
    ✓ should display the blog title as header 1 and id="blog_title"

Executed 2 of 2 specs SUCCESS in 2 secs.
[21:14:34] I/launcher - 0 instance(s) of WebDriver still running
[21:14:34] I/launcher - chrome #01 passed

That was simple. Now let us apply our learnings to the blog content.

To save the change:

git commit -am'1.3.2 add ID to HTML template (success)'; git push

Phase 2: Create the Test for the Blog Content

The content of the blog can be seen on WordPress:

The content starts with: In this hello world style tutorial, we will follow a step by step guide to a working Angular 4 application.

Let us search for that on our application.

Step 2.1 Add Blog Content e2e Tests

Similar to what we have done for the Blog Title, let us create an e2e test for the blog content. We add the parts in blue to e2e/app.e2e-spec.ts:

// e2e/app.e2e-spec.ts
...
describe('Blog', () => {

  beforeEach(() => {
    browser.get('/');
  });

  const blog_title = element(by.id('blog_title'));
  const blog_content = element(by.id('blog_content'));

  it('should display the blog title as header 1 and id="blog_title"', () => {
    expect(element(by.css('h1')).getText()).toEqual('Angular 4 Hello World Quickstart');
    expect(blog_title.getText()).toEqual('Angular 4 Hello World Quickstart');
  });

  it('should display the blog content', () => {
    expect(blog_content.getText()).toContain('In this hello world style tutorial, we will follow a step by step guide to a working Angular 4 application.');
  });
});

Since the content is quite large, we did not compare it with the equality operator, but we have used the ‘toContain’ function instead.

The new protractor test fails as expected:

$ protractor
[21:23:04] I/direct - Using ChromeDriver directly...
[21:23:04] I/launcher - Running 1 instances of WebDriver
Jasmine started

  consuming-a-restful-web-service-with-angular App
    ✓ should display the title

  Blog
    ✓ should display the blog title as header 1 and id="blog_title"
    ✗ should display the blog content
      - Failed: No element found using locator: By(css selector, *[id="blog_content"])
          at WebDriverError (/usr/local/lib/node_modules/protractor/node_modules/selenium-webdriver/lib/error.js:27:5)
          at NoSuchElementError (/usr/local/lib/node_modules/protractor/node_modules/selenium-webdriver/lib/error.js:242:5)
          at /usr/local/lib/node_modules/protractor/built/element.js:808:27
          at ManagedPromise.invokeCallback_ (/usr/local/lib/node_modules/protractor/node_modules/selenium-webdriver/lib/promise.js:1379:14)
          at TaskQueue.execute_ (/usr/local/lib/node_modules/protractor/node_modules/selenium-webdriver/lib/promise.js:2913:14)
          at TaskQueue.executeNext_ (/usr/local/lib/node_modules/protractor/node_modules/selenium-webdriver/lib/promise.js:2896:21)
          at asyncRun (/usr/local/lib/node_modules/protractor/node_modules/selenium-webdriver/lib/promise.js:2775:27)
          at /usr/local/lib/node_modules/protractor/node_modules/selenium-webdriver/lib/promise.js:639:7
          at process._tickCallback (internal/process/next_tick.js:103:7)Error
          at ElementArrayFinder.applyAction_ (/usr/local/lib/node_modules/protractor/built/element.js:461:27)
          at ElementArrayFinder._this.(anonymous function) [as getText] (/usr/local/lib/node_modules/protractor/built/element.js:103:30)
          at ElementFinder.(anonymous function) [as getText] (/usr/local/lib/node_modules/protractor/built/element.js:829:22)
          at Object. (/protractor/e2e/app.e2e-spec.ts:33:25)
          at /usr/local/lib/node_modules/protractor/node_modules/jasminewd2/index.js:94:23
          at new ManagedPromise (/usr/local/lib/node_modules/protractor/node_modules/selenium-webdriver/lib/promise.js:1082:7)
          at controlFlowExecute (/usr/local/lib/node_modules/protractor/node_modules/jasminewd2/index.js:80:18)
          at TaskQueue.execute_ (/usr/local/lib/node_modules/protractor/node_modules/selenium-webdriver/lib/promise.js:2913:14)
          at TaskQueue.executeNext_ (/usr/local/lib/node_modules/protractor/node_modules/selenium-webdriver/lib/promise.js:2896:21)
          at asyncRun (/usr/local/lib/node_modules/protractor/node_modules/selenium-webdriver/lib/promise.js:2820:25)
      From: Task: Run it("should display the blog content") in control flow
          at Object. (/usr/local/lib/node_modules/protractor/node_modules/jasminewd2/index.js:79:14)
          at /usr/local/lib/node_modules/protractor/node_modules/jasminewd2/index.js:16:5
          at ManagedPromise.invokeCallback_ (/usr/local/lib/node_modules/protractor/node_modules/selenium-webdriver/lib/promise.js:1379:14)
          at TaskQueue.execute_ (/usr/local/lib/node_modules/protractor/node_modules/selenium-webdriver/lib/promise.js:2913:14)
          at TaskQueue.executeNext_ (/usr/local/lib/node_modules/protractor/node_modules/selenium-webdriver/lib/promise.js:2896:21)
          at asyncRun (/usr/local/lib/node_modules/protractor/node_modules/selenium-webdriver/lib/promise.js:2775:27)
      From asynchronous test:
      Error
          at Suite. (/protractor/e2e/app.e2e-spec.ts:32:3)
          at Object. (/protractor/e2e/app.e2e-spec.ts:18:1)
          at Module._compile (module.js:570:32)
          at Module.m._compile (/protractor/node_modules/ts-node/src/index.ts:392:23)
          at Module._extensions..js (module.js:579:10)
          at Object.require.extensions.(anonymous function) [as .ts] (/protractor/node_modules/ts-node/src/index.ts:395:12)

**************************************************
*                    Failures                    *
**************************************************

1) Blog should display the blog content
  - Failed: No element found using locator: By(css selector, *[id="blog_content"])

Executed 3 of 3 specs (1 FAILED) in 3 secs.
[21:23:12] I/launcher - 0 instance(s) of WebDriver still running
[21:23:12] I/launcher - chrome #01 failed 1 test(s)
[21:23:12] I/launcher - overall: 1 failed spec(s)
[21:23:12] E/launcher - Process exited with error code 1

To save the change:

git commit -am'2.1 add test for blog content (failed)'; git push

Step 2.2 Fix the Blog Content Test

Let us fix the test now.

Step 2.2.1 Add the Blog Content to the HTML Template

In order to display the blog content, we need to add the following to the HTML template src/app/app.component.html:

Step 2.2.2 Define the Variable ‘content’ in the Component

However, as long as the variable ‘content’ is not defined, we will have added an empty div. To define the variable, we must change the component src/app/app.component.ts

import { Component, OnInit } from '@angular/core';
import { Http } from '@angular/http';
import { Response } from '@angular/http';
import 'rxjs/add/operator/map'

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {

  title : any = null
  content : any = null

  constructor(private _http: Http) {}

  ngOnInit() {
     this._http.get('https://public-api.wordpress.com/rest/v1.1/sites/oliverveits.wordpress.com/posts/3078')
                .map((res: Response) => res.json())
                 .subscribe(data => {
                        this.title = data.title;
                        this.content = data.content;
                        console.log(data);
                });
  }
}

That’s it: the e2e tests are successful:

$ protractor
[21:30:12] I/direct - Using ChromeDriver directly...
[21:30:12] I/launcher - Running 1 instances of WebDriver
Jasmine started

  consuming-a-restful-web-service-with-angular App
    ✓ should display the title

  Blog
    ✓ should display the blog title as header 1 and id="blog_title"
    ✓ should display the blog content

Executed 3 of 3 specs SUCCESS in 3 secs.
[21:30:19] I/launcher - 0 instance(s) of WebDriver still running
[21:30:19] I/launcher - chrome #01 passed

To save the change:

git commit -am'2.2.2 Added content to HTML template and component (success)'; git push

Step 2.3 Explore the Result

Now let us have a look at what we have accomplished and let us open the browser on http://localhost:4200:

The good news is: the content is there.

😉

The bad news it: it is not readable because the HTML code in the blog content variable has been HTML escaped.

😦

This is the standard behavior in Angular. So what can we do now? The solution to the problem can be found in Step 2.3 of my original post: we need to set the innerHTML of the div instead of adding the content as text. But, as we are performing a “behavior-driven” approach, let us try to write the tests first.

Step 2.4 Improve the e2e Test Spec

Let us add an additional line to the test specification in order to make sure, we will see the HTML in the correct format:

import { browser, by, element } from 'protractor';

describe('Blog', () => {

  beforeEach(() => {
    browser.get('/');
  });

  const blog_title = element(by.id('blog_title'));
  const blog_content = element(by.id('blog_content'));

  it('should display the blog title as header 1 and id="blog_title"', () => {
    expect(element(by.css('h1')).getText()).toEqual('Angular 4 Hello World Quickstart');
    expect(blog_title.getText()).toEqual('Angular 4 Hello World Quickstart');
  });

  it('should display the blog content', () => {
    expect(blog_content.getText()).toContain('In this hello world style tutorial, we will follow a step by step guide to a working Angular 4 application.');
    
  });
});

With that, we test, whether the innerHTML of the div element starts with the correct HTML code. For that, we have made use of two functionalities of Jasmine:

  1. reading the innerHTML of an element with the getInnerHtml() function
  2. matching against a regular expression with toMatch(/regexp/)

As expected, the protractor test fails with the message

To save the change:

git commit -am'2.4 added innerHTML test for content with regular expression (fail)'; git push

Step 2.5 Fulfill the improved e2e Test

We can see that the content is escaped (e.g.  instead of ). Let us fix that by specifying the innerHTML like follows:

As soon as the content is loaded, the innerHTML ‘Loading…’ will be replaced by the content retrieved from WordPress.

Let us run the test:

$ protractor
[20:55:50] I/direct - Using ChromeDriver directly...
[20:55:50] I/launcher - Running 1 instances of WebDriver
Jasmine started
[20:55:56] W/element - more than one element found for locator By(css selector, app-root h1) - the first result will be used

  consuming-a-restful-web-service-with-angular App
    ✓ should display blog title

[20:55:57] W/element - more than one element found for locator By(css selector, h1) - the first result will be used
  Blog
    ✓ should display the blog title as header 1 and id="blog_title"
    ✓ should display the blog content

Executed 3 of 3 specs SUCCESS in 3 secs.
[20:55:57] I/launcher - 0 instance(s) of WebDriver still running
[20:55:57] I/launcher - chrome #01 passed

That was easy, again.

To save the change:

git commit -am'2.5 Fix the content innerHTML test (success)'; git push

Step 3: Explore the Final Result

Now let us head over to the browser on URL http://localhost:4200 again:

Even though there is no styling implemented yet, that looks much better now. This is, what we had in mind to implement today.

Excellent! Thump up!

 

As a wrap-up, the changes can be merged into the develop branch: the tests are successful and also the explorative “tests” have shown a correct result.


git checkout develop
git merge feature/0004-add-blog-content
git push

Summary

In this blog post, we have shown how to retrieve HTML-formated data from the WordPress API and display it in a correct format. In a “test-driven” approach, we have created Protractor e2e test specifications, before we have implemented the function.

Appendix: Error message: failed loading configuration file ./protractor.conf.js

After successfully cloning and installing the repo, I had seen following error message, when trying to perform the e2e tests:

$ protractor
[19:23:16] E/configParser - Error code: 105
[19:23:16] E/configParser - Error message: failed loading configuration file ./protractor.conf.js
[19:23:16] E/configParser - Error: Cannot find module 'jasmine-spec-reporter'
    at Function.Module._resolveFilename (module.js:469:15)
    at Function.Module._load (module.js:417:25)
    at Module.require (module.js:497:17)
    at require (internal/module.js:20:19)
    at Object. (/protractor/protractor.conf.js:4:26)
    at Module._compile (module.js:570:32)
    at Object.Module._extensions..js (module.js:579:10)
    at Module.load (module.js:487:32)
    at tryModuleLoad (module.js:446:12)
    at Function.Module._load (module.js:438:3)

Resolution:

I have seen that the cli command was creating all files as user root. This was because I had defined

alias cli='docker run -it --rm -w /app -v $(pwd):/app oveits/angular-cli:1.4.3 $@'

After changing this to

alias cli='docker run -it --rm -w /app -v $(pwd):/app -p 4200:4200 -u $(id -u $(whoami)) oveits/angular-cli:1.4.3 $@'

and re-performing the cli npm i after the clone, the problem was resolved. However, this has caused the next ‘npm i’ issue described below, and it is better to perform following workaround:

Better:

  1. Keep the first version of the alias
  2. After applying ‘cli npm i’, perform the command sudo chown -R $(whoami) PROJECT_ROOT_DIR .

Appendix npm i: Error: EACCES: permission denied, mkdir ‘/.npm’

npm ERR! Linux 4.2.0-42-generic
npm ERR! argv "/usr/local/bin/node" "/usr/local/bin/npm" "i"
npm ERR! node v6.11.2
npm ERR! npm  v3.10.10
npm ERR! path /.npm
npm ERR! code EACCES
npm ERR! errno -13
npm ERR! syscall mkdir

npm ERR! Error: EACCES: permission denied, mkdir '/.npm'
npm ERR!     at Error (native)
npm ERR!  { Error: EACCES: permission denied, mkdir '/.npm'
npm ERR!     at Error (native)
npm ERR!   errno: -13,
npm ERR!   code: 'EACCES',
npm ERR!   syscall: 'mkdir',
npm ERR!   path: '/.npm',
npm ERR!   parent: 'consuming-a-restful-web-service-with-angular' }
npm ERR!
npm ERR! Please try running this command again as root/Administrator.

npm ERR! Please include the following file with any support request:
npm ERR!     /app/npm-debug.log

The reason is, that I had defined

alias cli='docker run -it --rm -w /app -v $(pwd):/app -p 4200:4200 -u $(id -u $(whoami)) oveits/angular-cli:1.4.3 $@'

With that, npm i is run as the vagrant user with ID=900. However, inside the container, neither the user “vagrant” nor the user ID 900 is defined. This seems to cause the problem that the cli npm i command wants to create a directory /$HOME/.npm, but $HOME is not set. Therefore, the user with id=900 wants to create the file /.npm, but only root is allowed to do so.

The better workaround is to define

alias cli='docker run -it --rm -w /app -v $(pwd):/app -p 4200:4200 oveits/angular-cli:1.4.3 $@'

without the -u option and perform a command

chown -R $(whoami) .

where needed (e.g. after each npm i command).

0

Behavior-Driven Angular – part 1: Consuming a RESTful Web Service with Angular 4


In this step-by-step tutorial, we will follow a behavior-driven development approach to create an Angular 4 application from Angular CLI. The hello-world-like application will consume the WordPress REST API and it will display a blog post title. We will create and run end-to-end test scripts that simulate the customer behavior on a Chrome browser within a Protractor headless Docker container.

As a side feature of this tutorial, we will demonstrate basic Git handling: we will learn how to create a GIT Repository, create a feature branch, commit code changes, and merge the tested and fully functional feature branch into the main development branch.

Check out this book on Amazon: Angular Test-Driven Development

Introduction

My post Consuming a RESTful Web Service with Angular 4 has grown much more popular than expected. Thanks a lot for your views! The September ist still not finished, and the article has achieved more than 3000 views in its fourth month. I hope the trend will keep on:

😉

So, why would I want to rework a blog post that seemingly plays a chord in the developer’s community? The reasons I have started to rework the example are:

  • I have come to a point, where I had the need to refactor the code. However, I do not like refactoring before I do not have a good coverage of end-to-end tests. This was fixed easily in my previous blog post Angular end-to-end Testing.
  • The next topic was not so easy to be resolved: I had created a working example, but when I have created a GIT repository from it, Angular CLI had a problem with new clones of that code. An Angular problem I could not resolve easily and it looked like I had to start from scratch. This is, what I am doing now, committing many snapshots to GIT. If I so, why not explaining to my audience, what I am doing and why? This way, the current post has become an example that demonstrates basic GIT handling.

This blog post will fix those two issues, I deem.

Even if I am tempted to automate many of the development process steps, we will keep it simple without the usage of DevOps tools like Jenkins with BitBucket, Sonar, BrowserStack, JMeter Integration and Docker data center integration you would find in real-world agile projects. Some of such topics can be explored in more detail on my other blog posts on Jenkins (explore the “Jenkins Tutorial” drop-down menu of my blog).

Why behavior driven development?

I have made a very good experience with behavior driven development (BDD), or “test first” development. Some years ago, I have applied this principle on a ProvisioningEngine I had developed based on Ruby on Rails and java (Apache Camel). The advantages of BDD I see are:

  • better customer view: if you follow the behavior driven principle, your first thought is, how the web pages look like and how the web pages behave with respect to customer actions — in detail. This helps me to always start with the customer view in mind.
  • higher motivation: as a developer, I find it rewarding to start with test development with “red” test cases that become green over time
  • higher quality: I often challenge myself to optimize my code (e.g. make it DRYer of more versatile). In this process, I do not want to sacrifice and previous achievements. A large set of unit tests and e2e test help me to keep the set of features intact in phases of code restructuring

Okay, as an Angular beginner, I deem I am far from being an ideal behavior driven Angular developer. However, at some point in future, I believe that I can increase my hobby development productivity by applying principles like BDD together with build&deployment automation based on TravisCI, CircleCI or a local Jenkins system to my development approach.

Overview

Along the way, we will get acquainted with a set of typical error messages and we will learn how to cope with them.

So, if you are ready for a quick ride into a simple “test first” strategy example with GIT repo handling, buckle up and start coding with me in four phases:

😉

  • Phase 1: Create a Hello World App based on Angular CLI
  • Phase 2: Adapt the end-to-end Tests
  • Phase 3: Adapt the Code
  • Phase 4: Verify the successful e2e Tests

If you do not care about BDD and GIT, then you might also want head over to the post Consuming a RESTful Web Service with Angular 4. Or better, follow the instructions you find here, but omit the steps related to e2e testing (protractor) and/or GIT.

Phase 1: Create a Hello World App based on Angular CLI

In this phase, we will

  • use an Angular CLI Docker image to create a new application
  • fix some problems with the end to end testing inherent in the standard hello world app
  • save and upload the changes to GIT

Step 1.0: Get access to a Docker Host with enough Resources

If you do not have access to a Docker host yet, I recommend following the step 0 instructions on my JHipster post. I recommend to use a Docker host with at least 1.5 GB RAM. To be honest, this is a guess. I always test on a 4 GB Docker Host Virtualbox VM, but I know that 750 MB RAM is not sufficient.

Step 1.1: Prepare an alias for later use

Let us first define an alias that helps us to shorten the commands thereafter.

(dockerhost)$ alias cli='docker run -it --rm -w /app -v $(pwd):/app -p 4200:4200 -u $(id -u $(whoami)) oveits/angular-cli:1.4.3 $@'

Why this complicated user option -u $(id -u $(whoami))? The reason is that

  • if we omit it, then all new files will be created as root, so we will get permissions problems later on
  • If we use ‘centos’, then the container will complain that he does not find the user ‘centos’ in its passwd file
  • If we use the ID of centos, then it works. However, it might not work in all cases. This time, the ID of centos user is 1000, and by chance, a user (named ‘node’) exists on the container as well. But let us live with this uncertainty for now.

With each cli something command, we will start a something command on an Angular CLI @ Alpine container originally created by Alex Such and enriched with git and bash by me.

Consider appending the alias command to your Docker host’s ~/.bashrc file, so the alias is persistent.

Step 1.2: Create a Project and install required Modules

Now let us create a new project and install the node modules via npm:

(dockerhost)$ cli ng new consuming-a-restful-web-service-with-angular
(dockerhost)$ cd consuming-a-restful-web-service-with-angular
(dockerhost)$ cli npm install
npm info it worked if it ends with ok
npm info using npm@3.10.10
npm info using node@v6.11.2
npm info attempt registry request try #1 at 7:54:24 PM
npm http request GET https://registry.npmjs.org/fsevents
npm http 200 https://registry.npmjs.org/fsevents
npm info lifecycle consuming-a-restful-web-service-with-angular@0.0.0~preinstall: consuming-a-restful-web-service-with-angular@0.0.0
npm info linkStuff consuming-a-restful-web-service-with-angular@0.0.0
npm info lifecycle consuming-a-restful-web-service-with-angular@0.0.0~install: consuming-a-restful-web-service-with-angular@0.0.0
npm info lifecycle consuming-a-restful-web-service-with-angular@0.0.0~postinstall: consuming-a-restful-web-service-with-angular@0.0.0
npm info lifecycle consuming-a-restful-web-service-with-angular@0.0.0~prepublish: consuming-a-restful-web-service-with-angular@0.0.0
npm WARN optional SKIPPING OPTIONAL DEPENDENCY: fsevents@^1.0.0 (node_modules/chokidar/node_modules/fsevents):
npm WARN notsup SKIPPING OPTIONAL DEPENDENCY: Unsupported platform for fsevents@1.1.2: wanted {"os":"darwin","arch":"any"} (current: {"os":"linux","arch":"x64"})
npm info ok

Step 1.3 (optional): Create a local GIT Repository

Now is a good time to create a git repository and to commit the initial code.

If you have not installed GIT on your Docker host, depending on the operating system of your Docker host, you might need to install it first (e.g. apt-get update; apt-get install -y git in case of Ubuntu, or yum install -y git in case of CentOS). Alternatively, you may want to use the git I have installed in the container. In that case, prepend a “do” before the git command, e.g. try cli git --version. However, a git diff does not look nice in a container, so I recommend to install GIT on your Docker host instead.

Now let us initialize the git repo, add all files and commit the changes:

(dockerhost)$ git init
(dockerhost)$ git add .
(dockerhost)$ git commit -m'initial commit'

Now let us start the service in a container:

(dockerhost)$ cli ng serve --host 0.0.0.0
** NG Live Development Server is listening on 0.0.0.0:4200, open your browser on http://localhost:4200/ **
Date: 2017-09-26T20:04:45.036Z
Hash: 24fe32460222f3b3faf2
Time: 15376ms
chunk {inline} inline.bundle.js, inline.bundle.js.map (inline) 5.83 kB [entry] [rendered]
chunk {main} main.bundle.js, main.bundle.js.map (main) 8.88 kB {vendor} [initial] [rendered]
chunk {polyfills} polyfills.bundle.js, polyfills.bundle.js.map (polyfills) 209 kB {inline} [initial] [rendered]
chunk {styles} styles.bundle.js, styles.bundle.js.map (styles) 11.3 kB {inline} [initial] [rendered]
chunk {vendor} vendor.bundle.js, vendor.bundle.js.map (vendor) 2.29 MB [initial] [rendered]

webpack: Compiled successfully.

Step 1.4: Perform end-to-end Tests

Step 1.4.1: Use a Protractor Docker Image to perform the Tests

In the spirit of “test first” strategies of “behavior-driven development”, let us check the end-to-end tests that come with Angular CLI 1.4.3. We will see that they are broken and need to be adapted.

Like above, we will use a Docker container for the task. This time we will use the Docker image protractor-headless from webnicer. In a second terminal, we first define an alias, enter the project root folder and run protractor.

(dockerhost)$ alias protractor='docker run -it --privileged --rm --net=host -v /dev/shm:/dev/shm -v $(pwd):/protractor webnicer/protractor-headless $@'
(dockerhost)$ cd consuming-a-restful-web-service-with-angular
(dockerhost)$ protractor

[20:20:34] I/direct - Using ChromeDriver directly...
[20:20:34] I/launcher - Running 1 instances of WebDriver
Jasmine started

  consuming-a-restful-web-service-with-angular App
    ✗ should display welcome message
      - Failed: Error while waiting for Protractor to sync with the page: "Could not find testability for element."
          at /usr/local/lib/node_modules/protractor/built/browser.js:272:23
          at ManagedPromise.invokeCallback_ (/usr/local/lib/node_modules/protractor/node_modules/selenium-webdriver/lib/promise.js:1379:14)
          at TaskQueue.execute_ (/usr/local/lib/node_modules/protractor/node_modules/selenium-webdriver/lib/promise.js:2913:14)
          at TaskQueue.executeNext_ (/usr/local/lib/node_modules/protractor/node_modules/selenium-webdriver/lib/promise.js:2896:21)
          at asyncRun (/usr/local/lib/node_modules/protractor/node_modules/selenium-webdriver/lib/promise.js:2775:27)
          at /usr/local/lib/node_modules/protractor/node_modules/selenium-webdriver/lib/promise.js:639:7
          at process._tickCallback (internal/process/next_tick.js:103:7)Error
          at ElementArrayFinder.applyAction_ (/usr/local/lib/node_modules/protractor/built/element.js:461:27)
          at ElementArrayFinder._this.(anonymous function) [as getText] (/usr/local/lib/node_modules/protractor/built/element.js:103:30)
          at ElementFinder.(anonymous function) [as getText] (/usr/local/lib/node_modules/protractor/built/element.js:829:22)
          at AppPage.getParagraphText (/protractor/e2e/app.po.ts:9:43)
          at Object. (/protractor/e2e/app.e2e-spec.ts:12:17)
          at /usr/local/lib/node_modules/protractor/node_modules/jasminewd2/index.js:94:23
          at new ManagedPromise (/usr/local/lib/node_modules/protractor/node_modules/selenium-webdriver/lib/promise.js:1082:7)
          at controlFlowExecute (/usr/local/lib/node_modules/protractor/node_modules/jasminewd2/index.js:80:18)
          at TaskQueue.execute_ (/usr/local/lib/node_modules/protractor/node_modules/selenium-webdriver/lib/promise.js:2913:14)
          at TaskQueue.executeNext_ (/usr/local/lib/node_modules/protractor/node_modules/selenium-webdriver/lib/promise.js:2896:21)
      From: Task: Run it("should display welcome message") in control flow
          at Object. (/usr/local/lib/node_modules/protractor/node_modules/jasminewd2/index.js:79:14)
          at /usr/local/lib/node_modules/protractor/node_modules/jasminewd2/index.js:16:5
          at ManagedPromise.invokeCallback_ (/usr/local/lib/node_modules/protractor/node_modules/selenium-webdriver/lib/promise.js:1379:14)
          at TaskQueue.execute_ (/usr/local/lib/node_modules/protractor/node_modules/selenium-webdriver/lib/promise.js:2913:14)
          at TaskQueue.executeNext_ (/usr/local/lib/node_modules/protractor/node_modules/selenium-webdriver/lib/promise.js:2896:21)
          at asyncRun (/usr/local/lib/node_modules/protractor/node_modules/selenium-webdriver/lib/promise.js:2775:27)
      From asynchronous test:
      Error
          at Suite. (/protractor/e2e/app.e2e-spec.ts:10:3)
          at Object. (/protractor/e2e/app.e2e-spec.ts:3:1)
          at Module._compile (module.js:570:32)
          at Module.m._compile (/protractor/node_modules/ts-node/src/index.ts:392:23)
          at Module._extensions..js (module.js:579:10)
          at Object.require.extensions.(anonymous function) [as .ts] (/protractor/node_modules/ts-node/src/index.ts:395:12)

**************************************************
*                    Failures                    *
**************************************************

1) consuming-a-restful-web-service-with-angular App should display welcome message
  - Failed: Error while waiting for Protractor to sync with the page: "Could not find testability for element."

Executed 1 of 1 spec (1 FAILED) in 0.878 sec.
[20:20:41] I/launcher - 0 instance(s) of WebDriver still running
[20:20:41] I/launcher - chrome #01 failed 1 test(s)
[20:20:41] I/launcher - overall: 1 failed spec(s)
[20:20:41] E/launcher - Process exited with error code 1

Even though my application is listening on port 4200,  we can see that the e2e tests have a problem.

Step 1.4.2: Correct the Protractor sync Issue

As already pointed out in this blog post, we need to add the option

useAllAngular2AppRoots: true

to our protractor.conf.js file. At the end, the file has following content (correction in blue):

// protractor.conf.js
// Protractor configuration file, see link for more information
// https://github.com/angular/protractor/blob/master/lib/config.ts

const { SpecReporter } = require('jasmine-spec-reporter');

exports.config = {
  allScriptsTimeout: 11000,
  specs: [
    './e2e/**/*.e2e-spec.ts'
  ],
  capabilities: {
    'browserName': 'chrome'
  },
  directConnect: true,
  baseUrl: 'http://localhost:4200/',
  useAllAngular2AppRoots: true,
  framework: 'jasmine',
  jasmineNodeOpts: {
    showColors: true,
    defaultTimeoutInterval: 30000,
    print: function() {}
  },
  onPrepare() {
    require('ts-node').register({
      project: 'e2e/tsconfig.e2e.json'
    });
    jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
  }
};

After that, the e2e test is still not successful:

$ protractor
[20:30:32] I/direct - Using ChromeDriver directly...
[20:30:32] I/launcher - Running 1 instances of WebDriver
Jasmine started

  consuming-a-restful-web-service-with-angular App
    ✗ should display welcome message
      - Expected 'Welcome to !' to equal 'Welcome to app!'.
          at Object. (/protractor/e2e/app.e2e-spec.ts:12:37)
          at /usr/local/lib/node_modules/protractor/node_modules/jasminewd2/index.js:94:23
          at new ManagedPromise (/usr/local/lib/node_modules/protractor/node_modules/selenium-webdriver/lib/promise.js:1082:7)
          at controlFlowExecute (/usr/local/lib/node_modules/protractor/node_modules/jasminewd2/index.js:80:18)
          at TaskQueue.execute_ (/usr/local/lib/node_modules/protractor/node_modules/selenium-webdriver/lib/promise.js:2913:14)
          at TaskQueue.executeNext_ (/usr/local/lib/node_modules/protractor/node_modules/selenium-webdriver/lib/promise.js:2896:21)
          at asyncRun (/usr/local/lib/node_modules/protractor/node_modules/selenium-webdriver/lib/promise.js:2820:25)
          at /usr/local/lib/node_modules/protractor/node_modules/selenium-webdriver/lib/promise.js:639:7
          at process._tickCallback (internal/process/next_tick.js:103:7)

**************************************************
*                    Failures                    *
**************************************************

1) consuming-a-restful-web-service-with-angular App should display welcome message
  - Expected 'Welcome to !' to equal 'Welcome to app!'.

Executed 1 of 1 spec (1 FAILED) in 0.848 sec.
[20:30:40] I/launcher - 0 instance(s) of WebDriver still running
[20:30:40] I/launcher - chrome #01 failed 1 test(s)
[20:30:40] I/launcher - overall: 1 failed spec(s)
[20:30:40] E/launcher - Process exited with error code 1

Step 1.4.3: Correct the e2e Test Script

The reason is that the app.component.ts file is not correct. In the HTML template, we find a line

Welcome to {{title}}!

but in the component file, the title is missing:

import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {

  constructor() { }

  ngOnInit() {
  }

}

This is leading to following corrupt web page:

Let us correct this now (in blue):

import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {

  title : any = null

  constructor() { }

  ngOnInit() {
     this.title = "app";
  }

}

Now the Web page looks better:

Now the e2e tests are successful:

$ protractor
[20:53:42] I/direct - Using ChromeDriver directly...
[20:53:42] I/launcher - Running 1 instances of WebDriver
Jasmine started

  consuming-a-restful-web-service-with-angular App
    ✓ should display welcome message

Executed 1 of 1 spec SUCCESS in 0.956 sec.
[20:53:50] I/launcher - 0 instance(s) of WebDriver still running
[20:53:50] I/launcher - chrome #01 passed

The angular CLI installation works as expected now.

Excellent! Thump up!

Let us save the changes:

(dockerhost)$ git add protractor.conf.js
(dockerhost)$ git commit -m'protractor.conf.js: added useAllAngular2AppRoots: true for avoiding sync problems'
(dockerhost)$ git add src/app/app.component.ts
(dockerhost)$ git commit -m'app component: defined missing title'

Now is the time to sign up with Github and save the project. In my case, I have created following project: a project like follows:

https://github.com/oveits/consuming-a-restful-web-service-with-angular

Once this is done, we can upload the changes as follows:

(dockerhost)$ git remote add origin https://github.com/oveits/consuming-a-restful-web-service-with-angular.git
(dockerhost)$ git push -u origin master

Phase 2: Adapt the end-to-end Tests

In this phase, we will

  • based on the input from the WordPress API, we will plan, how the web page should look like from a customer’s point of view.
  • We will adapt the e2e tests, so they reflect the (assumed) customer’s expectations.
  • We will save the changed code to the remote GIT repository.

Step 2.1: Planning

In an attempt to follow a behavior driven development process, we will write/adapt the end to end tests first, before we perform the changes. For this, let us outline our plan:

  • We would like to create a Web page that displays the title and content of a WordPress Article
  • the WordPress article of our choice is the first angular blog post I have written: the Angular 4 Hello World Quickstart blog post
  • The article will be retrieved dynamically from the WordPress API, a REST API.

Step 2.2: Explore the WordPress REST API

Let us have a look at the WordPress API. The WordPress API can be explored via the WordPress.com REST API console. We can display a list of blog posts like so:

We can see that the blog post we would like to display has the ID 3078 and the title and content star like follows:

  • title: “Angular 4 Hello World Quickstart”
  • content: “<p>In this hello world style tutorial, we will follow a step by step guide to a working Angular 4 application. We will also …

The  single blog post can be retrieved with the URL

https://public-api.wordpress.com/rest/v1.1/sites/oliverveits.wordpress.com/posts/3078

We can verify this by copying the URL into a Browser:

Step 2.3: Adapt the end-to-end Tests

With the knowledge about the title and content of the blog post, we can re-write the end-to-end (e2e) test. The e2e test is found in the e2e folder:

ls e2e/
app.e2e-spec.ts app.po.ts tsconfig.e2e.json

$ cat e2e/app.e2e-spec.ts
import { AppPage } from './app.po';

describe('consuming-a-restful-web-service-with-angular App', () => {
  let page: AppPage;

  beforeEach(() => {
    page = new AppPage();
  });

  it('should display welcome message', () => {
    page.navigateTo();
    expect(page.getParagraphText()).toEqual('Welcome to app!');
  });
});

Instead of searching for the text ‘Welcome to app’, let us search for the title “Angular 4 Hello World Quickstart”:

$ cat e2e/app.e2e-spec.ts
import { AppPage } from './app.po';

describe('consuming-a-restful-web-service-with-angular App', () => {
  let page: AppPage;

  beforeEach(() => {
    page = new AppPage();
  });

  it('should display the title', () => {
    page.navigateTo();
    expect(page.getParagraphText()).toContain('Angular 4 Hello World Quickstart');
  });
});

The e2e test should fail now with the message Expected 'Welcome to app!' to contain 'Angular 4 Hello World Quickstart'

$ protractor
[20:46:02] I/direct - Using ChromeDriver directly...
[20:46:02] I/launcher - Running 1 instances of WebDriver
Jasmine started

  consuming-a-restful-web-service-with-angular App
    ✗ should display welcome message
      - Expected 'Welcome to app!' to contain 'Angular 4 Hello World Quickstart'.
          at Object. (/protractor/e2e/app.e2e-spec.ts:12:37)
          at /usr/local/lib/node_modules/protractor/node_modules/jasminewd2/index.js:94:23
          at new ManagedPromise (/usr/local/lib/node_modules/protractor/node_modules/selenium-webdriver/lib/promise.js:1082:7)
          at controlFlowExecute (/usr/local/lib/node_modules/protractor/node_modules/jasminewd2/index.js:80:18)
          at TaskQueue.execute_ (/usr/local/lib/node_modules/protractor/node_modules/selenium-webdriver/lib/promise.js:2913:14)
          at TaskQueue.executeNext_ (/usr/local/lib/node_modules/protractor/node_modules/selenium-webdriver/lib/promise.js:2896:21)
          at asyncRun (/usr/local/lib/node_modules/protractor/node_modules/selenium-webdriver/lib/promise.js:2820:25)
          at /usr/local/lib/node_modules/protractor/node_modules/selenium-webdriver/lib/promise.js:639:7
          at process._tickCallback (internal/process/next_tick.js:103:7)

**************************************************
*                    Failures                    *
**************************************************

1) consuming-a-restful-web-service-with-angular App should display welcome message
  - Expected 'Welcome to app!' to contain 'Angular 4 Hello World Quickstart'.

Executed 1 of 1 spec (1 FAILED) in 0.907 sec.
[20:46:21] I/launcher - 0 instance(s) of WebDriver still running
[20:46:21] I/launcher - chrome #01 failed 1 test(s)
[20:46:21] I/launcher - overall: 1 failed spec(s)
[20:46:21] E/launcher - Process exited with error code 1

Step 2.4: Save the Changes on a separate GIT Branch

We believe that the e2e tests are correct now, so it is a good time to create a new git feature branch and commit the code:

git checkout -b feature/0001-retrieve-and-display-WordPress-title-from-API
git add .
git commit -m'adapted e2e tests to display WordPress blog title'
git push

Phase 3: Adapt the Code

Now, after having written the e2e tests, let us change the code, so our app fulfills the expectations.

Step 3.1: Define the HTML View

In the spirit of a behavior driven approach, let us define the view first. For that we replace the content of the app’s template file:

$ cat src/app/app.component.html
<h1>{{title}}</h1>

The output of the application now is:

This is because, in the Hello World app, we have set the title to the static value ‘app’. The e2e tests are not successful and the error ‘Expected ‘app’ to contain ‘Angular 4 Hello World Quickstart’.’ is thrown when we run protractor.

Step 3.2: Subscribe an Observable

As can be seen in many tutorials, we now subscribe to an observable like follows:

$ cat src/app/app.component.ts
import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {

  title : any = null

  constructor() { }

  ngOnInit() {
     //this.title = "app";
     this._http.get('https://public-api.wordpress.com/rest/v1.1/sites/oliverveits.wordpress.com/posts/3078')
                .map((res: Response) => res.json())
                 .subscribe(data => {
                        this.title = data.title;
                        console.log(data);
                });
  }

}

We perform an HTTP GET on the WordPress API’s URL, map the response to a JSON object and subscribe the retrieved data. The data should contain a title, which we assign to the local title variable.

However, we will see in the log:

ERROR in /app/src/app/app.component.ts (16,11): Property '_http' does not exist on type 'AppComponent'.

And in the browser, we see:

Let us fix that now.

Step 3.3: Define private local _http Variable

In angular, we can define the private local _http variable in the constructor:

constructor(private _http: Http) {}

Once, this is done, the error message is changed to:

ERROR in /app/src/app/app.component.ts (12,30): Cannot find name 'Http'.

Step 3.4: Import Http Components

The used Http module is not known to our app component. Let us change this now. We add the following line

import { Http } from '@angular/http';

to the file src/app/app.component.ts. The error message changes to:

ERROR in /app/src/app/app.component.ts (18,18): Property 'map' does not exist on type 'Observable<Response>'.

Step 3.5: Import map

The map function needs to be imported as well:

import 'rxjs/add/operator/map'

Now we get an illegible error like follows:

ERROR in /app/src/app/app.component.ts (18,6): The type argument for type parameter 'T' cannot be inferred from the usage. Consider specifying the type arguments explicitly.
  Type argument candidate 'Response' is not a valid type argument because it is not a supertype of candidate 'Response'.
    Types of property 'type' are incompatible.
      Type 'ResponseType' is not assignable to type 'ResponseType'. Two different types with this name exist, but they are unrelated.
        Type '"basic"' is not assignable to type 'ResponseType'.

Step 3.6: Import Response Type

We finally can get rid of the quite illegible error message by adding another import:

import { Response } from '@angular/http';

However, this still does not lead to the desired result. In the browser we see an empty page:

and the e2e tests fail with the following message:

$ protractor
...
Failed: Angular could not be found on the page http://localhost:4200/. If this is not an Angular application, you may need to turn off waiting for Angular. Please see https://github.com/angular/protractor/blob/master/docs/timeouts.md#waiting-for-angular-on-page-load

Step 3.7: Add HttpModule in the app Module

The solution of the above error lies in the src/app/app.module.ts (added parts in blue). We first need to add the HttpModule to the imports, which alters the error message to

$ cat src/app/app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { HttpModule }    from '@angular/http';

import { AppComponent } from './app.component';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    HttpModule,
    BrowserModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

This seems to have been the last stepping stone towards success:

Phase 4: Verify the successful e2e Tests

Now the e2e tests are successful:

$ protractor
[22:16:06] I/direct - Using ChromeDriver directly...
[22:16:06] I/launcher - Running 1 instances of WebDriver
Jasmine started

  consuming-a-restful-web-service-with-angular App
    ✓ should display welcome message

Executed 1 of 1 spec SUCCESS in 1 sec.
[22:16:14] I/launcher - 0 instance(s) of WebDriver still running
[22:16:14] I/launcher - chrome #01 passed

That is, how the e2e Tests should look like. Success!

Excellent! Thump up!

Step 4.2: Save the changes to the develop branch on GIT

Since our new feature “retrieve and display a blog title from WordPress API” has been verified to work fine, it is time to commit the change and push it to the remote repository:

git add .
git commit -m'added all code needed for successful e2e tests'
git push
git checkout -b develop
git push

In addition to that, we can create a new “develop” branch, if it does not exist yet:

git checkout -b develop
git push

In case the develop branch exist already, you need to merge the code to the instead of creating the develop branch:

git checkout develop
git merge feature/0001-retrieve-and-display-WordPress-title-from-API
git push

It makes sense to allow a merge to the develop branch only if the code is fully tested. This way, we will never break the code in the develop branch.

For large teams, several measures can be taken to make sure that only high quality code enters the develop branch: e.g. on BitBucket GIT, you can allow a merge only, if code has been reviewed and acknowledged by a certain number of team members. Moreover, you can integrate the repository with a Jenkins system: with the correct plugins, you can make sure that a merge is allowed only in case all quality gates (e2e test, unit tests, style, performance, …) in the Jenkins pipeline are met.

However, if you are a hobby developer working on a , it is probably sufficient if you run the tests manually before you merge the changed code into the develop or master branch.

Summary

In this hello world style step-by-step guide, we have learned

  • How to create a new Hello World project using Angular CLI, repair the e2e tests and save the changes on GIT.
  • How to create/adapt the e2e tests in advance a “test first” manner.
  • How to consume a REST service using Angular 4 and verify the result using the e2e test scripts we have created before.

Next Steps

In part 2 of this series, we will learn how to add and display HTML content to the body of our application. We will see that we cannot just use the method we have used for the title. If we do so, we will see escaped HTML code like follows:

<p>In this hello world style tutorial,…

We will show how to make Angular accept the HTML code and display it correctly.

References:

Appendix A: Adding Docker Support

This is, how I have added Docker support for the application, following my tl:dr of the blog post Angular 4 Docker Example.

A.1 Add Dockerfile and NginX config

git clone https://github.com/oveits/consuming-a-restful-web-service-with-angular
git checkout -b feature/0002-docker-support
curl -O https://raw.githubusercontent.com/avatsaev/angular4-docker-example/master/Dockerfile
curl -O https://raw.githubusercontent.com/avatsaev/angular4-docker-example/master/nginx/default.conf
mkdir nginx
mv default.conf nginx/

remove ‘package-log.json’ from Dockerfile

git add .
git commit -m 'added Dockerfile and nginx config file'
git push

A.2 Build the Docker  Image

On a docker host, I have issued following commands:

docker build . --tag oveits/consuming-a-restful-web-service-with-angular:v0.2
docker push oveits/consuming-a-restful-web-service-with-angular:v0.2
docker tag oveits/consuming-a-restful-web-service-with-angular:v0.2 oveits/consuming-a-restful-web-service-with-angular:latest
docker push oveits/consuming-a-restful-web-service-with-angular:v0.2
docker push oveits/consuming-a-restful-web-service-with-angular:latest

A.3 Run the Service

$ alias consuming='docker run --rm --name consuming-a-restful-web-service-with-angular -d -p 80:80 oveits/consuming-a-restful-web-service-with-angular $@'
$ consuming

A.4 Access the Service

In a browser, head to the public DNS of the image:

Works!

Excellent! Thump up!

 

 

0

Angular end to end Testing


– Angular e2e Protractor Tests on Systems without GUI applied to static and dynamic Web Pages –

In this blog post, we will show how to perform end-to-end (e2e) tests with Angular 4:

  • first, we will apply Protractor end to end tests on a little Angular CLI hello world application with static HTML content
  • second, we will perform end to end tests for a dynamic web application, a REST client application that retrieves and displays content received via the WordPress API.

For that, we will use a Docker Protractor Image that can be used on systems with or without graphical user interface (e.g. a Jenkins CI system).

 

Protractor + Docker

In a classical installation situation, you often use a machine with a graphical window system (e.g. X11 for Linux) so you can run a real Chrome or Firefox Browser on the system. In such a situation, you often install Jasmine, Protractor, Selenium and a Chrome or Firefox browser on the test machine. However, in this blog post, we prefer to use a pre-installed Docker image provided by webnicer instead, which allows us to run the e2e tests on Docker hosts without a graphical system. The missing need for a graphical interface makes this an ideal deployment option for continuous integration purposes, e.g. for Jenkins systems.

Why End to End Tests?

My motivation to write a blog about end-to-end tests is, that I have made a very good experience with “tests first” and more specifically “behavior driven development” (BDD) principles with previous projects. And end-to-end tests are the main ingredient needed for BDD. In my experience, projects that follow “test first” or BDD principles benefit from a better customer view and higher quality, together with higher motivated developers, who start their work with a set of failed (red) tests and are rewarded for their work with successful (green) tests.

Even if we are far from following behavior driven principles yet, let us perform our first tiny steps towards this principle by looking more closely at end-to-end testing now:

Step 1: Install and start an Angular CLI Hello World Application

We are closely following the phase 1 instructions on a previous popular blog post of mine Consuming a RESTful Web Service with Angular 4. Since I do not want to copy and paste that part of the other blog post, let me just summarize the commands after you have got access to a Docker host (e.g. by following the instructions found here; search for the term “Install a Docker Host”):

  • start Docker container
docker run -it -p 4200:4200 -v $(pwd):/localdir centos bash
  • install Angular CLI
(container)# yum install -y epel-release
(container)# yum install -y https://kojipkgs.fedoraproject.org//packages/http-parser/2.7.1/3.el7/x86_64/http-parser-2.7.1-3.el7.x86_64.rpm
(container)# yum install -y nodejs 
(container)# npm install -g @angular/cli
  • create a project
(container)# cd /localdir
(container)# ng new my-project-name
(container)# cd my-project-name
  • we do not yet start the service in order to see a certain error message, but in step 3, we will start the service with this command:
(container)# ng serve --host 0.0.0.0

For more detailed information about Step 1, see phase 1 of this blog post.

Step 2: Prepare Docker Host for Protractor Usage

For a convenient handling of the protractor test, let us open a new terminal and create a shell script on the Docker host like follows (see the readme of the Docker image page webnicer/protractor-headless):

If your Docker host does not allow the usage of sudo, then try the same commands without sudo.

(dockerhost)$ sudo cat - << END | sudo tee /usr/local/bin/protractor-headless
#!/bin/bash
docker run -it --privileged --rm --net=host -v /dev/shm:/dev/shm -v \$(pwd):/protractor webnicer/protractor-headless \$@
END

Then we make sure the file is executable:

(dockerhost)$ sudo chmod +x /usr/local/bin/protractor-headless
(dockerhost)$ which /usr/local/bin/protractor-headless
/usr/local/bin/protractor-headless

Step 3: Download and run Protractor in a Docker Container

Let us download the protractor Docker image webnicer/protractor-headless so the container will be started immediately from the image later:

(dockerhost)$ docker pull webnicer/protractor-headless 

Then we enter the project root folder we have created in step 1:

(dockerhost)$ cd my-project-name

and run protractor via:

(dockerhost)$ protractor-headless
[23:43:08] I/direct - Using ChromeDriver directly...
[23:43:08] I/launcher - Running 1 instances of WebDriver
Spec started
[23:43:24] E/protractor - Could not find Angular on page http://localhost:4200/ : retries looking for angular exceeded

  my-project-name App
    ✗ should display welcome message
      - Failed: Angular could not be found on the page http://localhost:4200/. If this is not an Angular application, you may need to turn off waiting for Angular. Please see https://github.com/angular/protractor/blob/master/docs/timeouts.md#waiting-for-angular-on-page-load
          at /usr/local/lib/node_modules/protractor/built/browser.js:506:23
          at ManagedPromise.invokeCallback_ (/usr/local/lib/node_modules/protractor/node_modules/selenium-webdriver/lib/promise.js:1379:14)
          at TaskQueue.execute_ (/usr/local/lib/node_modules/protractor/node_modules/selenium-webdriver/lib/promise.js:2913:14)
          at TaskQueue.executeNext_ (/usr/local/lib/node_modules/protractor/node_modules/selenium-webdriver/lib/promise.js:2896:21)
          at asyncRun (/usr/local/lib/node_modules/protractor/node_modules/selenium-webdriver/lib/promise.js:2775:27)
          at /usr/local/lib/node_modules/protractor/node_modules/selenium-webdriver/lib/promise.js:639:7
          at process._tickCallback (internal/process/next_tick.js:103:7)
      From: Task: Run it("should display welcome message") in control flow
          at Object. (/usr/local/lib/node_modules/protractor/node_modules/jasminewd2/index.js:79:14)
          at /usr/local/lib/node_modules/protractor/node_modules/jasminewd2/index.js:16:5
          at ManagedPromise.invokeCallback_ (/usr/local/lib/node_modules/protractor/node_modules/selenium-webdriver/lib/promise.js:1379:14)
          at TaskQueue.execute_ (/usr/local/lib/node_modules/protractor/node_modules/selenium-webdriver/lib/promise.js:2913:14)
          at TaskQueue.executeNext_ (/usr/local/lib/node_modules/protractor/node_modules/selenium-webdriver/lib/promise.js:2896:21)
          at asyncRun (/usr/local/lib/node_modules/protractor/node_modules/selenium-webdriver/lib/promise.js:2775:27)
      From asynchronous test:
      Error
          at Suite. (/protractor/e2e/app.e2e-spec.ts:10:3)
          at Object. (/protractor/e2e/app.e2e-spec.ts:3:1)
          at Module._compile (module.js:570:32)
          at Module.m._compile (/protractor/node_modules/ts-node/src/index.ts:382:23)
          at Module._extensions..js (module.js:579:10)
          at Object.require.extensions.(anonymous function) [as .ts] (/protractor/node_modules/ts-node/src/index.ts:385:12)

**************************************************
*                    Failures                    *
**************************************************

1) my-project-name App should display welcome message
  - Failed: Angular could not be found on the page http://localhost:4200/. If this is not an Angular application, you may need to turn off waiting for Angular. Please see https://github.com/angular/protractor/blob/master/docs/timeouts.md#waiting-for-angular-on-page-load

Executed 1 of 1 spec (1 FAILED) in 10 secs.
[23:43:24] I/launcher - 0 instance(s) of WebDriver still running
[23:43:24] I/launcher - chrome #01 failed 1 test(s)
[23:43:24] I/launcher - overall: 1 failed spec(s)
[23:43:24] E/launcher - Process exited with error code 1

Okay, this is expected, because no angular is running on port 4200 yet.

Step 4: Start Angular CLI Application and run Protractor again

As anticipated, let us start the Angular application as describes in step 1:

(container)# ng serve --host 0.0.0.0

Then we get following output, if we try to run the e2e tests:

(dockerhost)$ protractor-headless
[00:19:21] I/direct - Using ChromeDriver directly...
[00:19:21] I/launcher - Running 1 instances of WebDriver
Spec started

  my-project-name App
    ✗ should display welcome message
      - Failed: Error: Error while waiting for Protractor to sync with the page: "Could not find testability for element."
          at proxyDone.fail (/usr/local/lib/node_modules/protractor/node_modules/jasminewd2/index.js:87:34)
          at ManagedPromise.invokeCallback_ (/usr/local/lib/node_modules/protractor/node_modules/selenium-webdriver/lib/promise.js:1379:14)
          at TaskQueue.execute_ (/usr/local/lib/node_modules/protractor/node_modules/selenium-webdriver/lib/promise.js:2913:14)
          at TaskQueue.executeNext_ (/usr/local/lib/node_modules/protractor/node_modules/selenium-webdriver/lib/promise.js:2896:21)
          at asyncRun (/usr/local/lib/node_modules/protractor/node_modules/selenium-webdriver/lib/promise.js:2775:27)
          at /usr/local/lib/node_modules/protractor/node_modules/selenium-webdriver/lib/promise.js:639:7
          at process._tickCallback (internal/process/next_tick.js:103:7)
      From: Task: Run it("should display welcome message") in control flow
          at Object. (/usr/local/lib/node_modules/protractor/node_modules/jasminewd2/index.js:79:14)
          at /usr/local/lib/node_modules/protractor/node_modules/jasminewd2/index.js:16:5
          at ManagedPromise.invokeCallback_ (/usr/local/lib/node_modules/protractor/node_modules/selenium-webdriver/lib/promise.js:1379:14)
          at TaskQueue.execute_ (/usr/local/lib/node_modules/protractor/node_modules/selenium-webdriver/lib/promise.js:2913:14)
          at TaskQueue.executeNext_ (/usr/local/lib/node_modules/protractor/node_modules/selenium-webdriver/lib/promise.js:2896:21)
          at asyncRun (/usr/local/lib/node_modules/protractor/node_modules/selenium-webdriver/lib/promise.js:2775:27)
      From asynchronous test:
      Error
          at Suite. (/protractor/e2e/app.e2e-spec.ts:10:3)
          at Object. (/protractor/e2e/app.e2e-spec.ts:3:1)
          at Module._compile (module.js:570:32)
          at Module.m._compile (/protractor/node_modules/ts-node/src/index.ts:382:23)
          at Module._extensions..js (module.js:579:10)
          at Object.require.extensions.(anonymous function) [as .ts] (/protractor/node_modules/ts-node/src/index.ts:385:12)

**************************************************
*                    Failures                    *
**************************************************

1) my-project-name App should display welcome message
  - Failed: Error: Error while waiting for Protractor to sync with the page: "Could not find testability for element."

Executed 1 of 1 spec (1 FAILED) in 0.565 sec.
[00:19:26] I/launcher - 0 instance(s) of WebDriver still running
[00:19:26] I/launcher - chrome #01 failed 1 test(s)
[00:19:26] I/launcher - overall: 1 failed spec(s)
[00:19:26] E/launcher - Process exited with error code 1

Step 5: Fix the “waiting for Protractor to sync” Problem and run Protractor again

I have found a solution on this StackOverflow Q&A: we need to add the additional configuration line into protractor.conf.js:

useAllAngular2AppRoots: true

In my case, the protractor configuration file looks like follows:

// Protractor configuration file, see link for more information
// https://github.com/angular/protractor/blob/master/lib/config.ts

const { SpecReporter } = require('jasmine-spec-reporter');

exports.config = {
  allScriptsTimeout: 11000,
  specs: [
    './e2e/**/*.e2e-spec.ts'
  ],
  capabilities: {
    'browserName': 'chrome'
  },
  directConnect: true,
  baseUrl: 'http://localhost:4200/',
  useAllAngular2AppRoots: true,
  framework: 'jasmine',
  jasmineNodeOpts: {
    showColors: true,
    defaultTimeoutInterval: 30000,
    print: function() {}
  },
  onPrepare() {
    require('ts-node').register({
      project: 'e2e/tsconfig.e2e.json'
    });
    jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
  }
};

After that, the e2e test via Protractor Docker Container is successful:

protractor-headless
[00:46:32] I/direct - Using ChromeDriver directly...
[00:46:32] I/launcher - Running 1 instances of WebDriver
Spec started
[00:46:38] W/element - more than one element found for locator By(css selector, app-root h1) - the first result will be used

  my-project-name App
    ✓ should display welcme message

Executed 1 of 1 spec SUCCESS in 1 sec.
[00:46:38] I/launcher - 0 instance(s) of WebDriver still running
[00:46:38] I/launcher - chrome #01 passed

Excellent! Thump up!

Step 6: Review the Protractor Spec File

Now, we want to understand in more detail, what happened. For that, let us analyze the e2e specification file (e2e/app.e2e-spec.ts):

import { MyProjectNamePage } from './app.po';

describe('my-project-name App', () => {
  let page: MyProjectNamePage;

  beforeEach(() => {
    page = new MyProjectNamePage();
  });

  it('should display welcome message', done => {
    page.navigateTo();
    page.getParagraphText()
      .then(msg => expect(msg).toEqual('Welcome to app!!'))
      .then(done, done.fail);
  });
});

Now let us compare that with the browser content on http://localhost:4200:

There, it is: the e2e test is loading the page, retrieving the first paragraph and will compare it with the text “Welcome to app!!”. Since the text of the first paragraph matches this text, the test is successful.

Step 7 (optional): Review the Error Message of a failed Test

Now let us see, what happens, if we change the expected text in e2e/app.e2e-spec.ts to “Welcome to ppa!!” instead:

$ protractor-headless
[00:32:30] I/direct - Using ChromeDriver directly...
[00:32:30] I/launcher - Running 1 instances of WebDriver
Spec started
[00:32:36] W/element - more than one element found for locator By(css selector, app-root h1) - the first result will be used

  my-project-name App
    ✗ should display welcome message
      - Expected 'Angular 4 Hello World Quickstart' to equal 'Welcome to ppa!!'.
          at /protractor/e2e/app.e2e-spec.ts:13:32
          at /usr/local/lib/node_modules/protractor/built/element.js:798:32
          at ManagedPromise.invokeCallback_ (/usr/local/lib/node_modules/protractor/node_modules/selenium-webdriver/lib/promise.js:1379:14)
          at TaskQueue.execute_ (/usr/local/lib/node_modules/protractor/node_modules/selenium-webdriver/lib/promise.js:2913:14)
          at TaskQueue.executeNext_ (/usr/local/lib/node_modules/protractor/node_modules/selenium-webdriver/lib/promise.js:2896:21)
          at asyncRun (/usr/local/lib/node_modules/protractor/node_modules/selenium-webdriver/lib/promise.js:2775:27)
          at /usr/local/lib/node_modules/protractor/node_modules/selenium-webdriver/lib/promise.js:639:7
          at process._tickCallback (internal/process/next_tick.js:103:7)

**************************************************
*                    Failures                    *
**************************************************

1) my-project-name App should display welcome message
  - Expected 'Angular 4 Hello World Quickstart' to equal 'Welcome to ppa!!'.

Executed 1 of 1 spec (1 FAILED) in 1 sec.
[00:32:36] I/launcher - 0 instance(s) of WebDriver still running
[00:32:36] I/launcher - chrome #01 failed 1 test(s)
[00:32:36] I/launcher - overall: 1 failed spec(s)
[00:32:36] E/launcher - Process exited with error code 1

So, this is, how a failed test looks like.

Step 8 (advanced): Apply the e2e Test to a dynamic Web Page

Up to now, we have seen a simple test, which is comparing a static pattern with a static web page. We now want to apply the principle to a dynamic page as we have created in my blog post Consuming a RESTful Web Service with Angular 4. For that, we reverse the change in Step 7, so we get a successful e2e test again. Then we need to follow the steps in the corresponding blog post and run the resulting service on localhost port 4200. After that, the e2e test will fail:

(dockerhost)$ protractor-headless
[00:32:30] I/direct - Using ChromeDriver directly...
[00:32:30] I/launcher - Running 1 instances of WebDriver
Spec started
[00:32:36] W/element - more than one element found for locator By(css selector, app-root h1) - the first result will be used

  my-project-name App
    ✗ should display welcome message
      - Expected 'Angular 4 Hello World Quickstart' to equal 'Welcome to app!!'.
          at /protractor/e2e/app.e2e-spec.ts:13:32
          at /usr/local/lib/node_modules/protractor/built/element.js:798:32
          at ManagedPromise.invokeCallback_ (/usr/local/lib/node_modules/protractor/node_modules/selenium-webdriver/lib/promise.js:1379:14)
          at TaskQueue.execute_ (/usr/local/lib/node_modules/protractor/node_modules/selenium-webdriver/lib/promise.js:2913:14)
          at TaskQueue.executeNext_ (/usr/local/lib/node_modules/protractor/node_modules/selenium-webdriver/lib/promise.js:2896:21)
          at asyncRun (/usr/local/lib/node_modules/protractor/node_modules/selenium-webdriver/lib/promise.js:2775:27)
          at /usr/local/lib/node_modules/protractor/node_modules/selenium-webdriver/lib/promise.js:639:7
          at process._tickCallback (internal/process/next_tick.js:103:7)

**************************************************
*                    Failures                    *
**************************************************

1) my-project-name App should display welcome message
  - Expected 'Angular 4 Hello World Quickstart' to equal 'Welcome to app!!'.

Executed 1 of 1 spec (1 FAILED) in 1 sec.
[00:32:36] I/launcher - 0 instance(s) of WebDriver still running
[00:32:36] I/launcher - chrome #01 failed 1 test(s)
[00:32:36] I/launcher - overall: 1 failed spec(s)
[00:32:36] E/launcher - Process exited with error code 1

But what is the expected result we want to see? Let us head to http://localhost:4200 again, and we will see:

The blue part is static content again. However, the Title “Angular 4 Hello World Quickstart” is dynamic content retrieved from the WordPress API. We can easily see that this pattern is missing in the source HTML code:

Nevertheless, we want to use our Protractor Docker Container to test, whether the dynamic content is visible in a browser. For that, let us adapt the e2e test file on (e2e/app.e2e-spec.ts) to the new situation:

import { MyProjectNamePage } from './app.po';

describe('my-project-name App', () => {
  let page: MyProjectNamePage;

  beforeEach(() => {
    page = new MyProjectNamePage();
  });

  it('should display title', done => {
    page.navigateTo();
    page.getParagraphText()
      .then(msg => expect(msg).toContain('Angular 4 Hello World Quickstart'))
      .then(done, done.fail);
  });
});

the e2e test is successful:

(dockerhost)$ protractor-headless
[00:46:32] I/direct - Using ChromeDriver directly...
[00:46:32] I/launcher - Running 1 instances of WebDriver
Spec started
[00:46:38] W/element - more than one element found for locator By(css selector, app-root h1) - the first result will be used

  my-project-name App
    ✓ should display title

Executed 1 of 1 spec SUCCESS in 1 sec.
[00:46:38] I/launcher - 0 instance(s) of WebDriver still running
[00:46:38] I/launcher - chrome #01 passed

Excellent! Thump up!

We have achieved our goal: we had intended to perform a successful end to end test for a dynamic web page that retrieves its content from an external resource like the WordPress API.

Summary

In this blog post, we

  • have applied a Protractor Docker Container on an existing Angular CLI Hello World.
    For this to work, we had to adapt the Protractor configuration file to circumvent an “Error while waiting for Protractor to sync with the page”
  • have applied a Protractor Docker Container with the patched Protractor configuration on an app that is dynamically downloading and displaying content retrieved from the WordPress API. This has worked as expected although the content we have looked for was not visible in the HTML source code.

With the latter, we have successfully verified that the Protractor Docker container can handle dynamic content retrieved and altered via javascript.

Next Steps

  • Write more complete test cases for the dynamic web page
  • Refactor the dynamic web page application by dividing the HTTP GET into a separate service. The Protractor tests will help me to verify that the refactored application will keep its previously achieved features.

References:

2

Angular 4 Universal: Boosting Performance through Server Side Rendering


This time we will show, how to use server side rendering with Angular 4 (or Angular 2). Like in my previous blog post, we will consume a RESTful Web Service with Angular 4. However, the web page will be displayed almost immediately because of server side rendering, as opposed to the client side rendered situation described in my previous blog post about Angular consuming a REST API. There, we had observed load times of several seconds in situations with low bandwidth between Angular server and REST service.

Why Server Side Rendering?

In our last blog post, Consuming a RESTful Web Service with Angular 4,  we have created an Angular simple single page application that has displayed data from an external resource, the WordPress API. In case of limited bandwidth between client and WordPress API, the latency for some responses were several seconds (!). Because of this fact, the perceived performance of the application was quite poor.

Angular Universal offers a possibility to mitigate the problem: it offers a combination of server side rendering and client side rendering. When the client contacts its server, the complete HTML page is downloaded to the client and the client can display the content immediately. Then the client will perform the REST call and will replace the server-side rendered page by a client-side rendered page. In case of a low performance link to the RESTful interface, this can be perceived by a flickering of the page. This is not perfect, but it is much better than waiting for the page display for seconds.

Now let us begin with our step by step guide.

Phase 1: Run a Server Side Rendered Hello World Page

Step 1.0: Get a Docker Host

This time again, our application will need more than 750 MB RAM. Therefore, we cannot use my beloved Katacoda as our Docker playground, which is limited to this amount of DRAM. Instead, you need to get access to a Docker host with, >~ 2 GB RAM. A nice way of installing an Ubuntu Docker host via Vagrant is described here (search for the term “Install a Docker Host”).

Step 1.1 Run my CentOS Angular Image

On the Docker host, let us start my angular image like follows (the -v $(pwd):/localdir option is only needed, if you want to keep the project folder for later use)

(dockerhost)$ docker run -it -p 8000:8000 -v $(pwd):/localdir oveits/angular_hello_world:centos bash

Then, on the container, we perform following commands:

(container)# git clone https://github.com/FrozenPandaz/ng-universal-demo
(container)# cd ng-universal-demo
(container)# npm i
(container)# npm run watch &
[1] 37

> ng-universal-demo@1.0.0 watch /ng-universal-demo
> webpack --watch


Webpack is watching the files…

Hash: 068db451109474765aa6ca44939d0968e157c122
Version: webpack 2.6.1
Child
    Hash: 068db451109474765aa6
    Time: 15786ms
              Asset       Size  Chunks                    Chunk Names
        0.client.js    2.13 kB       0  [emitted]
          client.js     2.5 MB       1  [emitted]  [big]  main
    0.client.js.map    1.04 kB       0  [emitted]
      client.js.map    3.03 MB       1  [emitted]         main
         index.html  222 bytes          [emitted]
       [0] ./~/rxjs/Observable.js 11.4 kB {1} [built]
       [3] ./~/rxjs/util/root.js 885 bytes {1} [built]
       [7] ./~/@angular/platform-browser/@angular/platform-browser.es5.js 141 kB {1} [built]
       [9] (webpack)/buildin/global.js 509 bytes {1} [built]
      [19] ./~/rxjs/add/operator/map.js 187 bytes {1} [built]
      [39] ./~/@angular/platform-browser-dynamic/@angular/platform-browser-dynamic.es5.js 5.88 kB {1} [built]
      [40] ./src/app/browser-app.module.ts 1.4 kB {1} [built]
      [41] ./~/reflect-metadata/Reflect.js 48 kB {1} [built]
      [42] ./~/zone.js/dist/zone.js 96 kB {1} [built]
      [44] ./~/@angular/compiler/@angular/compiler.es5.js 1.02 MB {1} [built]
      [45] ./src/app/app.module.ts 1.61 kB {1} [built]
      [47] ./src/main.browser.ts 350 bytes {1} [built]
      [49] ./src/modules/transfer-state/browser-transfer-state.module.ts 1.35 kB {1} [built]
      [50] ./~/process/browser.js 5.42 kB {1} [built]
      [78] ./~/rxjs/util/toSubscriber.js 760 bytes {1} [built]
        + 66 hidden modules

    ERROR in /ng-universal-demo/node_modules/rxjs/Subject.d.ts (16,22): Class 'Subject' incorrectly extends base class 'Observable'.
      Types of property 'lift' are incompatible.
        Type '(operator: Operator<T, R>) => Observable' is not assignable to type '(operator: Operator<T, R>) => Observable'.
          Type 'Observable' is not assignable to type 'Observable'.
            Type 'T' is not assignable to type 'R'.
    Child html-webpack-plugin for "index.html":
           [0] ./~/html-webpack-plugin/lib/loader.js!./src/index.html 193 bytes {0} [built]
Child
    Hash: ca44939d0968e157c122
    Time: 21298ms
              Asset     Size  Chunks                    Chunk Names
        0.server.js  2.14 kB       0  [emitted]
          server.js  4.23 MB       1  [emitted]  [big]  main
    0.server.js.map  1.04 kB       0  [emitted]
      server.js.map  5.18 MB       1  [emitted]         main
       [4] ./~/@angular/core/@angular/core.es5.js 489 kB {1} [built]
     [145] ./src/api/app.ts 222 bytes {1} [built]
     [146] ./src/app/server-app.module.ts 2.2 kB {1} [built]
     [147] ./src/routes.ts 80 bytes {1} [built]
     [148] ./~/@nguniversal/express-engine/index.js 196 bytes {1} [built]
     [149] ./~/express/index.js 224 bytes {1} [built]
     [150] ./~/reflect-metadata/Reflect.js 48 kB {1} [built]
     [151] ./~/rxjs/Rx.js 9.65 kB {1} [built]
     [152] ./~/zone.js/dist/zone-node.js 71.1 kB {1} [built]
     [158] ./src/main.server.ts 1.22 kB {1} [built]
     [245] ./~/rxjs/add/operator/bufferCount.js 235 bytes {1} [built]
     [336] ./~/rxjs/add/operator/windowTime.js 229 bytes {1} [built]
     [337] ./~/rxjs/add/operator/windowToggle.js 241 bytes {1} [built]
     [338] ./~/rxjs/add/operator/windowWhen.js 229 bytes {1} [built]
     [339] ./~/rxjs/add/operator/withLatestFrom.js 253 bytes {1} [built]
        + 475 hidden modules

    WARNING in ./~/express/lib/view.js
    80:29-41 Critical dependency: the request of a dependency is an expression

    ERROR in /ng-universal-demo/node_modules/rxjs/Subject.d.ts (16,22): Class 'Subject' incorrectly extends base class 'Observable'.
      Types of property 'lift' are incompatible.
        Type '(operator: Operator<T, R>) => Observable' is not assignable to type '(operator: Operator<T, R>) => Observable'.
          Type 'Observable' is not assignable to type 'Observable'.
            Type 'T' is not assignable to type 'R'.

    ERROR in /ng-universal-demo/node_modules/rxjs/observable/dom/WebSocketSubject.d.ts (24,22): Class 'WebSocketSubject' incorrectly extends base class 'AnonymousSubject'.
      Types of property 'lift' are incompatible.
        Type '(operator: Operator<T, R>) => WebSocketSubject' is not assignable to type '(operator: Operator<T, R>) => Observable'.
          Type 'WebSocketSubject' is not assignable to type 'Observable'.
            Types of property 'operator' are incompatible.
              Type 'Operator<any, R>' is not assignable to type 'Operator<any, T>'.
                Type 'R' is not assignable to type 'T'.

Let us ignore the error in red for now.

(container)# npm run server
[2] 60

> ng-universal-demo@1.0.0 server /ng-universal-demo
> nodemon dist/server.js

[nodemon] 1.11.0
[nodemon] to restart at any time, enter `rs`
[nodemon] watching: dist/*server.js src/index.html
[nodemon] starting `node dist/server.js`
Listening at http://localhost:8000
GET: /: 187.390ms
GET: /data: 3.213ms

or if you want to avoid the npm run watch error described as issue on git:angular/angular, you either can fix the typescript version in package.js: "typescript": "2.3.4". Alternatively you can replace the previous two commands by npm run start (the npm run server command, which needs to be restarted often below, is quicker though):

(container)# npm run start
> ng-universal-demo@1.0.0 start /localdir/FrozenPandaz__ng-universal-demo
> npm run build && npm run server


> ng-universal-demo@1.0.0 prebuild /localdir/FrozenPandaz__ng-universal-demo
> npm run clean


> ng-universal-demo@1.0.0 clean /localdir/FrozenPandaz__ng-universal-demo
> rimraf dist


> ng-universal-demo@1.0.0 build /localdir/FrozenPandaz__ng-universal-demo
> webpack

Hash: a746c6e416ab32c2fe97cac872fdf2e493c7e402
Version: webpack 2.6.1
Child
    Hash: a746c6e416ab32c2fe97
    Time: 18765ms
              Asset       Size  Chunks                    Chunk Names
        0.client.js    2.07 kB       0  [emitted]
          client.js     2.5 MB       1  [emitted]  [big]  main
    0.client.js.map    1.07 kB       0  [emitted]
      client.js.map    3.03 MB       1  [emitted]         main
         index.html  222 bytes          [emitted]
       [0] ./~/rxjs/Observable.js 11.4 kB {1} [built]
       [3] ./~/rxjs/util/root.js 885 bytes {1} [built]
       [7] ./~/@angular/platform-browser/@angular/platform-browser.es5.js 141 kB {1} [built]
       [9] (webpack)/buildin/global.js 509 bytes {1} [built]
      [19] ./~/rxjs/add/operator/map.js 187 bytes {1} [built]
      [39] ./~/@angular/platform-browser-dynamic/@angular/platform-browser-dynamic.es5.js 5.88 kB {1} [built]
      [40] ./src/app/browser-app.module.ts 1.35 kB {1} [built]
      [41] ./~/reflect-metadata/Reflect.js 48 kB {1} [built]
      [42] ./~/zone.js/dist/zone.js 96 kB {1} [built]
      [44] ./~/@angular/compiler/@angular/compiler.es5.js 1.02 MB {1} [built]
      [45] ./src/app/app.module.ts 1.55 kB {1} [built]
      [47] ./src/main.browser.ts 350 bytes {1} [built]
      [49] ./src/modules/transfer-state/browser-transfer-state.module.ts 1.31 kB {1} [built]
      [50] ./~/process/browser.js 5.42 kB {1} [built]
      [78] ./~/rxjs/util/toSubscriber.js 760 bytes {1} [built]
        + 66 hidden modules
    Child html-webpack-plugin for "index.html":
           [0] ./~/html-webpack-plugin/lib/loader.js!./src/index.html 193 bytes {0} [built]
Child
    Hash: cac872fdf2e493c7e402
    Time: 24164ms
              Asset     Size  Chunks                    Chunk Names
        0.server.js  2.07 kB       0  [emitted]
          server.js  4.23 MB       1  [emitted]  [big]  main
    0.server.js.map  1.07 kB       0  [emitted]
      server.js.map  5.18 MB       1  [emitted]         main
       [4] ./~/@angular/core/@angular/core.es5.js 489 kB {1} [built]
     [145] ./src/api/app.ts 222 bytes {1} [built]
     [146] ./src/app/server-app.module.ts 2.11 kB {1} [built]
     [147] ./src/routes.ts 80 bytes {1} [built]
     [148] ./~/@nguniversal/express-engine/index.js 196 bytes {1} [built]
     [149] ./~/express/index.js 224 bytes {1} [built]
     [150] ./~/reflect-metadata/Reflect.js 48 kB {1} [built]
     [151] ./~/rxjs/Rx.js 9.65 kB {1} [built]
     [152] ./~/zone.js/dist/zone-node.js 71.1 kB {1} [built]
     [158] ./src/main.server.ts 1.22 kB {1} [built]
     [245] ./~/rxjs/add/operator/bufferCount.js 235 bytes {1} [built]
     [336] ./~/rxjs/add/operator/windowTime.js 229 bytes {1} [built]
     [337] ./~/rxjs/add/operator/windowToggle.js 241 bytes {1} [built]
     [338] ./~/rxjs/add/operator/windowWhen.js 229 bytes {1} [built]
     [339] ./~/rxjs/add/operator/withLatestFrom.js 253 bytes {1} [built]
        + 475 hidden modules

    WARNING in ./~/express/lib/view.js
    80:29-41 Critical dependency: the request of a dependency is an expression

> ng-universal-demo@1.0.0 server /localdir/FrozenPandaz__ng-universal-demo
> nodemon dist/server.js

[nodemon] 1.11.0
[nodemon] to restart at any time, enter `rs`
[nodemon] watching: dist/*server.js src/index.html
[nodemon] starting `node dist/server.js`
Listening at http://localhost:8000

Now we connect to port 8000 of the Docker host, I have mapped the internal port 8000 to:

When looking at the source code, we can see the server-side rendered HTML Code:

The text “Universal Demo” and “Hello World” are visible in the source.

Excellent! Thump up!

This is exactly, what we wanted to see: a web page with the full HTML content, not just the “Loading…” directive that you usually see in Angular Index files.

Phase 2: Create a functional new Link in the main Page

Step 2.1 Create a new Link on the Home Page

With following little change in blue, we will add a new link to the Hello World page:

src/app/app.component.ts

import { Component, OnInit } from '@angular/core'
import { TransferState } from '../modules/transfer-state/transfer-state';

@Component({
  selector: 'demo-app',
  template: `
    <h1>Universal Demo</h1>
    <a routerLink="/">Home</a>
    <a routerLink="/lazy">Lazy</a>
    <a routerLink="/blog">Blog</a>
    <router-outlet></router-outlet>
  `,
  styles: [
    `h1 {
      color: green;
    }`
  ]
})
export class AppComponent implements OnInit {
  constructor(private cache: TransferState) {}
  ngOnInit() {
    this.cache.set('cached', true);
  }
}

From the steps above, npm run watch is still running in the background.

It seems that npm run server needs to be stopped are restarted manually. Since we have started it in the foreground above, a <Ctrl>-C and re-issuing the command is sufficient:

(container)# <Ctrl>-C
(container)# npm run server

After that, the change should be seen immediately in the browser (try pressing F5 to refresh, if this is not the case):

You will notice, though, that the Blog link is not yet functional. When you press F12, choose the “console” tab in the Browser and reload the page, we will see, what is missing:

ERROR Error: Uncaught (in promise): Error: Cannot match any routes. URL Segment: 'blog'

We still need to define the route.

Step 2.2: Create a Route from /blog to a Module

For creating a route, we need to add the /blog route to following file:

src/routes.ts

export const ROUTES: string[] = [
  '/',
  '/lazy',
  '/blog'
];

As we can see in the browser network debugging (F12), the error message does not change:

ERROR Error: Uncaught (in promise): Error: Cannot match any routes. URL Segment: 'blog'

To turn this around, we need a second change: we need to add a link from ‘blog’ to a module. For now, let us point the /blog link to the same module as the /lazy link:

src/app/app.module.ts

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { APP_BASE_HREF, CommonModule } from '@angular/common';
import { HttpModule } from '@angular/http';
import { RouterModule } from '@angular/router';
import { AppComponent } from './app.component';
import { HomeView } from './home/home-view.component';
import { TransferHttpModule } from '../modules/transfer-http/transfer-http.module';


@NgModule({
  imports: [
    CommonModule,
    HttpModule,
    TransferHttpModule,
    RouterModule.forRoot([
      { path: '', component: HomeView, pathMatch: 'full'},
      { path: 'lazy', loadChildren: './+lazy/lazy.module#LazyModule'},
      { path: 'blog', loadChildren: './+lazy/lazy.module#LazyModule'}
    ])
  ],
  declarations: [ AppComponent, HomeView ],
  exports: [ AppComponent ]
})
export class AppModule {}

Now the /blog link is functional and is pointing to the lazy module, showing “i’m lazy”, when clicking on the Blog link.

Now let us create our own module that is pointing to “i’m a blog”

Step 2.3: Create your own Blog Module

Above, we have re-used an existing “LazyModule”. Now, let us create our own module by copying and changing LazyModule:

mkdir src/app/+blog
cp src/app/+lazy/lazy.module.ts src/app/+blog/blog.module.ts

We replace ‘lazy’ by ‘blog’ and ‘Lazy’ by ‘Blog’ in place:

sed -r -i "s/lazy/blog/g" src/app/+blog/blog.module.ts
sed -r -i "s/i'm blog/i'm a blog/g" src/app/+blog/blog.module.ts
sed -r -i "s/Lazy/Blog/g" src/app/+blog/blog.module.ts

After that, the content of blog.module.ts looks like follows:

Now we need to change the route to point to the new BlogModule:

sed -r -i '/blog/s/lazy/blog/g; /blog/s/Lazy/Blog/' src/app/app.module.ts

after that, the file content looks like follows:

src/app/app.module.ts

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { APP_BASE_HREF, CommonModule } from '@angular/common';
import { HttpModule } from '@angular/http';
import { RouterModule } from '@angular/router';
import { AppComponent } from './app.component';
import { HomeView } from './home/home-view.component';
import { TransferHttpModule } from '../modules/transfer-http/transfer-http.module';


@NgModule({
  imports: [
    CommonModule,
    HttpModule,
    TransferHttpModule,
    RouterModule.forRoot([
      { path: '', component: HomeView, pathMatch: 'full'},
      { path: 'lazy', loadChildren: './+lazy/lazy.module#LazyModule'},
      { path: 'blog', loadChildren: './+blog/blog.module#BlogModule'}
    ])
  ],
  declarations: [ AppComponent, HomeView ],
  exports: [ AppComponent ]
})
export class AppModule {}

After restarting the server and reloading the Browser, the link to ‘Blog’ leads to following output:

In the source view, we also can confirm that this is a server-rendered page:

This is exactly, what we wanted to see: a web page with the full HTML content, not just the “Loading…” directive that you usually see in Angular Index files. With that, we can see, that this a server rendered page.

Excellent! Thump up!

Step 2.4 Add Includes to Browser, Server and Server AOT

To be honest, I am a newbie to Angular and I do not exactly know the function of following three includes. However, I have found them by searching recursively for occurrences of the term “lazy”. Those three includes seem to be needed, although the server side rendering seems to look fine without as well.

This one might be needed for client side rendering within webpack:

src/tsconfig.browser.json

{
  "extends": "../tsconfig.json",
  "angularCompilerOptions": {
    "entryModule": "./app/browser-app.module#BrowserAppModule"
  },
  "include": [
    "./main.browser.ts",
    "./app/+lazy/lazy.module.ts",
    "./app/+blog/blog.module.ts"
  ]
}

AOT stands for “Ahead of Time” and often refers to the compile time. Since we are not compiling, but we are “transpiling” in case of Angular, I guess, the next file will control the server-side pre-tranpiled pages:

src/tsconfig.server.aot.json

{
  "extends": "./tsconfig.server.json",
  "angularCompilerOptions": {
    "genDir": "ngfactory",
    "entryModule": "./app/server-app.module#ServerAppModule"
  },
  "include": [
    "main.server.aot.ts",
    "./app/+lazy/lazy.module.ts",
    "./app/+blog/blog.module.ts",
    "./app/server-app.module.ts"
  ]
}

The next one is a server configuration file for non AOT pages?

src/tsconfig.server.json

{
  "extends": "../tsconfig.json",
  "angularCompilerOptions": {
    "entryModule": "./app/server-app.module#ServerAppModule"
  },
  "include": [
    "main.server.ts",
    "./app/+lazy/lazy.module.ts",
    "./app/+blog/blog.module.ts"
  ]
}

Phase 3: Inserting a WordPress Blog POST via RESTful API

In this phase, we will insert a single WordPress Blog Post via a RESTful API of WordPress, as we had done in my previous blog post “Consuming a RESTful Web Service with Angular 4“. However, this time, the page will be served server-side rendered, which helps for a much better user experience (quicker load time). Especially, the performance is improved substantially, if the WordPress REST API is reachable via a low-bandwidth connection only.

Step 3.1: Change your Blog Module to perform HTTP Requests

In order to perform HTTP requests, we need to adapt the file src/app/+blog/blog.module.ts file, so it performs the same function as did the file src/app/app.component.ts in my previous blog post about Angular consuming HTTP:

  • Like last time, we had to import Http, Response, Headers as well as map as well as Observable. We add Oninit as well, this time.
  • I have replaced the inline template by a templateUrl file. This also helps me to display the content of my Blog Module correctly. However, this will lead to typescript errors as long as the template is not created. We will do this soon.
  • Different from last time, we explicitly have defined private variables title and content. The reason we are not using data.title and data.content in the HTML template is, that the data is null as long as we are waiting for the HTML response and a title and content of null does not exist. When debugging the browser, errors are visible. With the private variable title and content, we do not create such errors.
  • Like last time, we need to define a private variable (_http in our case)with type Http as argument of the constructor
  • Different from last time, we have introduced an OnInit function, which is calling the getMyBlog() function, instead of calling this function in the constructor. However, both possibilities work fine.
  • The getMyBlog() function looks similar to last time. The only difference is, that we set the title and content explicitly.

src/app/+blog/blog.module.ts

import {NgModule, Component, OnInit} from '@angular/core'
import {RouterModule} from '@angular/router'
import { Http, Response, Headers } from '@angular/http';
import 'rxjs/add/operator/map'
import { Observable } from 'rxjs/Observable';


@Component({
  selector: 'blog-view',
  templateUrl: './blog.module.html'
})

export class BlogView implements OnInit {
  data: any = null;
  title: any = null;
  content: any = null;
  public subs: Observable<string>;

  constructor(private _http: Http) {
  }

  ngOnInit(){
    this.getMyBlog();
  }

  private getMyBlog() {
    return this._http.get('https://public-api.wordpress.com/rest/v1.1/sites/oliverveits.wordpress.com/posts/3078')
                .map((res: Response) => res.json())
                 .subscribe(data => {
                        this.data = data;
                        this.title = this.data.title;
                        this.content = this.data.content;
                        console.log(this.data);
                });
  }

}

@NgModule({
  declarations: [BlogView],
  imports: [
    RouterModule.forChild([
      { path: '', component: BlogView, pathMatch: 'full'}
    ])
  ]
})
export class BlogModule {

}

The function getMyBlog() is retrieving the data of a blog post from the WP.COM Rest API v1.1, similar to what we also have done on the previous blog post Consuming a RESTful Web Service with Angular 4. This time we are writing the title and content in the corresponding public variables, which can be used in the HTML template.

Step 3.2 Create Blog HTML Template

If your npm run watch command is still active, you will notice following error in the console, where the command is running:

    ERROR in ./src/app/+blog/blog.module.ts
    Module not found: Error: Can't resolve './blog.module.html' in '/ng-universal-demo/src/app/+blog'
     @ ./src/app/+blog/blog.module.ts 38:18-47
     @ ./src lazy
     @ ./~/@angular/core/@angular/core.es5.js
     @ ./src/app/browser-app.module.ts
     @ ./src/main.browser.ts

The error will disappear, if we create the following file:

src/app/+blog/blog.module.html

The blog.module.ts file is pointing to a HTML templateUrl on ./blog.module.html with following content (as picture, since WordPress is confused about the HTML code, even if set in <pre>…</pre> tags):

Now the error is disappeared. If we restart the npm run server and the Browser content (e.g. press F5), we will see following content:

And different from last time, we not only see “Loading…” in the source of the page, but we will see the full HTML content:

To be honest, we see the content more often than we need: we see it as “innerHtml”, but we also see it explicitly as data within the tags. Large pages will have doubled size. Okay, seeing it twice is better than seeing it no time at all. Let us call it not perfect, but still …

Excellent! Thump up!

Summary

In this blog post, we have performed following tasks:

  • Phase 1: Run a Server Side Rendered Hello World Page
    • Here we could show that the server provides the browser with the full HTML content
  • Phase 2: Create a functional new Link in the main Page
    • Those steps are about Link creation and routing to a module
  • Phase 3: Inserting a WordPress Blog POST via RESTful API
    • In those steps we have shown how to create a component that retrieves content from a REST API and how to display the information in a browser

We could see, that the user is presented with the page content much more quickly than was the case in a client rendered solution of my previous blog. Especially, if there is a low bandwidth connection between Angular server and REST service, the user perception is improved a lot by server side rendering: the content is displayed almost immediately as opposed of the several second loading time we had experienced in case of client side rendering.

Appendix: Why using innerHtml?

Let us first demonstrate, what happens, if we use following template: The HTML template is making use of the variables “title” and “content” we have defined in the blog.module.ts before. We need to make use of this innerHtml trick in order to display the HTML-based content correctly. If we would use “{{content}}” instead, we will see escaped HTML.

This is not, what we want. If we change the template by following content:

or better (see this StackOverflow Q&A that states “Sanitized content can’t be bound using prop="{{sanitizedContent}}" because {{}} stringyfies the value before it is assigned which breaks sanitization.”; we do not yet sanitize here, but we might do so in the future):

With this, we will get, what we want:

Appendix: Webpack Problem

Error

Webpack has been initialised using a configuration object that does not match the API schema.

How to reproduce

docker run -it -p 8081:8000 -v $(pwd):/localdir oveits/angular_hello_world:centos bash
cd /localdir
git clone https://github.com/robwormald/ng-universal-demo
cd ng-universal-demo
npm i
$ npm start
> ng-universal-demo@1.0.0 start /localdir/ng-universal-demo
> npm run build && npm run server


> ng-universal-demo@1.0.0 build /localdir/ng-universal-demo
> webpack -p

Invalid configuration object. Webpack has been initialised using a configuration object that does not match the API schema.
 - configuration.output.path: The provided value "dist" is not an absolute path!

Workaround

Clone the fork https://github.com/FrozenPandaz/ng-universal-demo instead.

Further Reading

2

Consuming a RESTful Web Service with Angular 4


In this step by step hello world style tutorial, you will learn how to

  • install Angular 4 using the Angular CLI
  • create an Angular single page application that is consuming and displaying data from a REST API (the WordPress API in this case)

At the end, we will have created a simple Angular 4 page that is displaying the contents of my previous blog post on WordPress, a post about Angular 4 Quickstart installation. The content has been retrieved from the WordPress REST API in JSON format. The title and content will be displayed:

Phase 1: Install Angular CLI on CentOS

Step 1.0: Install a Docker Host

This time, we will need to use a real Docker host: i.e., different from our last Angular Hello World Quickstart post, this time it is not possible to use Katacoda as our Docker Host playground. We will install Angular CLI, and this requires more Memory than is provided by Katacoda. If you have no access to a Docker host yet and if you are working on a Windows machine, you might want to follow the instructions found here; search for the term “Install a Docker Host”. It describes step by step how to install a Docker host on Virtualbox using Vagrant. I have made good experiences with that approach. For other operating systems, you may want to consult the official Docker installation instructions.

Note: the official Docker installation instructions for Windows might work as well, but years ago, when I had started with Docker, the official solution had urged me to write a blog post “Why is it so hard to install Docker on Windows”). Those were the times with boot2docker. However, the situation has improved from what I have heard from other colleagues that have started with Docker more recently. Still, I like the Vagrant approach a lot, and I will stick to that solution.

Step 1.1: Start CentOS Container

Let us create a Docker CentOS container. In the simplest case, this is done on a Docker host via:

docker run -it -p 4200:4200 centos bash

We have mapped TCP container port 4200 to the Docker host port 4200, since we want to access the service on this port from outside.

Persistent alternative (optional): to persist the Angular project we will be creating on the Docker host, it makes sense to map a  volume from Docker host to Docker container. I am using Vagrant with a synced folder on /vagrant (from point of view of the VirtualBox VM), so I have created a folder on this place for this purpose:

mkdir /vagrant/angular-4-on-centos; cd /vagrant/angular-4-on-centos
docker run -it -p 4200:4200 -v $(pwd):/localdir centos bash

Step 1.2: Install NodeJS and Angular CLI

on the container:

(container)# yum install -y epel-release
(container)# yum install -y https://kojipkgs.fedoraproject.org//packages/http-parser/2.7.1/3.el7/x86_64/http-parser-2.7.1-3.el7.x86_64.rpm
(container)# yum install -y nodejs
(container)# npm install -g @angular/cli

Note: the second command has been introduced recently, since the latest epel-release is missing the http parser library. See SvennD’s blog for more details.

In order to verify the installation, let us check the version of Angular CLI and node:

(container)# ng -v
    _                      _                 ____ _     ___
   / \   _ __   __ _ _   _| | __ _ _ __     / ___| |   |_ _|
  / △ \ | '_ \ / _` | | | | |/ _` | '__|   | |   | |    | |
 / ___ \| | | | (_| | |_| | | (_| | |      | |___| |___ | |
/_/   \_\_| |_|\__, |\__,_|_|\__,_|_|       \____|_____|___|
               |___/
@angular/cli: 1.1.0
node: 6.10.3
os: linux x64

Step 1.3: Create new Project

Now let us create a project:

cd /localdir
ng new my-project-name
cd my-project-name

Step 1.4: Start the Service

ng serve --host 0.0.0.0

> my-project-name@0.0.0 start /localdir/my-project-name
> ng serve

** NG Live Development Server is listening on localhost:4200, open your browser on http://localhost:4200 **
Hash: 46f1654c27ac4755fbcd
Time: 9010ms
chunk    {0} polyfills.bundle.js, polyfills.bundle.js.map (polyfills) 158 kB {4} [initial] [rendered]
chunk    {1} main.bundle.js, main.bundle.js.map (main) 5.29 kB {3} [initial] [rendered]
chunk    {2} styles.bundle.js, styles.bundle.js.map (styles) 10.5 kB {4} [initial] [rendered]
chunk    {3} vendor.bundle.js, vendor.bundle.js.map (vendor) 2.11 MB [initial] [rendered]
chunk    {4} inline.bundle.js, inline.bundle.js.map (inline) 0 bytes [entry] [rendered]
webpack: Compiled successfully.

Step 1.5: Connect to Service

If port 4200 is mapped to Vagrant port 4200 we get:

Excellent! Thump up!

Phase 2: Configure REST API Client

In this phase, we will see, how easy it is in Angular 2 or 4 to retrieve and display data from a REST API.

Step 2.1: Add HTTP Module

First we need to tell Angular that we will use the HttpModule. For that, we edit src/app/app.module.ts (added parts in red)

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { HttpModule }    from '@angular/http';

import { AppComponent } from './app.component';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    HttpModule,
    BrowserModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Step 2.2: Configure Component to use HTTP

In a second step, we configure Edit src/app/app.component.ts:

import { Component } from '@angular/core';
import { Http, Response, Headers } from '@angular/http';
import 'rxjs/add/operator/map'

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})

export class AppComponent {
  data: any = null;

  constructor(private _http: Http) {
    this.getMyBlog();
  }

  private getMyBlog() {
    return this._http.get('https://public-api.wordpress.com/rest/v1.1/sites/oliverveits.wordpress.com/posts/3078')
                .map((res: Response) => res.json())
                 .subscribe(data => {
                        this.data = data;
                        console.log(this.data);
                });
  }

}

Step 2.3: Adapt HTML Template

Remove all content of src/app/app.component.html and replace it by the following content:

(WordPress seems to have a problem displaying the content correctly, so I had to post it as a screenshot this time)

After that, we will see that the browser is displaying following content:

With that, we have created a little proof of concept how to retrieve data from a REST API and display the data dynamically.

Excellent! Thump up!

Summary

In this hello world style step-by-step guide, we have learned

  • how to install Angular 4 using the Angular CLI and
  • how to consume the WordPress API for retrieving and displaying a blog post title and HTML content.

Next steps

  • Retrieve a list of Blog Posts instead of a single post only.
    • Figure out to handle the fact that retrieving all posts takes a lot of time initially; the WordPress REST API does not seem to allow to retrieve the list of titles only. Do we need to create an improved REST service that is quicker and allows to
  • The REST client can be separated in its own service like shown in this article.
  • Optionally, this.getMyBlog function can be placed in an ngOnInit function instead of calling it in the constructor. It is not clear to me yet, what are the advantaged/disadvantages of this method.

Appendix: Exploring the WordPress REST API

The WordPress API can be explored via the WordPress.com REST API console.

You need to login via Sign in - WordPress Developer Console and your WordPress.com credentials.

There seem to be several types and versions of APIs:

  • WP.COM API
    • v1.1 (default)
    • v1
  • WP REST API
    • wp/v2
    • wpcom/v2

In this Proof of Concept / Hello World, we are using the WP.COM API V1.1 to retrieve the content of a single WordPress blog post of mine:

https://public-api.wordpress.com/rest/v1.1/sites/oliverveits.wordpress.com/posts/3078

However, we can also use the WP REST API wp/v2:

https://public-api.wordpress.com/wp/v2/sites/oliverveits.wordpress.com/posts/3078

Because of the organization of the wp/v2 result, we need to exchange data.title by data.title.rendered and data.content by data.content.rendered in the html template, though.

References:

0

Automating Network Provisioning with Cisco APIC — Exploring the REST API


How to automate the configuration of network devices? Cisco APIC-EM (= Application Policy Infrastructure Controller) is a controller that can help us with that task. In this blog post, we will explore its modern REST API for accomplishing basic tasks like creating, reading, updating and deleting (CRUD) objects like

  • APIC users (e.g. administrators)
  • network discovery tasks
  • network devices and their configurations.

We will use the Chrome Postman plugin for exploring the API.

Tldr;

Click here to have a look at the summary section.

What is Cisco APIC?

See here a nice 4 minute Cisco youtube commercial about APIC. And here you find a Cisco live! 40 minute session, which gives a short overview. I will summarize:

  • APIC is a controller with Web Interface and REST API, which can be used to Create, Read, Update and Delete (CRUD) following kind of objects (and more)
    • Network Devices
      • Locations
      • Interfaces
        • Links
        • Hosts
    • Policies
    • Tasks
  • Anything you can do in the Web interface can also be done via the REST API (similar to Ruby on Rails, RoR)
  • The video is showing how a REST test tool like Chrome Postman or simple Python scripts can be used to interface with the REST API. Some examples are:
    • displaying a list of all network devices
    • displaying a the network path from arbitrary address A to arbitrary address B
    • finding an ACL on a network path that is blocking a certain application
  • The REST API is self-documenting using Swagger
    hc_001
    Note: “self-documenting” means

    • that it can be explored through the same interface (but different path) like the API itself
    • the documentation is generated from source code. Note, that the developer needs to add the content of the swagger documentation to the source code, similar to javadocs. Swagger takes care of converting the source code into swagger interactive web pages.

Why should you install Cisco APIC?

Quick answer: you should not. The installation on VMware 5.1 or 5.5 would require 6 vCPUs and 64 GB of RAM, 500 GB disk and 200 MB disk I/O speed. A monster of an application. Wow.

To be more specific: you can install Cisco APIC, but you do not need to install Cisco APIC, if you just want to explore the Cisco APIC interfaces: there is a more clever, cloud-based possibility: DevNet sandboxes offered by Cisco.

If you still want to install it, the SW can be found via this DevNet page (use IE or Firefox; since Chrome is not supported; click on “3”). The SW is also available on Cisco’s SW repository. However, authorization is required there, while in DevNet, the access is less restrictive.

A clever Alternative: DevNet Sandboxes:

Instead of installing this monster application, we can also connect to one of the DevNet Learning Labs found in the DevNet Sandbox catalog.

2016.03.08-23_56_12-hc_001

Note that you need to register with Cisco, if you have not done already, either as a Customer or a Partner. In both cases, they want you to enter a contract number. I am a CCIE, so I already had an account.

See below more information on the pre-installed labs.

 

2016.03.09-00_42_31-hc_001

Most of the labs are APIC labs. When clicking on the “Develop in the Sandbox” button on the upper right of the page, I reach at the lab catalog.

2016.03.09-00_49_05-hc_001

Let us keep simple, and try to enter the APIC-EM DB Only Always-On lab: this seems to be a kind of guided lab. Cool. 😉

2016.03.09-00_50_40-hc_001

scrolled down on the left pane:

2016.03.09-15_31_28-hc_001_credentials_removed

we find a link to the APIC-EM portal: https://sandboxapic.cisco.com:9443. There, I could log in with the specified default credentials (removed).

2016.03.09-00_57_14-hc_001

With that, I have reached the APIC-EM portal:

2016.03.09-15_28_14-hc_001

Manipulating Users

Note: In this example, we will show how to create, read and delete users using Postman, a chrome app for sending RESTful HTTP commands (most important: POST, GET, PUT, DELETE for Create, Read, Update and Delete of an object).

Let us go back to the guided tour on the “APIC-EM DB Only Always-On lab” we had reached from the lab catalog: on the left pane, we can find a link to the sandbox:

2016.03.09-15_31_28-hc_001_credentials_removed

Furthermore, we can find a link to a hello world guided tour in order to explore the northbound REST interface.

There, they are requesting that the user should install Postman. This is the tool I use often to test my ProvisioningEngine and its target systems. Within Postman, I have created a new collection called “Cisco APIC – hello world sandbox”:

2016.03.14-01_22_40-hc_001

Before sending my first REST commands to the Cisco APIC, I would like to explore the self-documented interface. From what I have seen in the video, I just need to enter the link https://sandboxapic.cisco.com:9443/swagger in a browser. Yes; here we are:

2016.03.09-15_54_23-hc_001

In the hello world example, we will start with the creation of a ticket. Let us explore the ticket documentation:

2016.03.09-16_00_49-hc_001

Init: Creation of an authentication Token

Now, on Postman, let us perform our first real command: creation of a ticket. In Postman, I enter:

2016.03.09-16_17_17-hc_001

and the body:

2016.03.09-16_11_19-hc_001_password_removed

After pressing the “Send” button we get:

2016.03.09-16_15_40-hc_001

Good. So, what do we do with that?

Read Users: GET /user

The serviceTicket above then is used as an X-Auth-token for all subsequent requests. Let us use it to show all users:

2016.03.09-16_30_03-hc_001

Do not forget to switch from POST to GET, before you press the Send button.

Oups, we get: “Not Found”

That was, because I had a double-slash instead of a slash in the URL. After correcting the URL and pressing the Send button again, we get a meaningful answer:

{
  "response": [
    {
      "username": "greg",
      "authorization": [
        {
          "scope": "ALL",
          "role": "ROLE_ADMIN"
        }
      ]
    },
    ...
    {
      "username": "admin",
      "authorization": [
        {
          "scope": "ALL",
          "role": "ROLE_ADMIN"
        }
      ]
    },
    ...
  ,
  "version": "1.0"
}

Note: The API developers have chosen to use singular nouns like GET /user. This is a little bit awkward, since a GET /user is returning a collection of users, not a single user. GET /users sounds more natural, especially, if you are used to Ruby on Rails. However, at the end, it is a matter of convention. Unfortunately, there is no agreement on the convention either way and we find popular API examples for both conventions. Fortunately, they have chosen not to mix plural and singular nouns in the URL. Good.

Create a User: POST /user

With a little bit of trial and error, I also have been successful in creating a new user entry:

2016.03.09-17_04_53-hc_001

And yes: the user can be seen with a GET /user request as above:

2016.03.09-17_11_14-hc_001

The password seems to be saved more securely elsewhere.

Now, I can log into APIC portal as user “olli”:

2016.03.10-16_20_32-hc_001

Read single User: GET /user/{name}

Now let us read a single user. This is not straightforward, since the API developers have chosen not to auto-generate and display user IDs. Maybe the username is the ID? Then it needs to be unique. Let us test this first: trying to create a user with the same name:

2016.03.09-17_16_16-hc_001

yepp: the username is the unique ID. Following REST semantics, we should be able to read a single user with GET /user/olli:

2016.03.09-17_18_30-hc_001

Works fine.

Update User: PUT /user

The tricky part is to update the user. From swagger we see that the more obvious syntax

PUT /user/{id}

is not supported. We are required to issue the command

PUT /user

without ID, and the ID is specified in the body. This is not following REST API best practices and will be discussed after the Summary.

2016.03.09-17_55_44-hc_001

Even if I use the right syntax PUT /user, I am not allowed to change the password of user “olli” when using an authentication token (ticket) that has been created by the admin:

2016.03.10-16_35_47-hc_001

Password change via the REST API is supported only, if you log in as the user in question. Therefore I need to get a new ticket (token) for user olli instead:

2016.03.10-16_37_10-hc_001

And now we can use the serviceTicket as an authentication token in the update user command:

2016.03.10-16_38_38-hc_001

Note: you need to remove the /olli part from the URL above!

Now the update works:

2016.03.10-16_41_04-hc_001

Yesss! That was not easy. I have removed my first unsuccessful attempts, where I had tried with the URL /user/olli in order to simplify the post and in order to not confuse you.

Now I can log in to the APIC-EM portal using the updated password:

2016.03.10-16_43_11-hc_001

Delete user: DELETE /user/{name}

With the ticket (token) of the admin (again), it is easy to delete the entry via DELETE /user/olli:

2016.03.09-17_21_12-hc_001

Note: we have got a 200 OK upon a DELETE request, which indicates that it has been deleted immediately and that the response body is not empty. For asynchronous processing, 202 would have been the right answer HTTP code and for a successful deletion without response body, code 204 is used. See Section 9 of RFC2616.

As expected, after the deletion us user “olli”, a GET /user/olli then leads to “404 Not Found” error:

2016.03.09-17_39_25-hc_001

Discovering the Network

We did not start playing around with Cisco APIC in order to manipulate users, did we? Let us get into the real stuff: discovering and manipulating networks.

2016.03.09-18_11_42-hc_001

Read network discoveries: GET /discovery/{startIndex}/{recordsToReturn}

When I understand it right, I can manipulate network discovery tasks on /discovery. Two network discovery tasks have already been conducted:

2016.03.09-18_13_23-hc_001

What is missing in the interface, is a GET /discovery to return a list of all discoveries. The only request, which seems to come close is

GET /discovery/{startIndex}/{recordsToReturn}

2016.03.09-18_17_53-hc_001

Yes, GET /discovery/1/2 was, what I was looking for. Also GET /discovery/1/500 works fine, and comes close to GET  /discovery I was looking for. Note that 500 seems to be the largest possible value: GET /discovery/1/501 fails with following error message:

2016.03.10-14_23_42-hc_001

Unlike user objects, discovery objects have explicit IDs. Those are the IDs you need to use, if you want to manipulate a single discovery.

Read single network discoveriy: GET /discovery/{id}

Let us read a single discovery with ID=1: GET /discovery/1

2016.03.10-14_45_11-hc_001

I guess, you need to run a discovery first, which will populate the database with all network-devices, links, hosts, policies, etc. After that, the network-devices can be manipulated.

TODO: clear the network database, run a discovery and observe, what happens.

But for now, let us review the existing data in the network topology database.

Collecting Network Device Info

Read network device info: GET /network-device

Let us look for network-devices: with GET /network-device, we get a list of devices:

2016.03.10-15_50_25-hc_001

Read running-config of network devices: GET /network-device/config

That is interesting as well: we can retrieve the configuration of all known network-devices with a GET /network-device/config:

2016.03.10-15_55_27-hc_001

The id of the network device can be found if we scroll down:2016.03.10-15_56_30-hc_001

However, the config cannot be changed: there is no POST or PUT command defined for /network-device/config:

2016.03.10-15_58_37-hc_001

Summary

Cisco Application Policy Infrastructure Controller is a software based centralized provisioning system for networks running on Linux Ubuntu that provides administrators with the possibility to manage network policies (Security ACLs, QoS) from a central point using a modern RESTful interface. The software can run on either on physical hardware or on a VMware VM (ESXi V5.1 or 5.5). The resource needs are too challenging to be run on a developer’s notebook: 6 cores and 64 GB RAM (see release notes). However, Cisco offers Cloud based development sandboxes on DevNet. See here a catalog of APIC labs.

Using such a sandbox, we have demonstrated the usage of the north-bound REST API of Cisco APIC (= Application Policy Infrastructure Controller), a controller for automatic provisioning of Cisco based networks. For that, we have used a DevNet sandbox (registration required), a Cisco-hosted lab for developers. We have used the simplest species of those labs that is based on a pre-populated database with no real target network behind (southbound). We have demonstrated on how to

  • browse the API documentation
  • get an authentication token
  • show or manipulate
    • APIC users
    • network discovery tasks
    • network devices
    • and network device running-configs (read only)

The Cisco APIC offers many more features, which were not yet explored here. Among others, policies can be manipulated.

If you need to download the SW, it can be found via this DevNet page (use IE or Firefox; since Chrome is not supported; click on “3”).

Appendix: Does the Cisco APIC REST API follow REST best practice?

During the hands-on lab, we have observed that the Cisco APIC REST API does not follow REST best practices (another article on REST best practice can be found here):

  • the API is using singular nouns like GET /user for getting a collection of users
  • the API is not very consequent with the assignment of unique IDs:
    • for users, the name is used as ID
    • for network devices, UUIDs are used as IDs
    • for discoveries, incremented numbers 1,2, … are used as IDs
  • the syntax of the API for reading a collection depends on the object type:
    • for users: GET /user will display all defined users.
    • for discoveries: GET /discovery is not defined. Instead, only a paginated version of the command is supported: GET/discovery/{offset}/{number} will return up to 500 entries, starting with entry #{offset}.
      • I rate it as inconvenient that GET /discovery is not defined. I deem the developers will have had their reasons; maybe this is a measure to avoid too long answers or to limit the performance required by the requests?
      • pagination is a nice feature, especially, if used for GUI display. However, I personally like a more verbose syntax better: e.g. GET/discovery/pagination/offset/{offset}/limit/{limit} or GET/discovery?pagination=yes&offset={offset}&limit={limit}.
      • If an API offers pagination, it should do so with all objects. However, GET /discovery/1/500 is defined, but GET /user/1/500 leads to an error. On the other hand, GET /user is defined, but GET /discovery leads to an error.
  • The syntax for updating an entry is counter-intuitive and does not follow REST API best practices (IMO):
    • a single user can be read with GET /user/{id}, where the user name is used as ID. Following REST best practice, the same resource can be updated with a PUT /user/{id}. However, the developers have chosen that the syntax is PUT /user without ID, while the user ID is specified in the body. This makes sense only for batch bulk update of all users. However, the body contains a single entry only instead of a a list of entries.

Those are little inconveniences, we can live with, though.