1 of 49

Reactive Angular2

October 25, 2016

Rob Wormald

@robwormald

2 of 49

Rob Wormald

Developer Advocate @ Google

Mildly Obsessed With Reactive Programming

3 of 49

Reactive APIs in Angular 2

  • Forms
  • Http
  • Async Pipe
  • Router
  • @Query decorators

4 of 49

A bit of history

5 of 49

BUT FETCH?

6 of 49

BUT SCARY

7 of 49

BUT SCARY

Promise

Promises

8 of 49

WHAT ARE YOU DOING NOOOO

9 of 49

Observables are about composition made easy

10 of 49

Reactive Forms - Typeahead

“Any time somebody talks about Observables and mentions type-aheads, take a drink”

11 of 49

Observable APIs in Angular 2

  • Forms
  • Http
  • Async Pipe
  • Router

12 of 49

Reactive Forms - Form Group

<input type=”text” [formControl]=”myControl”>

<ul>

<li *ngFor=”r of results”>{{ r.name }}</li>

<ul>

13 of 49

Reactive Forms - Form Group

export class AppComponent {

myControl = new FormControl();

}

14 of 49

Reactive Forms - Value Changes

export class AppComponent {

myControl = new FormControl();

constructor(){

this.myControl.valueChanges

.subscribe(text => console.log(text));� }

}

15 of 49

Reactive Forms - Validity Changes

export class AppComponent {

myControl = new FormControl();

constructor(){

this.myControl.statusChanges

.subscribe(text => console.log(text));� }

}

16 of 49

Reactive Forms - Typeahead

export class AppComponent {

myControl = new FormControl();

constructor(){

this.myControl.valueChanges

.subscribe(text => console.log(text));� }

}

17 of 49

Reactive Forms - Typeahead

export class AppComponent {

myControl = new FormControl();

constructor(http:Http){

this.myControl.valueChanges

.subscribe(text => console.log(text));� }

}

18 of 49

Reactive Forms - Typeahead - map to URL

export class AppComponent {

myControl = new FormControl();

constructor(http:Http){

this.myControl.valueChanges

.map(text => `http://api.com?q=${text}`)

.subscribe(url => console.log(url));� }

}

19 of 49

Reactive Forms - Typeahead - flatMap Request => Response

export class AppComponent {

myControl = new FormControl();

constructor(http:Http){

this.myControl.valueChanges

.map(text => `http://api.com?q=${text}`)

.flatMap(url => http.get(url))

.subscribe(response => console.log(response));� }

}

20 of 49

Reactive Forms - Typeahead - flatMap Request => Response

export class AppComponent {

myControl = new FormControl();

constructor(http:Http){

this.myControl.valueChanges

.map(text => `http://api.com?q=${text}`)

.flatMap(url => http.get(url))

.map(res => res.json())

.subscribe(results => console.log(results));� }

}

21 of 49

Reactive Forms - Typeahead - Result Selector

export class AppComponent {

myControl = new FormControl();

constructor(http:Http){

this.myControl.valueChanges

.map(text => `http://api.com?q=${text}`)

.flatMap(url => http.get(url), (req, res) => res.json())

.subscribe(results => console.log(results));� }

}

22 of 49

Reactive Forms - Typeahead - In-flight requests

export class AppComponent {

myControl = new FormControl();

constructor(http:Http){

this.myControl.valueChanges

.map(text => `http://api.com?q=${text}`)

.switchMap(url => http.get(url), (req, res) => res.json())

.subscribe(results => console.log(results));� }

}

23 of 49

Reactive Forms - Typeahead - Async Pipe

export class AppComponent {

myControl = new FormControl();

results: any[];

constructor(http:Http){

this.myControl.valueChanges

.map(text => `http://api.com?q=${text}`)

.switchMap(url => http.get(url), (req, res) => res.json())

.subscribe(results => this.results = results);� }

}

24 of 49

Reactive Forms - Typeahead - Async Pipe

<input type=”text” [formControl]=”myControl”>

<ul>

<li *ngFor=”r of results”>{{ r.name }}</li>

<ul>

25 of 49

Reactive Forms - Typeahead - Async Pipe

export class AppComponent {

myControl = new FormControl();

results: Observable<any[]>

constructor(http:Http){

this.results = this.myControl.valueChanges

.map(text => `http://api.com?q=${text}`)

.switchMap(url => http.get(url), (req, res) => res.json())� }

}

26 of 49

Reactive Forms - Typeahead - Async Pipe

<input type=”text” [formControl]=”myControl”>

<ul>

<li *ngFor=”r of results | async”>{{ r.name }}</li>

<ul>

27 of 49

Reactive Forms - Typeahead - Optimizing

export class AppComponent {

myControl = new FormControl();

results: Observable<any[]>

constructor(http:Http){

this.results = this.myControl.valueChanges

.filter(text => text.length > 2)

.map(text => `http://api.com?q=${text}`)

.switchMap(url => http.get(url), (req, res) => res.json())� }

}

28 of 49

Reactive Forms - Typeahead - Optimizing

