Como se inscrever em um evento em um serviço no Angular2?

112

Eu sei como gerar um evento com o EventEmitter. Também posso anexar um método a ser chamado se tiver um componente como este:

<component-with-event (myevent)="mymethod($event)" />

Quando tenho um componente como esse, tudo funciona muito bem. Mudei alguma lógica para um serviço e preciso gerar um evento de dentro do serviço. O que eu fiz foi isso:

export class MyService {
  myevent: EventEmitter = new EventEmitter();

  someMethodThatWillRaiseEvent() {
    this.myevent.next({data: 'fun'});
  }
}

Eu tenho um componente que precisa atualizar algum valor com base neste evento, mas não consigo fazer funcionar. O que eu tentei foi isso:

//Annotations...
export class MyComponent {
  constructor(myService: MyService) {
    //myService is injected properly and i already use methods/shared data on this.
    myService.myevent.on(... // 'on' is not a method <-- not working
    myService.myevent.subscribe(.. // subscribe is not a method <-- not working
  }
}

Como faço para que MyComponent se inscreva no evento quando o serviço que o gera não é um componente?

Estou em 2.0.0-alpha.28

EDIT: Modifiquei meu "exemplo de trabalho" para realmente funcionar, então o foco pode ser colocado na parte que não está funcionando;)

Código de exemplo: http://plnkr.co/edit/m1x62WoCHpKtx0uLNsIv

Per Hornshøj-Schierbeck
fonte
1
Pode ser mais uma questão de design, pois um serviço não deve emitir eventos em nome de um componente, pois isso acopla fortemente o serviço ao componente. por exemplo, e se houver várias instâncias do componente, todas devem emitir eventos nesse caso?
Angular University
@jhadesdev eu li você. Redesenhei a solução para que o serviço não precise mais emitir o resultado. Eu ainda acho que alguns projetos se beneficiariam por serem capazes de gerar eventos - dependendo do tipo de "serviço" que seja ...
Por Hornshøj-Schierbeck
uma maneira, então, poderia ser fazer com que o componente criasse o emissor de evento em vez do serviço e, em seguida, passasse o emissor de evento como um argumento para o serviço
Angular University

Respostas:

135

Update : Eu encontrei uma maneira melhor / adequada de resolver este problema usando um BehaviorSubject ou um Observable em vez de um EventEmitter. Veja esta resposta: https://stackoverflow.com/a/35568924/215945

Além disso, os documentos Angular agora têm um exemplo de livro de receitas que usa um Assunto .


Resposta original / desatualizada / errada: novamente, não use um EventEmitter em um serviço . Isso é um anti-padrão.

Usando beta.1 ... NavService contém o EventEmiter. O Component Navigation emite eventos por meio do serviço e o componente ObservingComponent se inscreve nos eventos.

nav.service.ts

import {EventEmitter} from 'angular2/core';
export class NavService {
  navchange: EventEmitter<number> = new EventEmitter();
  constructor() {}
  emitNavChangeEvent(number) {
    this.navchange.emit(number);
  }
  getNavChangeEmitter() {
    return this.navchange;
  }
}

components.ts

import {Component} from 'angular2/core';
import {NavService} from '../services/NavService';

@Component({
  selector: 'obs-comp',
  template: `obs component, item: {{item}}`
})
export class ObservingComponent {
  item: number = 0;
  subscription: any;
  constructor(private navService:NavService) {}
  ngOnInit() {
    this.subscription = this.navService.getNavChangeEmitter()
      .subscribe(item => this.selectedNavItem(item));
  }
  selectedNavItem(item: number) {
    this.item = item;
  }
  ngOnDestroy() {
    this.subscription.unsubscribe();
  }
}

@Component({
  selector: 'my-nav',
  template:`
    <div class="nav-item" (click)="selectedNavItem(1)">nav 1 (click me)</div>
    <div class="nav-item" (click)="selectedNavItem(2)">nav 2 (click me)</div>
  `,
})
export class Navigation {
  item = 1;
  constructor(private navService:NavService) {}
  selectedNavItem(item: number) {
    console.log('selected nav item ' + item);
    this.navService.emitNavChangeEvent(item);
  }
}

Plunker

Mark Rajcok
fonte
Eu acompanhei isso, mas só funciona para a classe em si. Não está funcionando para outra classe (de outro arquivo). Parece que o problema está na instância da classe de serviço. Existe alguma maneira de tornar a classe de serviço estática?
asubanovsky de
5
@asubanovsky, no plunker eu apenas injeto NavService no nível de bootstrap (ou seja, componente raiz), então deve haver apenas uma instância do serviço para todo o aplicativo. Você está injetando em outro lugar providers: [NavService]? Nesse caso, você obterá várias instâncias do serviço.
Mark Rajcok
Muito obrigado. Agora está funcionando como minha expectativa. Agora eu entendo a diferença entre injetar os provedores no nível de bootstrap e no nível do componente.
asubanovsky
Esta postagem foi seriamente alterada para um estado em que eu não "entendo" mais. Por que chamar um mehtod no serviço .getNavChangeEmitter () que retorna o EventEmitter é diferente de se inscrever diretamente no navChange?
Per Hornshøj-Schierbeck
@ PerHornshøj-Schierbeck, usei o método getNavChangeEmitter () para esconder o fato de que um EventEmitter estava sendo usado dentro do serviço. Os usuários do método podem presumir que o objeto retornado possui um subscribe()método. Usando um método, podemos facilmente alterar o EventEmitter para um Assunto ou Observable, o que é muito melhor, pois agora aprendemos que EventEmitters deve ser usado apenas para propriedades de saída. A maneira correta é usar um Assunto, BehaviorSubject ou Observable, e normalmente os assinamos diretamente, portanto, não precisamos mais de um método getNavChangeEmitter ().
Mark Rajcok
6

Usando o alfa 28, consegui inscrever-me programaticamente em emissores de eventos por meio do eventEmitter.toRx().subscribe(..)método. Como não é intuitivo, pode ser alterado em uma versão futura.

Runeboy
fonte
1
talvez forneça um exemplo simples para as pessoas usarem se toparem com esta postagem? A propósito, não fui eu quem votou contra;)
Per Hornshøj-Schierbeck
2
Não sei por que foi rejeitado, certamente funciona. Simplificando, para se inscrever em um "clique" de emissor de evento, use click.toRx().subscribe(your_callback_function)
Runeboy
Eu acho que pode ter sido rejeitado porque falta um exemplo funcional. Eu sei que valeria a pena um voto positivo e talvez uma resposta aceita se tivesse um link para um pluncker / jsfiddle fiddle ou mesmo algumas linhas de código para exibir melhor a sintaxe;)
Por Hornshøj-Schierbeck
Estou recebendo EXCEÇÃO: ReferenceError: o clique não está definido @Runeboy Você pode fornecer um plnkr para sua solução?
Benjamin McFerren
Você precisa definir um EventEmitter chamado click para que funcione
Mazen Elkashef