You are on page 1of 43

Mildchat client

Warsztaty Angular4

Radosław Kintzi
Łukasz Wojciechowski
Część I - Wprowadzenie
O aplikacji
● Prosty grupowy czat
● Framework Angular4
● TypeScript
O aplikacji
● Prosty grupowy czat
● Framework Angular4
● TypeScript
● UI bazujące na IRC
O aplikacji
● Prosty grupowy czat
● Framework Angular4
● TypeScript
● UI bazujące na IRC
● Protokół oparty o WebSocket (3 ramki)
Build system
● Bazujemy na QuickStart seed z quick start guide
(https://angular.io/docs/ts/latest/quickstart.html)
● Przenieśliśmy pliki wynikowe (.js) do katalogu dist
● Wyłączyliśmy ghost mode w lite-server
Zmiany w build systemie

--- a/bs-config.json --- a/bs-config.json


+++ b/bs-config.json +++ b/bs-config.json
@@ -1,8 +1,9 @@ @@ -1,8 +1,9 @@
{ "version": "1.0.0",
"server": { "description": "...",
- "baseDir": "src", "scripts": {
+ "baseDir": "dist", - "build": "tsc -p src/",
"routes": { - "build:watch": "tsc -p src/ -w",
"/node_modules": "node_modules" + "build": "tsc -p src/ -outDir dist/",
} + "build:watch": "tsc -p src/ -outDir dist/ -w",
- } "build:e2e": "tsc -p e2e/",
+ }, "serve": "lite-server -c=bs-config.json",
+ "ghostMode": false "serve:e2e": "lite-server -c=bs-config.e2e.json",
}
Instalacja i uruchamianie (w trybie dev)
Instalacja:
● npm install
Uruchamianie (dwie konsole):
● npm run build:watch
● npm run serve
Serwer
● Prosty serwer Mildchat napisany w golang na potrzeby warsztatów
(https://github.com/rkintzi/mildchat-server)
● Uruchomiony jest pod adresem: ws://localhost:8080/chat
Komponenty (1)
● Komponenty to nie jest nowa idea (AngularJS, Polymer, React).
● Filozofia budowania aplikacji opiera się na komponentach.
● Większość komponentów to Presentational Components
○ Posiadać bardzo mało logiki
○ Trzymać najmniejszą ilość stanu aplikacji
○ Powinny być możliwie najbardziej reużywalne

● Pozostałe komponenty to Container Components


○ Łączą dane z logiką aplikacji
○ Wyświetlają Presentational Components
○ Są mało reużywalne
Komponenty (2)
Komponent - Przykład
import { Component } from '@angular/core';

@Component({
selector: 'app',
template: `<div>{{ name }}</div>`,
styles: [`div { color: red; }`]
})

export class AppComponent {


name: string;

constructor() {
this.name = "John";
}
}
Komponent - Rejestracja
import { NgModule } from '@angular/core';
import { AppComponent } from
'./app.component';
import { Chat } from './chat.component';

@NgModule({
bootstrap: [ AppComponent ],
declarations: [
AppComponent,
],
})
export class AppModule { }
Komponent - Składnia szablonów

Element Opis Przykład

NgIf Instrukcja warunkowa <div *ngIf=”prop”></div>

NgFor Pętla pozwalająca powtarzać elementy <div *ngFor="let elem of elems">


DOM {{elem}}</div>

Input Elementy wejściowe do komponentu <component [param]=”value”></component>

Output Zdarzenia komponentu <comp (action)=”handler”></comp>


Komponent - Data binding
{{ value }}

DOM (Template)

COMPONENT
[property]=”value”

(event)=”handler”

[(ng-model)]=”property”
Komponent - Data binding

Parent Template
Parent Component
Child Component
Property Binding

{ } { }
Event Binding
Shadow DOM
● Pozwala na separację drzewa DOM i CSS
● W połączeniu z komponentami adresuje style z komponentu tylko do niego
samego.
● Angular dostarcza dodatkowe selektory:

○ :host
○ :host-context
○ /deep/
Komponent - Struktura do implementacji

HeaderComponent

AppComponent

ChatComponent

CreateMessageComponent
Dependency Injection
● Rozluźnianie zależności między elementami systemu.
● Łatwiejsze testowanie

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


import { MyService } from './MyService';

@Component({
...
})

export class AppComponent {


constructor(
private service: MyService
) { }
}
Serwisy
import { Injectable } from '@angular/core';

@Injectable()
export class ChatService {
sendNickMessage(msg: NickMessage) {
...
}

sendChatMessage(msg: ChatMessage) {
...
}
}
Część III - Komunikacja z serwerem
Protokół
● Oparty o WebSocket, ramki w formacie JSON
● Protokół obsługuje trzy ramki:
○ ChatMessage - do przesyłania komunikatów tekstowych do/od wszystkich użytkowników
○ NickMessage - do przypisywania i zmiany nicków użytkowników
○ ErrorMessage - do sygnalizowania błędów przez serwer
● Dwie pierwsze ramki wysyła klient do serwera a serwer rozgłasza je do
wszystkich podłączonych użytkowników (również do nadawcy)
● Trzecia ramka wysyłana jest przez serwer tylko do klienta którego dotyczy
sygnalizowany błąd
Protokół - ramka ChatMessage
● Format ramki
{
"type": "ChatMessage",
"data": {
"author": "rakin",
"body": "what’s up"
}
}

● Serwer ignoruje pole author


Protokół - ramka NickMessage
● Format ramki
{
"type": "NickMessage",
"data": {
"old_name": "rakin",
"new_name": "radek"
}
}

● Serwer ignoruje pole old_name


Protokół - ramka ErrorMessage
● Format ramki:
{
"type": "ErrorMessage",
"data": {
"error_code": "NoNickSet",
"message": "Set your nickname first"
}
}

● Dwa kody błędów:


○ NoNickSet
○ InvalidNick
RxJS - Reactive programming
RxJS - Reactive programming
Co to jest reactive programming:
● Programowanie z wykorzystaniem asynchronicznych strumieni danych
● Strumienie można obserwować i reagować na pojawienie się nowych zdarzeń
● Źródłem zdarzeń może być cokolwiek:
○ interakcje użytkownika
○ strumień danych z sieci
○ tablice
○ ...
RxJS - Przykład (1)
var list = ['1','1','foo','3','4','5','bar','8','13'];

var result = list


.map(n=>parseInt(n, 10))
.filter(n=>!isNan(n))
.reduce((acc, n)=>acc+n)

console.log(result)
RxJS - Przykład (2)
var list = ['1','1','foo','3','4','5','bar','8','13']; var list = ['1','1','foo','3','4','5','bar','8','13'];
var stream = Rx.Observable.interval(400).take(9)
.map(i=>list[i]);

var result = list var result = stream


.map(n=>parseInt(n, 10)) .map(n=>parseInt(n, 10))
.filter(n=>!isNan(n)) .filter(n=>!isNaN(n))
.reduce((acc, n)=>acc+n) .reduce((acc,n)=>acc+n);

console.log(result) result.subscribe(x=>console.log(x));
RxJS - Observable, Observer, Subject (1)
var list = ['1','1','foo','3','4','5','bar','8','13'];
var stream = Rx.Observable.interval(400).take(9)
.map(i=>list[i]);

var result = stream


.map(n=>parseInt(n, 10))
.filter(n=>!isNaN(n))
.reduce((acc,n)=>acc+n);

result.subscribe(x=>console.log(x));
RxJS - Observable, Observer, Subject (2)
var list = ['1','1','foo','3','4','5','bar','8','13']; interface Observer<T> {
var stream = Rx.Observable.interval(400).take(9) closed?: boolean;
.map(i=>list[i]);
next: (value: T) => void;
var result = stream error: (err: any) => void;
.map(n=>parseInt(n, 10)) complete: () => void;
.filter(n=>!isNaN(n)) }
.reduce((acc,n)=>acc+n);

result.subscribe(x=>console.log(x));
RxJS - Observable, Observer, Subject (3)
var interval400 = Observable.create((observer)=>{ interface Observer<T> {
var n = 0; closed?: boolean;
var intr = setInterval(() => {
next: (value: T) => void;
observer.next(n);
n++; error: (err: any) => void;
}, 400); complete: () => void;
return ()=>clearInterval(intr); }
});
var list = ['1','1','foo','3','4','5','bar','8','13'];
var stream = interval400.take(9).map(i=>list[i]);

var result = stream


.map(n=>parseInt(n, 10))
.filter(n=>!isNaN(n))
.reduce((acc,n)=>acc+n);

result.subscribe(x=>console.log(x));
RxJS - Observable, Observer, Subject (4)
var obsr = { ... };
var subject = new Subject();
subject.subscribe(obsr);
subject.next(1);
subject.next(2);
...
RxJS - Observable, Observer, Subject (5)
var obsr = { ... };
var subject = new Subject();
subject.subscribe(obsr);
subject.next(1);
subject.next(2);
...

var obsr2 = { ... };


subject.subscribe(obsr2);
subject.next(3);
RxJS - Observable, Observer, Subject (6)
var obsr = { ... };
var subject = new Subject();
subject.subscribe(obsr);
subject.next(1);
subject.next(2);
...

var obsr2 = { ... };


subject.subscribe(obsr2);
subject.next(3);
...

subject.complete();
RxJS - Observable, Observer, Subject (7)
● Observable - utility do tworzenia strumieni danych
○ constructor vs operator (interval() vs take())

● Observer - obiekt służący do obserwowania danych w strumieniu


○ w strumieniu oprócz właściwych danych mogą pojawić się błędy oraz sygnał końca

● Subject - Observable i Observer w jednym


RxJS - Cold Streams vs Hot Streams (1)
RxJS - Cold Streams vs Hot Streams (2)
var interval400 = Observable.create((observer)=>{
var n = 0;
var intr = setInterval(() => {
observer.next(n);
n++;
}, 400);
return ()=>clearInterval(intr);
});
var list = ['1','1','foo','3','4','5','bar','8','13'];
var stream = interval400.take(9).map(i=>list[i]);

var result = stream


.map(n=>parseInt(n, 10))
.filter(n=>!isNaN(n))
.reduce((acc,n)=>acc+n);

result.subscribe(x=>console.log(x));
RxJS - Cold Streams vs Hot Streams (3)
var interval400 = Observable.create((observer)=>{ function makeHot(cold) {
var n = 0; var subject = new Subject();
var intr = setInterval(() => { var refs = 0;
observer.next(n); return Observable.create((observer)=>{
n++; var coldSub;
}, 400); if (refs == 0) {
return ()=>clearInterval(intr); coldSub = cold.subscribe(o=>subject.next(o));
}); }
var list = ['1','1','foo','3','4','5','bar','8','13']; refs++;
var stream = interval400.take(9).map(i=>list[i]); var hotSub = subject.subscribe(observer);
return () => {
var result = stream refs--;
.map(n=>parseInt(n, 10)) if (refs == 0) coldSub.unsubscribe();
.filter(n=>!isNaN(n)) hotSub.unsubscribe();
.reduce((acc,n)=>acc+n); };
});
result.subscribe(x=>console.log(x)); }
var stream = makeHot(interval400);
RxJS w Mildchat client
W Mildchat client użyjemy biblioteki RxJS do komunikacji z serwerem:

import { Subject } from 'rxjs/Subject'; interface Message {


import { Observable } from 'rxjs/Observable'; toString(): string,
import { Observer } from 'rxjs/Observer'; }

import 'rxjs/add/operator/map'; class ErrorMessage implements Message {


import 'rxjs/add/operator/filter'; errorCode: string;
import 'rxjs/add/observable/dom/webSocket'; message: string;

const CHAT_URL = 'ws://localhost:8080/chat'; toString(): string {


var ws = Observable.webSocket(CHAT_URL); return "Error (" + this.errorCode + "): " + this.message;
}
}
Testowanie
● Testy w Angularze to First Class Citizen.
● Pisane są w plikach *.spec.ts zaraz obok implementacji komponentów,
serwisów itp.
● W projekcie wykorzystujemy narzędzia Karma oraz Jasmine.
● Uruchamianie testów przez npm test
TestBed
● Główne narzędzie do testowania.
● Inicjalizuje moduł, w którym możemy konfigurować zależności testów.

TestBed.configureTestingModule({
declarations: [ Chat ],
providers: [
{ provide: ChatService, useValue: chatServiceStub },
],
});

fixture = TestBed.createComponent(Chat);

You might also like