Como crio um serviço singleton no Angular 2?

142

Eu li que injetar quando o bootstrapping deve ter todos os filhos compartilhando a mesma instância, mas meus componentes principal e de cabeçalho (o aplicativo principal inclui o componente de cabeçalho e a saída do roteador) estão recebendo uma instância separada dos meus serviços.

Eu tenho um FacebookService que eu uso para fazer chamadas para a API Javascript do Facebook e um UserService que usa o FacebookService. Aqui está o meu bootstrap:

bootstrap(MainAppComponent, [ROUTER_PROVIDERS, UserService, FacebookService]);

No meu registro, parece que a chamada de autoinicialização termina, e vejo o FacebookService e o UserService sendo criados antes da execução do código em cada um dos construtores, MainAppComponent, HeaderComponent e DefaultComponent:

insira a descrição da imagem aqui

Jason Goemaat
fonte
8
Tem certeza de que não adicionou UserServicee FacebookServicea providersnenhum outro lugar?
Günter Zöchbauer 24/03

Respostas:

132

Jason está completamente certo! É causada pela maneira como a injeção de dependência funciona. É baseado em injetores hierárquicos.

Existem vários injetores em um aplicativo Angular2:

  • A raiz que você configura ao inicializar seu aplicativo
  • Um injetor por componente. Se você usar um componente dentro de outro. O injetor de componente é filho do componente pai. O componente do aplicativo (aquele que você especifica ao inicializar seu aplicativo) tem o injetor raiz como pai).

Quando o Angular2 tenta injetar algo no construtor do componente:

  • Examina o injetor associado ao componente. Se houver um correspondente, ele será usado para obter a instância correspondente. Essa instância é criada preguiçosamente e é um singleton para esse injetor.
  • Se não houver um provedor nesse nível, ele examinará o injetor pai (e assim por diante).

Portanto, se você deseja ter um singleton para todo o aplicativo, é necessário que o provedor seja definido no nível do injetor raiz ou do componente do aplicativo.

Mas o Angular2 examinará a árvore do injetor por baixo. Isso significa que o provedor no nível mais baixo será usado e o escopo da instância associada será este nível.

Veja esta pergunta para mais detalhes:

Thierry Templier
fonte
1
Obrigado, isso explica bem. Foi apenas contra-intuitivo para mim, porque isso meio que rompe com o paradigma de componente independente do angular 2. Então, digamos que estou criando uma biblioteca de componentes para o Facebook, mas quero que todos usem um serviço de singleton. Talvez haja um componente para mostrar a foto do perfil do usuário conectado e outro para postar. O aplicativo que usa esses componentes deve incluir o serviço do Facebook como provedor, mesmo que não use o serviço em si? Eu poderia simplesmente devolver um serviço com um 'getInstance ()' método que gere o singleton do serviço real ...
Jason Goemaat
@tThierryTemplier Como eu faria o oposto, eu tenho uma classe de serviço comum que quero injetar em vários componentes, mas instancia uma nova instância sempre (as opções de provedores / diretivas devem ser descontinuadas e removidas na próxima versão)
Boban Stojanovski
Desculpe por ser tão estúpido, mas não está claro para mim como você cria um serviço singleton, você pode explicar com mais detalhes?
você precisa saber é o seguinte
Portanto, para trabalhar com uma única instância de um serviço, ele deve ser declarado como provedor em app.module.ts ou em app.component.ts?
precisa saber é o seguinte
Declarar cada serviço apenas em app.module.ts, fez o trabalho por mim.
precisa saber é o seguinte
142

Atualização (Angular 6 +)

A maneira recomendada de criar um serviço singleton mudou. Agora é recomendado especificar no @Injectabledecorador no serviço que ele deve ser fornecido na 'raiz'. Isso faz muito sentido para mim e não há mais a necessidade de listar todos os serviços fornecidos em seus módulos. Você apenas importa os serviços quando precisa deles e eles se registram no local apropriado. Você também pode especificar um módulo para que ele seja fornecido apenas se o módulo for importado.

@Injectable({
  providedIn: 'root',
})
export class ApiService {
}

Atualização (Angular 2)

Com o NgModule, acho que a maneira de fazer isso agora é criar um 'CoreModule' com sua classe de serviço e listar o serviço nos provedores do módulo. Em seguida, importe o módulo principal no módulo principal do aplicativo, que fornecerá a instância para todos os filhos que solicitarem essa classe em seus construtores:

CoreModule.ts

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ApiService } from './api.service';

