Dominando Programação Reativa na Prática

William Grasel

@willgmbr

fb.me/wgrasel

define:

reactive programming 

"Reactive programming is a programming paradigm oriented around data flows and the propagation of change."

"Reactive programming is programming with asynchronous data streams"

- André Staltz

Data Streams?

O que mais pode ser um Stream?

  • Ações do usuário
  • Eventos do sistema
  • Estado da aplicação
  • Validações e regras de negócio
  • Qualquer fonte de dados, assíncrona ou não!

Qual a melhor forma de representar um fluxo de dados assíncrono?

síncrono

assíncrono

múltiplo

único

processamento

valor

Objeto

Array

Promise

Observable

Observable

Observable !== Object.observe

RxJS

The ReactiveX library for JavaScript.


  [1, 2, 3]
    .map(i => i * 2) // [2, 4, 6]
    .filter(i => i > 5) // [6]
    .concat([10, 20, 30]) // [6, 10, 20, 30]

Operadores ❤️


  Observable.of(1, 2, 3)
    .map(i => i * 2)
    .filter(i => i > 5)
    .concat(
      Observable.of(10, 20, 30)
    )

  Observable.fromEvent(document, 'click')
    .map(i => i * 2)
    .filter(i => i > 5)
    .concat(
      Observable.fromEvent(document, 'blur')
    )

  Observable.from(function* (){ yield 9; })
    .map(i => i * 2)
    .filter(i => i > 5)
    .concat(
      Observable.from([10, 20, 30])
    )
    .subscribe(console.log)

  Array.of(1, 2, 3)
    .map(i => i * 2)
    .filter(i => i > 5)
    .concat(
      Array.of(10, 20, 30)
    )

  Observable.interval(500)
    .map(i => i * 2)
    .filter(i => i > 5)
    .concat(
      Observable.of(10, 20, 30)
    )

  Observable.from(function* (){ yield 9; })
    .map(i => i * 2)
    .filter(i => i > 5)
    .concat(
      Observable.from([10, 20, 30])
    )

Observable

❤️

Promise


  Observable.from(Promise.resolve(1))
    .concat(Promise.resolve(2))
    .concat(Promise.resolve(3))
    .subscribe(console.log); // 1, 2, 3

Mas e se eu quiser uma Promise?


  Observable.of(1, 2, 3)
    .toPromise()
    .then(console.log) // 3

  Observable.of(1, 2, 3)
    .first()
    .toPromise()
    .then(console.log) // 1

Tá, e o que eu ganho com isso?

Show me the code!

Typeahead Search


  <input type="text" [(ngModel)]="myInput">
  <ul>
    <li *ngFor="let r of results">{{r.name}}</li>
  </ul>

  <input type="text" [formControl]="myInput">
  <ul>
    <li *ngFor="let r of results">{{r.name}}</li>
  </ul>

  <input type="text" [formControl]="myInput">
  <ul>
    <li *ngFor="let r of results | async">{{r.name}}</li>
  </ul>

  @Component({
    selector: 'app-root',
    templateUrl: './app.component.html',
    styleUrls: ['./app.component.css']
  })
  export class AppComponent {
    myInput = new FormControl;
    results = Observable.of([]);
  }

  @Component({
    selector: 'app-root',
    templateUrl: './app.component.html',
    styleUrls: ['./app.component.css']
  })
  export class AppComponent {
    myInput = new FormControl;
    results = Observable.of([])
      .merge(this.myInput.valueChanges);
  }

  @Component({
    selector: 'app-root',
    templateUrl: './app.component.html',
    styleUrls: ['./app.component.css']
  })
  export class AppComponent {
    myInput = new FormControl;
    results = Observable.of([])
      .merge(this.myInput.valueChanges)
      .map(v => `https://swapi.co/api/people/?search=${v}`);
  }

  @Component({
    selector: 'app-root',
    templateUrl: './app.component.html',
    styleUrls: ['./app.component.css']
  })
  export class AppComponent {
    myInput = new FormControl;
    results = Observable.of([])
      .merge(this.myInput.valueChanges)
      .map(v => `https://swapi.co/api/people/?search=${v}`)
      .mergeMap(url => this.http.get(url));

    constructor(private http: HttpClient) {}
  }

  @Component({
    selector: 'app-root',
    templateUrl: './app.component.html',
    styleUrls: ['./app.component.css']
  })
  export class AppComponent {
    myInput = new FormControl;
    results = Observable.of([])
      .merge(this.myInput.valueChanges)
      .map(v => `https://swapi.co/api/people/?search=${v}`)
      .switchMap(url => this.http.get(url))
      .map(json => json['results']);

    constructor(private http: HttpClient) {}
  }

  @Component({
    selector: 'app-root',
    templateUrl: './app.component.html',
    styleUrls: ['./app.component.css']
  })
  export class AppComponent {
    myInput = new FormControl;
    results = Observable.of([])
      .merge(this.myInput.valueChanges)
      .filter(v => v.length > 2)
      .map(v => `https://swapi.co/api/people/?search=${v}`)
      .switchMap(url => this.http.get(url))
      .map(json => json['results']);

    constructor(private http: HttpClient) {}
  }

  @Component({
    selector: 'app-root',
    templateUrl: './app.component.html',
    styleUrls: ['./app.component.css']
  })
  export class AppComponent {
    myInput = new FormControl;
    results = Observable.of([])
      .merge(this.myInput.valueChanges)
      .filter(v => v.length > 2)
      .debounceTime(300)
      .map(v => `https://swapi.co/api/people/?search=${v}`)
      .switchMap(url => this.http.get(url))
      .map(json => json['results']);

    constructor(private http: HttpClient) {}
  }

  @Component({
    selector: 'app-root',
    templateUrl: './app.component.html',
    styleUrls: ['./app.component.css']
  })
  export class AppComponent {
    myInput = new FormControl;
    results = Observable.of([])
      .merge(this.myInput.valueChanges)
      .map(v => `https://swapi.co/api/people/?search=${v}`)
      .mergeMap(url => this.http.get(url))
      .map(json => json['results']);

    constructor(private http: HttpClient) {}
  }

  @Component({
    selector: 'app-root',
    templateUrl: './app.component.html',
    styleUrls: ['./app.component.css']
  })
  export class AppComponent {
    myInput = new FormControl;
    results = Observable.of([])
      .merge(this.myInput.valueChanges)
      .filter(v => v.length > 2)
      .debounceTime(300)
      .map(v => `https://swapi.co/api/people/?search=${v}`)
      .switchMap(url => this.http.get(url))
      .map(json => json['results'])
      .retry(3);

    constructor(private http: HttpClient) {}
  }

