RxJS Avançado: compondo interfaces visuais reativamente

William Grasel

@willgmbr

fb.me/wgrasel

define:

reactive programming 

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

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!

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])
    )

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

Como eu acho meu operador mágico?

Marbles Diagrams

Animando os Diagramas!

Premissas, Padrões e Boas Práticas da Reatividade

Imutabilidade!

Todo estado deve fazer parte do fluxo!

NUNCA faça um subscribe dentro de outro subscribe!


  @Component()
  export class UserAddressComponent {

    constructor (
      user: UserService,
      address: AddressService,
    ) {
      user.getCurrentUser().subscribe(user => {



      })
    }
    
  }

  @Component()
  export class UserAddressComponent {

    constructor (
      user: UserService,
      address: AddressService,
    ) {
      user.getCurrentUser().pipe(

      ).subscribe(user => {
        this.address = address;
      })
    }
    
  }

  @Component()
  export class UserAddressComponent {

    constructor (
      user: UserService,
      address: AddressService,
    ) {
      user.getCurrentUser().subscribe(user => {
        address.getAddressFromUser(user).subscribe(address => {
          this.address = address;
        })
      })
    }
    
  }

  @Component()
  export class UserAddressComponent {

    constructor (
      user: UserService,
      address: AddressService,
    ) {
      user.getCurrentUser().pipe(
        mergeMap(user => this.address.getAddressFrom(user))
      ).subscribe(address => {
        this.address = address;
      })
    }
    
  }

Não esqueça de fazer o unsubscribe!

Mas se puder, evite o unsubscribe!

Existem formas de automatizar o unsubscribe!

No Angular use o Pipe Async sempre que possível!

Limite a duração dos eventos reativamente usando operadores!


  @Component()
  export class MyComponent {

    ngOnInit() {
      interval(1000)
        .subscribe(i => console.log(i));
    }

  }

  @Component()
  export class MyComponent {

    unsubscribe = new Subject();

    ngOnInit() {
      interval(1000)

        .subscribe(i => console.log(i));
    }

    ngOnDestroy() {
      this.unsubscribe.next();
      this.unsubscribe.complete();
    }
    
  }

  @Component()
  export class MyComponent {

    unsubscribe = new Subject();

    ngOnInit() {
      interval(1000)
        .pipe(takeUntil(this.unsubscribe))
        .subscribe(i => console.log(i));
    }

    ngOnDestroy() {
      this.unsubscribe.next();
      this.unsubscribe.complete();
    }
    
  }

Construindo uma Arquitetura Reativa!

Vamos subir nos ombros de gigantes!

Capture all changes to an application state as a sequence of events.

FOWLER, Martin. 2005

Modelar a mudança de estados como uma sequencia imutável de eventos

Cada evento descreve apenas a sua mudança para o estado, não o estado atual

O estado atual só pode ser descoberto depois de executar toda a cadeia de eventos!

Mas como eu faço isso na prática?

Command Query Responsibility Segregation

FOWLER, Martin. 2011

Um padrão de implementação para Event Sourcing

Visa separar a responsabilidade de quem e de quem escreve

Visa separar a

manutenção do estado

dos efeitos colaterais

Como eu escalo essa arquitetura para toda a aplicação?

Existem libs focadas em aplicar esse padrão para aplicações inteiras!

NGRX

Mas e os efeitos colaterais? 

Redux implementa o CQRS, mas não completamente 

@ngrx/effects

RxJS powered side effect model

redux-observable

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

Referências

Perguntas?

Obrigado! =)

RxJS Avançado: compondo interfaces visuais reativamente

By William Grasel

RxJS Avançado: compondo interfaces visuais reativamente

Programação reativa no mundo do Front End não é sobre usar um framework ou outro, é sobre como estruturar seu código e toda sua arquitetura de modo que esse paradigma faça sentido. Quem já brincou com qualquer implementação do ReactiveX sabe que é muito fácil dar um nó nas nossas cabeças, sem saber como estruturar os stream de modo que tudo se encaixem corretamente, ou mesmo sem encontrar o operador ideal para cada situação. Essa não é uma palestra introdutória ao tema, essa é uma palestra para ajudar aqueles que já queimaram muitos neurônios e querem melhorar seus skills com exemplos práticos.

  • 1,914