@NgModule({
    imports: [
        CommonModule
    ],
    exports: [ // components that we want to make available
    ],
    declarations: [ // components for use in THIS module
    ],
    providers: [ // singleton services
        ApiService,
    ]
})
export class CoreModule { }

AppModule.ts

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { AppComponent } from './app.component';
import { CoreModule } from './core/core.module';

@NgModule({
    declarations: [ AppComponent ],
    imports: [
        CommonModule,
        CoreModule // will provide ApiService
    ],
    providers: [],
    bootstrap: [ AppComponent ]
})
export class AppModule { }

Resposta original

Se você listar um provedor bootstrap(), não precisará listá-lo no decorador de componentes:

import { ApiService } from '../core/api-service';

@Component({
    selector: 'main-app',
    templateUrl: '/views/main-app.html',
    // DO NOT LIST PROVIDERS HERE IF THEY ARE IN bootstrap()!
    // (unless you want a new instance)
    //providers: [ApiService]
})
export class MainAppComponent {
    constructor(private api: ApiService) {}
}

De fato, listar sua classe em 'fornecedores' cria uma nova instância dela, se algum componente pai já a lista, os filhos não precisam, e se o fizerem, obterão uma nova instância.

Jason Goemaat
fonte
1
A partir de agora (janeiro de 2017), você listaria o serviço [providers]em seu arquivo de módulo, não em bootstrap(), certo?
precisa saber é o seguinte
5
Por que não colocar ApiServiceem AppModule's providers, e, assim, evitar a necessidade de CoreModule? (Não sei se este é o que @JasonSwett está sugerindo.)
Jan Aagaard
1
@JasonGoemaat Você pode adicionar o exemplo de como usá-lo no componente? Poderíamos importar o ApiServicena parte superior, mas por que, em seguida, colocá-lo na matriz de provedores do CoreModule e depois importá-lo no app.module ... ainda não clicou em mim.
Hogan #
Portanto, colocar o serviço no provedor de módulos fornecerá um singleton para esse módulo. E colocar o serviço nos provedores de componentes criará uma nova instância para cada instância do componente? Isso está certo?
BrunoLM 21/02
@BrunoLM Criei um aplicativo de teste para ajudar a mostrar o que está acontecendo. Interessante que, embora TestServiceseja especificado nos módulos principal e de aplicativo, as instâncias não são criadas para os módulos porque são fornecidas pelo componente para que o angular nunca chegue tão alto na árvore do injetor. Portanto, se você fornecer um serviço em seu módulo e nunca usá-lo, uma instância não parecerá criada.
Jason Goemaat
24

Eu sei que angular tem injetores hierárquicos como Thierry disse.

Mas tenho outra opção aqui, caso você encontre um caso de uso em que realmente não queira injetá-lo no pai.

Podemos conseguir isso criando uma instância do serviço e fornecendo sempre o retorno.

import { provide, Injectable } from '@angular/core';
import { Http } from '@angular/core'; //Dummy example of dependencies

@Injectable()
export class YourService {
  private static instance: YourService = null;

  // Return the instance of the service
  public static getInstance(http: Http): YourService {
    if (YourService.instance === null) {
       YourService.instance = new YourService(http);
    }
    return YourService.instance;
  }

  constructor(private http: Http) {}
}

export const YOUR_SERVICE_PROVIDER = [
  provide(YourService, {
    deps: [Http],
    useFactory: (http: Http): YourService => {
      return YourService.getInstance(http);
    }
  })
];

E, em seu componente, você usa seu método de fornecimento personalizado.

@Component({
  providers: [YOUR_SERVICE_PROVIDER]
})

E você deve ter um serviço de singleton sem depender dos injetores hierárquicos.

Não estou dizendo que essa é uma maneira melhor, apenas no caso de alguém ter um problema em que injetores hierárquicos não são possíveis.