export class AppComponent {

myControl = new FormControl();

results: Observable<any[]>

constructor(http:Http){

this.results = this.myControl.valueChanges

.filter(text => text.length > 2)

.debounceTime(150)

.map(text => `http://api.com?q=${text}`)

.switchMap(url => http.get(url), (req, res) => res.json())� }

}

29 of 49

Reactive Forms - Typeahead - Reusable Logic with .let()

export class AppComponent {

myControl = new FormControl();

results: Observable<any[]>

constructor(http:Http){

this.results = this.myControl.valueChanges

.filter(text => text.length > 2)

.debounceTime(150)

.map(text => `http://api.com?q=${text}`)

.switchMap(url => http.get(url), (req, res) => res.json())� }

}

30 of 49

Reactive Forms - Typeahead - Reusable Logic with .let()

function filterAndDebounce(input:Observable<string>){

return input

.filter(text => text.length > 2)

.debounceTime(150)

}

31 of 49

Reactive Forms - Typeahead - Reusable Logic with .let()

export class AppComponent {

myControl = new FormControl();

results: Observable<any[]>

constructor(http:Http){

this.results = this.myControl.valueChanges

.let(filterAndDebounce)

.map(text => `http://api.com?q=${text}`)

.switchMap(url => http.get(url), (req, res) => res.json())� }

}

32 of 49

Reactive Forms - Typeahead - Composition

function filterAndDebounceFor(input:Observable<string>){

return (delay) => input

.filter(text => text.length > 2)

.debounceTime(delay)

}

33 of 49

Reactive Forms - Typeahead - Reusable Logic with .let()

export class AppComponent {

myControl = new FormControl();

results: Observable<any[]>

constructor(http:Http){

this.results = this.myControl.valueChanges

.let(filterAndDebounceFor(150))

.map(text => `http://api.com?q=${text}`)

.switchMap(url => http.get(url), (req, res) => res.json())� }

}

34 of 49

Reactive Forms - Typeahead - Reusable Logic with .let()

export class AppComponent {

myControl = new FormControl();

results: Observable<any[]>

constructor(http:Http){

this.results = this.myControl.valueChanges

.let(filterAndDebounceFor(150))

.map(text => `http://api.com?q=${text}`)

.switchMap(url => http.get(url), (req, res) => res.json())� }

}

35 of 49

Router Composition

  • Forms
  • Http
  • Async Pipe
  • Router

36 of 49

Router Composition

Detail View

List View

37 of 49

Reactive Routing - List Component

export class ListComponent {

users : Observable<User[]>

constructor(userService:UserService){

this.users = userService.fetchUsers();� }

}

38 of 49

Reactive Routing - Detail Component

export class DetailComponent {

constructor(userService:UserService, route:ActivatedRoute){

� }

}

39 of 49

Reactive Routing - Detail Component

export class DetailComponent {

constructor(userService:UserService, route:ActivatedRoute){

route.params.subscribe(params => { … });� }

}

40 of 49

Reactive Routing - Detail Component

export class DetailComponent {

constructor(userService:UserService, router:Router){

router.queryParams.subscribe(params => { … });� }

}

41 of 49

Reactive Routing - Detail Component

export class DetailComponent {

constructor(userService:UserService, route:ActivatedRoute){

route.params

.flatMap(params => userService.getUserById(params.id))

.subscribe(userProfile => console.log(userProfile));

}

}

42 of 49

Reactive Routing - Detail Component

export class DetailComponent {

constructor(userService:UserService, route:ActivatedRoute){

route.params

.switchMap(params =>

userService.getUserById(params.id))

.subscribe(userProfile => console.log(userProfile));

}

}

43 of 49

Reactive Routing - Detail Component

export class DetailComponent {

user:Observable<User>

constructor(userService:UserService, route:ActivatedRoute){

this.user = route.params

.switchMap(params =>

userService.getUserById(params.id));

}

}

44 of 49

Reactive Routing - Detail Component View

<h1>User Profile</h1>

<div>

<span>{{ (user | async)?.name }}</span>

<span>{{ (user | async)?.id }}</span>

<img [src]=”(user | async)?.profile_url”></img>

</div>

45 of 49

Reactive Routing - Detail Container Component

export class DetailContainerComponent {

user:Observable<User>

constructor(userService:UserService, route:ActivatedRoute){

this.user = route.params

.switchMap(params =>

userService.getUserById(params.id));

}

}

46 of 49

Reactive Routing - Profile Component

<div>

<span>{{ user.name }}</span>

<span>{{ user.id }}</span>

<img [src]=”user.profile_url”></img>

<div>

export class ProfileComponent {

@Input() user:User = {}

}

47 of 49

Reactive Routing - Detail Container Component Refactored

<h1>User Profile</h1>

<user-profile [user]=”user | async”></user-profile>

48 of 49

Soon?

export class ProfileComponent {

@ObserveChild(SomeComponent, ‘click’) clicks;

@ObserveChildren(‘kids’, ‘customEvent’) customEvents;

}

49 of 49

The End!