Programação Reativa com Angular, Firebase e Observables

William Grasel

@willgmbr

fb.me/wgrasel

One Framework.

Mobile & desktop.

AngularJS !== Angular

É apenas Angular!

Longa vida ao Semantic Versioning!

Upgrading with ngUpgrade

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.interval(500)
    .map(i => i * 2)
    .take(3)
    .concat(
      Observable.of(10, 20, 30)
    )

  Observable.interval(500)
    .map(i => i * 2)
    .take(3)
    .concat(
      Observable.of(10, 20, 30)
    )
    .subscribe(console.log)

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

  Promise.resolve(1)
    .then(x => x * 2) // 2
    .then(x =>
      Promise.resolve(x + 10)
    ) // 12
    .then(console.log)

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

Tudo no Angular são Observables!

  • Http
  • Router
  • Forms
  • Etc...

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,
    ) {}
  }

Listando Objetos


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

  <section>
    <div>{{(userResult | async)?.name}}</div>
    <div>{{(userResult | async)?.age}}</div>
    <div>{{(userResult | async)?.email}}</div>
  </section>

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

Firebase

AngularFire

AngularJS + Firebase

3 Way Data Bind

Realtime Database

❤️

Observables


  @Component({
    selector: 'app-root',
    template: `
    <ul>
      <li *ngFor="let item of items | async">
        {{ item.name }}
      </li>
    </ul>
    `
  })
  export class MyApp {
    items = this.db.list('/items');

    constructor(private db: AngularFireDatabase) {}
  }

  @Component({
    selector: 'app-root',
    template: `
    <ul>
      <li *ngFor="let item of items | async">
        {{ item.name }}
      </li>
    </ul>
    `
  })
  export class MyApp {
    items = this.db.list('/items');

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

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();
  }

Será que da para deixa Redux ainda melhor?

ngrx

Reactive Extensions for Angular

Referências

Perguntas?

Obrigado! =)