Joel Almeida
fonte
1
Deve SHARE_SERVICE_PROVIDERestar YOUR_SERVICE_PROVIDERno componente? Também estou assumindo que a importação do arquivo de serviço é necessária como normal e o construtor ainda terá um parâmetro do tipo 'YourService', certo? Eu gosto disso, eu acho, permite garantir um singleton e não ter que garantir que o serviço seja fornecido na hierarquia. Ele também permite que componentes individuais obtenham sua própria cópia, apenas listando o serviço em providersvez do provedor singleton, certo?
Jason Goemaat
@JasonGoemaat você está certo. Editou isso. Exatamente, você faz exatamente da mesma maneira no construtor e nos provedores desse componente que você adiciona YOUR_SERVICE_PROVIDER. Sim, todos os componentes obterão a mesma instância apenas adicionando-a nos provedores.
Joel Almeida
No momento em que um desenvolvedor usa isso, eles devem se perguntar "por que estou usando o Angular, se não vou usar os mecanismos que o Angular fornece para essa situação?" A prestação do serviço no nível do aplicativo deve ser suficiente para todas as situações no contexto do Angular.
RyNo 18/08/16
1
+1 Embora esta seja uma maneira de criar serviços únicos que serve muito bem como uma maneira de criar um multiton serviço simplesmente mudando a instancepropriedade em um mapa de valores-chave de instâncias
Precastic
1
@RyNo Eu posso imaginar um aplicativo que não exija um serviço para todas as rotas ou um módulo reutilizável que queira uma instância estática e queira usar a mesma instância com outros módulos que a utilizem. Talvez algo que crie uma conexão de soquete da web com o servidor e processe mensagens. Talvez apenas algumas rotas no aplicativo desejem usá-lo, portanto, não é necessário criar uma instância de serviço e uma conexão de soquete da Web quando o aplicativo for iniciado, se não for necessário. Você pode programar isso para que os componentes 'iniciem' o serviço em qualquer lugar em que for usado, mas os singletons sob demanda seriam úteis.
Jason Goemaat
16

A sintaxe foi alterada. Verifique este link

Dependências são singletons no escopo de um injetor. No exemplo abaixo, uma única instância HeroService é compartilhada entre o HeroesComponent e seus filhos HeroListComponent.

Etapa 1. Crie uma classe singleton com o decorador @Injectable

@Injectable()
export class HeroService {
  getHeroes() { return HEROES;  }
}

Etapa 2. Injetar no construtor

export class HeroListComponent { 
  constructor(heroService: HeroService) {
    this.heroes = heroService.getHeroes();
  }

Etapa 3. Registre o provedor

@NgModule({
  imports: [
    BrowserModule,
    FormsModule,
    routing,
    HttpModule,
    JsonpModule
  ],
  declarations: [
    AppComponent,
    HeroesComponent,
    routedComponents
  ],
  providers: [
    HeroService
  ],
  bootstrap: [
    AppComponent
  ]
})
export class AppModule { }
Manish Jain
fonte
e se minha Injectableturma não for um serviço e apenas contiver staticcadeias de caracteres para uso global?
Syed Ali Taqi
2
como este provedores: [{fornecem: 'API_URL', useValue: ' coolapi.com '}]
Whisher
7

Adicionar @Injectabledecorador ao Serviço e registrá-lo como um provedor no Módulo Raiz o tornará um singleton.

bresleveloper
fonte
Apenas me diga se estou entendendo. Se eu fizer o que você disse, ok, será um singleton. Se, além disso, o serviço também for um provedor em outro módulo, não será mais um singleton, certo? Por causa da hierarquia.
Heringer
1
E não registre o provedor no decorador @Component das páginas.
Laura
@Laura. Ainda o importo em componentes que realmente usam o serviço?
Mark
@ Mark Sim, você precisa importá-lo e, em seguida, você só precisa declará-lo da constructorseguinte maneira: import { SomeService } from '../../services/some/some'; @Component({ selector: 'page-selector', templateUrl: 'page.html', }) export class SomePage { constructor( public someService: SomeService ) { }
Laura
6

isso parece estar funcionando bem para mim

@Injectable()
export class MyStaticService {
  static instance: MyStaticService;

  constructor() {
    return MyStaticService.instance = MyStaticService.instance || this;
  }
}
Capitão Harlock
fonte
8
Eu chamaria isso de anti-padrão Angular2. Forneça o serviço corretamente e o Angular2 sempre injeta a mesma instância. Veja também stackoverflow.com/questions/12755539/…
Günter Zöchbauer
3
@ Günter Zöchbauer, poderia dar alguns conselhos sobre "Fornecer o serviço corretamente e o Angular2 sempre injeta a mesma instância." ? Porque não é claro e não consegui encontrar nenhuma informação útil pesquisando no Google.
nowiko 5/08/16
Acabei de publicar esta resposta que possa ajudar com a sua pergunta stackoverflow.com/a/38781447/217408 (ver também o link lá)
Günter Zöchbauer
2
Isto é perfeito. Você deve usar a injeção de dependência dos próprios angulares, mas não há mal nenhum em usar esse padrão para ter certeza absoluta de que seu serviço é um singleton quando você espera que seja. Potencialmente economiza muito tempo procurando bugs apenas porque você injeta o mesmo serviço em dois lugares diferentes.
PaulMolloy
Eu usei esse padrão para determinar que o problema que estava enfrentando era por causa do serviço não ser Singleton
hr-tis
5

Aqui está um exemplo de trabalho com o Angular versão 2.3. Basta chamar o construtor do serviço da maneira como este construtor (private _userService: UserService). E criará um singleton para o aplicativo.

user.service.ts

import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Rx';
import { Subject }    from 'rxjs/Subject';
import { User } from '../object/user';


@Injectable()
export class UserService {
    private userChangedSource;
    public observableEvents;
    loggedUser:User;

    constructor() {
       this.userChangedSource = new Subject<any>();
       this.observableEvents = this.userChangedSource.asObservable();
    }

    userLoggedIn(user:User) {
        this.loggedUser = user;
        this.userChangedSource.next(user);
    }

    ...
}

app.component.ts

import { Component } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { UserService } from '../service/user.service';
import { User } from '../object/user';

@Component({
    selector: 'myApp',
    templateUrl: './app.component.html'
})
export class AppComponent implements OnInit {
    loggedUser:User;

    constructor(private _userService:UserService) { 
        this._userService.observableEvents.subscribe(user => {
                this.loggedUser = user;
                console.log("event triggered");
        });
    }
    ...
}
David Dehghan
fonte
4

A singleton serviceé um serviço para o qual existe apenas uma instância em um aplicativo.

Existem (2) maneiras de fornecer um serviço singleton para seu aplicativo.

  1. use a providedInpropriedade ou

  2. forneça o módulo diretamente no AppModuleaplicativo

Usando o fornecido

A partir do Angular 6.0, a maneira preferida de criar um serviço singleton é configurá-lo providedIncomo root no @Injectable()decorador do serviço . Isso indica à Angular para fornecer o serviço na raiz do aplicativo.

import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root',
})
export class UserService {
}

