Skip to content
This repository has been archived by the owner on Feb 5, 2021. It is now read-only.
/ ng2chat Public archive

This small app serves as material for an Angular 2 Workshop. Starting from the Socket.io example app (https://github.com/rauchg/chat-example), we are rewriting the client side of their example by using Angular 2 and Webpack. Follow along the README to find out the steps.

Notifications You must be signed in to change notification settings

cassilup/ng2chat

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

63 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Note: This is a living document. Pull requests and Issues are most welcome.

Angular2 Workshop: ng2chat

What we'll be creating

Introduction

Starting from the official Socket.io chat example, we are building an Angular2 chat app in order to explore some of Angular's features.

We will be using Webpack for bundling all the assets together.

The Workshop has 2 sections:

  1. Tooling & Initial Configuration
  2. Writing the Angular2 Application

Topics we will be covering during this workshop:

  • Angular2
    • Bootstrapping,
    • Components, Component Architecture,
    • NgModule,
    • Special Decorators,
    • Services (Declaring, Providing and Injecting),
    • Change detection (Zones provided by zone.js),
    • RxJS (Observables),
    • Templates (ngFor, HTMLElement by reference)
    • Karma + Jasmine Testing,
    • ...and others.
  • TypeScript (and Typings)
  • ES6 (classes, imports, arrow functions, template literals),
  • Webpack (Loaders, Dev Server, Plugins)
  • NPM as Task Runner
  • Jasmine & Karma
  • Websockets

Before Starting

Please make sure you have Node.js and npm installed.

Once these are installed, you're ready to go further.

1. Tooling & Initial Configuration

1. Clone this repository:

git clone https://github.com/cassilup/ng2chat.git

2. Navigate to the 1-start/ folder.

cd 1-start/
  • This folder holds the starting point, Socket.io's example. We will be using this code as a starting point. It provides us with a Node.js server that listens for incoming messages and emits them to all the clients that are connected.

3. Install the Node modules by running:

npm install

This command looks for the package.json file and install all packages listed under dependencies and devDependencies.

To find out more about Node Package Manager and the package.json file, click here: http://browsenpm.org/package.json.

To find out about peer dependencies, click here: https://nodejs.org/en/blog/npm/peer-dependencies/.

The folder structure we have should now be:

/
|-  app.js
|-  index.html
|-  node_modules/
|-  package.json

4. Run the app to make sure it works. Running this command will start the server:

node app.js

Navigate to http://localhost:3000. You should see the Socket.io chat app.

5. Let's organise our app before we start bringing in new libraries.

  • Create a new folder: client/
  • Create a new folder: server/
  • Move app.js and index.html into server/.
  • Note: We leave node_modules/ together with package.json. The two always belong together.
  • Best Practice: Have only one package.json file per project.

The new folder structure is:

/
|-  client/
|-  node_modules/
|-  package.json
|-  server/
    |-  app.js
    |-  index.html

6. We are now ready to bring in Angular2 libraries. Change directory to client/.

Note: npm commands relate to the first package.json they encounter while navigating the folder structure upwards.

7. Install Angular2 npm packages:

npm install --save @angular/common @angular/compiler @angular/core @angular/http @angular/platform-browser @angular/platform-browser-dynamic @angular/router bootstrap core-js reflect-metadata rxjs zone.js

To find out more about the npm packages that Angular2 requires, click here: https://angular.io/docs/ts/latest/guide/npm-packages.html.

To find out more about zone.js, here's an excellent video presentation by Brian Ford: https://www.youtube.com/watch?v=3IqtmUscE_U

Also, here is Misko Hevery's Zones proposal to TC39:

8. Install Webpack & Tooling npm packages:

npm install --save-dev webpack webpack-dev-server typescript ts-loader css-loader extract-text-webpack-plugin html-webpack-plugin raw-loader style-loader
npm install --global typings

9. Install Testing npm packages:

npm install --save-dev jasmine-core karma karma-chrome-launcher karma-jasmine karma-phantomjs-launcher karma-sourcemap-loader karma-webpack phantomjs-prebuilt es6-shim

10. Configure package.json as a Task Runner. Add the following section to your package.json file:

...
"scripts": {
    "start": "webpack-dev-server --config ./client/webpack/webpack.dev.js",
    "api": "node server/app.js",
    "test": "karma start ./client/karma/karma.conf.js",
    "test:headless": "karma start ./client/karma/karma.conf.js --browsers PhantomJS"
},
...

If you are interested in finding out more about using NPM as a task runner, click here: http://paulcpederson.com/articles/npm-run/.

11. We now need to set up Typescript. We will do that by creating the tsconfig.json file in client/ with the following contents:

{
    "compilerOptions": {
        "emitDecoratorMetadata": true,
        "experimentalDecorators": true,
        "module": "commonjs",
        "sourceMap": true,
        "target": "es5"
    },
    "exclude": [
        "node_modules",
        "client/typings/index",
        "client/typings/index.d.ts"
    ]
}

Note: We place the tsconfig.json file in the client/ folder because it is related to our Angular2 project, not to the node app in server/. Webpack will know to automatically search for a tsconfig.json and if it doesn't find it, move up the folder structure until it does. For more information on the subject, please read: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html.

Information on why we have configured the target property to es5 and have set module to commonjs can be found here: http://www.jbrantly.com/es6-modules-with-typescript-and-webpack/

More information on JavaScript Module Loaders can be found here: https://appendto.com/2016/06/the-short-history-of-javascript-module-loaders/. We are setting the module option to CommonJS, because that's what we're transpiling to.

12. Initialize typings:

typings init

This will create the typings.json file in the folder where we ran this command.

13. Install needed typings:

typings install dt~jasmine env~node dt~es6-promise --save --global

This will create the typings/ folder in the current folder. The typings/ folder will contain the typings indicated in the typings.json file. Similar to how npm works by reading the package.json and creating the node_modules/ off of it.

The --global flag means that the library is bound to the global scope (eg. it will be invoked by using window.<variable>).

--save adds the entry to typings.json.

Important! Be sure not to run typings init or typings install in multiple folders of your project. That will result in Typescript compilation errors.

Best Practice: Recommended way of reinstalling typings is to run:

typings install -oc

(That is the equivalent of running typings install --overwrite --clean and it deletes the typings/ folder and recreates it.)

More information on typings can be found here: https://github.com/typings/typings

Our app structure now looks like this:

/
|-  client/
    |- typings/
    |- typings.json
    |- tsconfig.json
|-  node_modules/
|-  server/
    |-  app.js
    |-  index.html
|-  package.json

14. It's now time to set up Webpack.

We create a webpack/ folder inside the client/ folder.

The first file we create in that folder is webpack.dev.js.

'use strict';

const HtmlWebpack = require('html-webpack-plugin');
const path = require('path');
const webpack = require('webpack');
const ChunkWebpack = webpack.optimize.CommonsChunkPlugin;
const ExtractTextPlugin = require('extract-text-webpack-plugin');

const rootDir = path.resolve(__dirname, '..');

module.exports = {
  debug: true,
  devServer: {
    contentBase: path.resolve(rootDir, 'dist'),
    port: 9000
  },
  devtool: 'inline-source-map',
  entry: {
    app: [ path.resolve(rootDir, 'src', 'bootstrap') ],
    vendor: [ path.resolve(rootDir, 'src', 'vendor') ]
  },
  module: {
     loaders: [
       { loader: 'raw', test: /\.(css|html)$/ },
       { exclude: /node_modules/, loader: 'ts', test: /\.ts$/ },
       { loader: ExtractTextPlugin.extract("style-loader", "css-loader"), test: /\.css$/ }
     ]
   },
   output: {
     filename: '[name].bundle.js',
     path: path.resolve(rootDir, 'dist')
   },
   plugins: [
    new ChunkWebpack({ // prevents us from having same lib imported multiple times
      filename: 'vendor.bundle.js',
      minChunks: Infinity,
      name: 'vendor'
    }),
    new HtmlWebpack({ // automatically injects <script> tag into index.html
      filename: 'index.html',
      inject: 'body',
      template: path.resolve(rootDir, 'src', 'index.html')
    }),
    new ExtractTextPlugin("styles.css")
  ],
  resolve: {
    extensions: [ '', '.ts', '.js' ]
  }
};

Notice we are loading styles through Webpack.

Also, for the resolve block, the order of the extensions does matter.

15. To start our Angular2 app in development mode, type:

npm start

To start the API, type:

npm run api

16. We will now set up Karma, the test runner. We are doing this on purpose before writing any Angular2 code, to emphasise the importance of writing tests.

Inside the client/ folder, create a new folder named karma/.

We will need 2 files in there: karma.conf.js and karma.entry.js.

For More information on this approach to testing, read this article: https://semaphoreci.com/community/tutorials/setting-up-angular-2-with-webpack.

karma.conf.js will have the following contents:

'use strict';

module.exports = (config) => {
  config.set({
    autoWatch: true,
    browsers: ['Chrome', 'PhantomJS'],
    files: [
      '../node_modules/es6-shim/es6-shim.min.js',
      'karma.entry.js'
    ],
    frameworks: ['jasmine'],
    logLevel: config.LOG_INFO,
    phantomJsLauncher: {
      exitOnResourceError: true
    },
    preprocessors: {
      'karma.entry.js': ['webpack', 'sourcemap']
    },
    reporters: ['dots'],
    singleRun: false,
    webpack: require('../webpack/webpack.test'),
    webpackServer: {
      noInfo: true
    }
  });
};

Note: We are ussing ES6 syntax. Namely, the Arrow Function. For More information on ES6, click here: http://es6-features.org/#Constants.

18. We now need to write webpack.test.js.

'use strict';

const path = require('path');
const webpack = require('webpack');

module.exports = {
  devtool: 'inline-source-map',
  module: {
    loaders: [
      { loader: 'raw', test: /\.(css|html)$/ },
      { exclude: /node_modules/, loader: 'ts', test: /\.ts$/ },
      { loader: "style-loader!css-loader", test: /\.css$/ }
    ]
  },
  resolve: {
    extensions: ['', '.js', '.ts'], // empty string is for node_modules, which we don't specify extension for
    modulesDirectories: ['node_modules'],
    root: path.resolve('.', 'src')
  }
};

webpack.test.js is simpler than webpack.dev.js, as it only loads the bare minimum for tests to execute.

17. karma.entry.js will be the entry point for Karma when testing our application.

require('es6-shim');
require('reflect-metadata');
require('zone.js/dist/zone');
require('zone.js/dist/long-stack-trace-zone');
require('zone.js/dist/async-test');
require('zone.js/dist/fake-async-test');
require('zone.js/dist/sync-test');
require('zone.js/dist/proxy');
require('zone.js/dist/jasmine-patch');

const browserTesting = require('@angular/platform-browser-dynamic/testing');
const coreTesting = require('@angular/core/testing');

coreTesting.TestBed.resetTestEnvironment();
coreTesting.TestBed.initTestEnvironment(
  browserTesting.BrowserDynamicTestingModule,
  browserTesting.platformBrowserDynamicTesting()
);

const context = require.context('../src/', true, /\.spec\.ts$/);
context.keys().forEach(context);

Error.stackTraceLimit = Infinity;
jasmine.DEFAULT_TIMEOUT_INTERVAL = 2000;

Once configured, this file will not require tweaking.

19. Now that we've set up the testing environment, it's time to see it in action.

To start the test suite, run:

npm test

For Headless testing:

npm test:headless

--

2. Writing the Angular2 Application

1. Create src/ folder

2. Create index.html

<!doctype html>
<html>
   <body>
      <app>Loading...</app>
   </body>
</html>
  • Notice there's no <script> tag. Neither a <style> one.
  • Notice the <app> tag.

3. Bootstrap Angular2 app through bootstrap.ts.

import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';

const platform = platformBrowserDynamic();
platform.bootstrapModule(AppModule);

We need to use platformBrowserDynamic because we're writing an app that compiles the templates dynamically in the browser. We also need thesethis line because Angular2 can run on multiple platforms.

Note that the ES6 import syntax is "destructuring-like" and not actually ES6 destructuring. Find out more about the subject here: https://ponyfoo.com/articles/es6-destructuring-in-depth#special-case-import-statements

4. Create vendor.ts

import 'es6-promise';
import 'reflect-metadata';
import 'zone.js/dist/zone';

Libraries that Angular2 needs in order to run. We keep them separated for the sake of clarity.

Note that the syntax used here is a special case of the ES6 import statement, where it just brings in a package and attaches it to the global scope.

5. Create the app/ folder inside client/src/.

The folder structure now looks like this:

/
|-  client/
   |-  typings/
   |-  typings.json
   |-  karma/
       |-  karma.conf.js
       |-  karma.entry.js
   |-  src/
       |-  app/
       |-  bootstrap.ts
       |-  index.html
       |-  styles.css
       |-  vendor.ts
   |-  webpack/
       |-  webpack.dev.js
       |-  webpack.test.js
   |-  typings.json
   |-  tsconfig.json
|-  node_modules/
|-  server/
   |-  app.js
   |-  index.html
|-  package.json

6. Declaring the Application Module

We need to create the client/src/app/app.module.ts file.

Angular2 RC-5 brought the @NgModule() decorator. With its help, we can create modules in our app.

import { NgModule }      from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { App } from './app.component';

@NgModule({
  imports:      [ BrowserModule ], // because it's a web application and it runs in the browser
  declarations: [ App ],
  bootstrap:    [ App ]
})
export class AppModule { }

More information on @NgModule can be found here: https://angular.io/docs/ts/latest/guide/ngmodule.html and here: https://www.youtube.com/watch?v=Usohbij6frA

6. Once we've created our module, we can proceed to create our first Component. app/app/component.ts:

import { Component } from "@angular/core";

@Component({
  selector: 'app',
  template: `
    <div class="app-wrapper">Hi!</div>
  `
})
export class App {}

This is the implementation of the <app> selector in index.html.

7. messageList

8. newMessage

  • Events and properties.

9. chatService

10. Providing the service to the module, instantiating the service.

11. Submitting new messages.

  • Notice that Angular2 automatically keeps the Components in sync with the data in the service.

12. Making use of ChatService in MessageList.

More information:

8. Bringing in socket.io and connecting ChatService to the Backend.

More information: http://www.syntaxsuccess.com/viewarticle/socket.io-with-rxjs-in-angular-2.0


Aaaand, we're done! :)

About

This small app serves as material for an Angular 2 Workshop. Starting from the Socket.io example app (https://github.com/rauchg/chat-example), we are rewriting the client side of their example by using Angular 2 and Webpack. Follow along the README to find out the steps.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published