Router como Stream


  export class AppComponent {

    results = this.route.params
      .map(p => p.name)
      .switchMap(name => this.user.search(name));

    constructor(
      private route: ActivatedRoute,
      private user: UserService,
    ) {}
  }

  export class AppComponent {

    myInput = new FormControl;

    results = this.route.params
      .map(p => p.name)
      .merge(this.myInput.valueChanges)
      .switchMap(name => this.user.search(name));

    constructor(
      private route: ActivatedRoute,
      private user: UserService,
    ) {}
  }

Para que async/await?


  <ul>
    <li *ngFor="let user of userList | async">
      {{user.name}}
    </li>
  </ul>

  <section *ngIf="userResult | async as user">
    <div>{{user.name}}</div>
    <div>{{user.age}}</div>
    <div>{{user.email}}</div>
  </section>

Firebase

Realtime Database

❤️

Observables

AngularFire


  @Component({
    template: `
    <div *ngFor="let user of users | async">
      {{ user.name }}
    </div>`
  })
  export class MyComponent {
    users = this.db.list('/users');

    constructor(private db: AngularFireDatabase) {}
  }

  @Component({
    template: `
    <div *ngFor="let user of users | async">
      {{ user.name }}
    </div>`
  })
  export class MyComponent {
    users = this.db.list('/users');

    constructor(private db: AngularFireDatabase) {}
  
    addItem() {
      this.items.push({ name: 'new item' });
    }
  }

Ridiculamente Simples

Bora juntar tudo!


  export class MyComponent {

    myInput = new FormControl;

    users = this.myInput.valueChanges
      .merge(this.route.parms)
      .startWith('joão')
      .switchMap(busca =>



      ));

    constructor(
      private db: AngularFireDatabase,
      private route: ActivatedRoute,
    ) {}
  }

  export class MyComponent {

    myInput = new FormControl;

    users = this.myInput.valueChanges
      .merge(this.route.parms)
      .startWith('joão')
      .switchMap(busca =>
        this.db.list('/users', ref =>
          ref.orderByChild('name')
            .equalTo(busca)
      ));

    constructor(
      private db: AngularFireDatabase,
      private route: ActivatedRoute,
    ) {}
  }

Que tal ganhar ainda

mais performance

de brinde?

Detecção de Mudanças

Input:

data

Output:

event

Mudando a Estratégia!


  import { Component, ChangeDetectionStrategy } from '@angular/core';
  import { Observable } from 'rxjs/Rx';

  @Component({
    selector: 'count-3',
    changeDetection: ChangeDetectionStrategy.OnPush,
    template: '<p>Hey: {{ count | async }}</p>',
  })
  export class HeroAsyncMessageComponent {
    count = Observable.interval(500).take(3).repeat();
  }

Redux

Será que da para deixa Redux ainda melhor?

ngrx

Reactive Extensions for Angular

redux-observable

Compose and cancel async actions to create side effects and more.

Referências

Perguntas?

Obrigado! =)

Dominando Programação Reativa na Prática

By William Grasel

Dominando Programação Reativa na Prática

Passado o hype de programação reativa de uns cinco anos atrás, foi tempo suficiente para esse conceito e suas ferramentas amadurecerem em meio a comunidade. Nessa apresentação veremos técnicas avançadas de programação assíncrona, muito além de callbacks, promises e async/await, para domar todo tipo de fluxo de dados com facilidade, utilizando um pattern muito antigo chamado Observable. Tudo isso em meio a exemplos práticos com novas ferramentas para seu dia a dia!

  • 2,716