Matriz de provedores NgModule

Em aplicativos criados com versões Angular anteriores à 6.0, os serviços são matrizes de provedores NgModule registradas da seguinte maneira:

@NgModule({
  ...
  providers: [UserService],
  ...
})

Se essa NgModulefosse a raiz AppModule, o UserService seria um singleton e disponível em todo o aplicativo. Embora você possa vê-lo codificado dessa maneira, é preferível usar a providedInpropriedade do @Injectable()decorador no próprio serviço a partir do Angular 6.0, pois ele torna seus serviços tremendos em árvore.

AbolfazlR
fonte
3

Você pode usar useValueem provedores

import { MyService } from './my.service';

@NgModule({
...
  providers: [ { provide: MyService, useValue: new MyService() } ],
...
})
Roman Rhrn Nesterov
fonte
5
useValuenão está relacionado ao singleton. Usevalue é apenas para passar um valor em vez de a Type( useClass) que DI chama newou useFactoryonde é passada uma função que retorna o valor quando chamado por DI. O DI angular mantém uma única instância por provedor automaticamente. Forneça apenas uma vez e você terá um singleton. Desculpe, eu tenho que reduzir a votação porque são apenas informações inválidas: - /
Günter Zöchbauer
3

No Angular @ 6, você pode ter providedInum Injectable.

@Injectable({
  providedIn: 'root'
})
export class UserService {

}

Confira os documentos aqui

Existem duas maneiras de tornar um serviço um singleton no Angular:

  1. Declare que o serviço deve ser fornecido na raiz do aplicativo.
  2. Inclua o serviço no AppModule ou em um módulo importado apenas pelo AppModule.

A partir do Angular 6.0, a maneira preferida de criar um serviço singleton é especificar no serviço que ele deve ser fornecido na raiz do aplicativo. Isso é feito configurando a opção fornecidoIn como root no decorador @Injectable do serviço:

sabithpocker
fonte
Isso é bom, mas você também pode ter problemas inesperados com as variáveis ​​que não estão lá e que podem ser resolvidas declarando alguns itens public static.
cjbarth
2

Apenas declare seu serviço como provedor apenas em app.module.ts.

Ele fez o trabalho para mim.

providers: [Topic1Service,Topic2Service,...,TopicNService],

instancie-o usando um parâmetro privado do construtor:

constructor(private topicService: TopicService) { }

ou, se o serviço for usado em html, a opção -prod reivindicará:

Property 'topicService' is private and only accessible within class 'SomeComponent'.

adicione um membro para o seu serviço e preencha-o com a instância recebida no construtor:

export class SomeComponent {
  topicService: TopicService;
  constructor(private topicService: TopicService) { 
    this.topicService= topicService;
  }
}
user1767316
fonte
0
  1. Se você deseja tornar o serviço único no nível do aplicativo, defina-o em app.module.ts

    Provedores: [MyApplicationService] (você também pode definir o mesmo no módulo filho para torná-lo específico)

    • Não adicione este serviço no provedor que cria uma instância para esse componente que quebra o conceito de singleton, apenas injeta através do construtor.
  2. Se você deseja definir o serviço singleton no serviço de criação no nível do componente, adicione esse serviço em app.module.ts e adicione na matriz de provedores o componente específico, conforme mostrado no snipet abaixo.

    @Component ({seletor: 'app-root', templateUrl: './test.component.html', styleUrls: ['./test.component.scss'], provedores: [TestMyService]})

  3. O Angular 6 fornece uma nova maneira de adicionar serviço no nível do aplicativo. Em vez de adicionar uma classe de serviço à matriz de provedores [] no AppModule, você pode definir a seguinte configuração em @Injectable ():

    @Injectable ({fornecidoIn: 'root'}) classe de exportação MyService {...}

A "nova sintaxe" oferece uma vantagem: os serviços podem ser carregados preguiçosamente pelo Angular (nos bastidores) e o código redundante pode ser removido automaticamente. Isso pode levar a um melhor desempenho e velocidade de carregamento - embora isso realmente atinja apenas serviços e aplicativos maiores em geral.

Vijay Barot
fonte
0

Além das excelentes respostas acima, pode haver algo que está faltando se as coisas no seu singleton ainda não estiverem se comportando como um singleton. Corri para o problema ao chamar uma função pública no singleton e descobrir que ele estava usando as variáveis ​​erradas. Acontece que o problema era que thisnão é garantido que ele esteja vinculado ao singleton para quaisquer funções públicas no singleton. Isso pode ser corrigido seguindo os conselhos aqui , da seguinte maneira:

@Injectable({
  providedIn: 'root',
})
export class SubscriptableService {
  public serviceRequested: Subject<ServiceArgs>;
  public onServiceRequested$: Observable<ServiceArgs>;

  constructor() {
    this.serviceRequested = new Subject<ServiceArgs>();
    this.onServiceRequested$ = this.serviceRequested.asObservable();

    // save context so the singleton pattern is respected
    this.requestService = this.requestService.bind(this);
  }

  public requestService(arg: ServiceArgs) {
    this.serviceRequested.next(arg);
  }
}

Como alternativa, você pode simplesmente declarar os membros da classe como em public staticvez de public, então o contexto não importa, mas você terá que acessá-los como em SubscriptableService.onServiceRequested$vez de usar injeção de dependência e acessá-los via this.subscriptableService.onServiceRequested$.

cjbarth
fonte
0

Serviços para pais e filhos

Eu estava tendo problemas com um serviço pai e seu filho usando instâncias diferentes. Para forçar uma instância a ser usada, você pode alias o pai com referência ao filho nos fornecedores do módulo de aplicativo. O pai não poderá acessar as propriedades do filho, mas a mesma instância será usada para ambos os serviços. https://angular.io/guide/dependency-injection-providers#aleased-class-providers

app.module.ts

providers: [
  ChildService,
  // Alias ParentService w/ reference to ChildService
  { provide: ParentService, useExisting: ChildService}
]

Serviços usados ​​por componentes fora do escopo dos módulos de aplicativos

Ao criar uma biblioteca que consiste em um componente e um serviço, deparei-me com um problema em que duas instâncias seriam criadas. Um pelo meu projeto Angular e outro pelo componente dentro da minha biblioteca. O conserto:

my-outside.component.ts

@Component({...})
export class MyOutsideComponent {
  @Input() serviceInstance: MyOutsideService;
  ...
}

my-inside.component.ts

  constructor(public myService: MyOutsideService) { }

my-inside.component.hmtl

<app-my-outside [serviceInstance]="myService"></app-my-outside>
Scott VandenToorn
fonte
Você quis responder sua própria pergunta? Nesse caso, você pode separar a resposta em uma Resposta formal no StackOverflow, recortando / colando no campo Resposta após a postagem da pergunta.
Ryanwebjackson