Search code examples
angulartypescriptangular7

Angular Service not a global singleton


I was under the impression that a Angular Service is a Singleton, but have recently discovered, or atleast it seems, that the service is only a singleton to the instance of the component and the component's children.

Consider the following code:

const CACHE_REFRESH_INTERVAL = 1800000; //30 minutes
const CACHE_SIZE = 1;

const COMPANY_CACHE_KEY = 'company_cache_key';

@Injectable({
  providedIn: "root"
})
export class CompanyService {

  private companyCache: Map<string, Observable<any>> = new Map();

  constructor(private httpClient: HttpClient) { }

  public getAllCompanies(): Observable<CompanyViewModel[]> {
    if (!this.companyCache[COMPANY_CACHE_KEY]) {
      const timer$ = timer(0, CACHE_REFRESH_INTERVAL);
      this.companyCache[COMPANY_CACHE_KEY] = timer$.pipe(
        switchMap(_ => this.getAllCompaniesHttpRequest()),
        shareReplay(CACHE_SIZE),
        refCount(),
        catchError(err => this.companyCache[COMPANY_CACHE_KEY] = undefined) //prevents storing error in cache
      );
    }
    return this.companyCache[COMPANY_CACHE_KEY];
  }

  private getAllCompaniesHttpRequest(): Observable<CompanyViewModel[]> {
    return this.httpClient.get<CompanyViewModel[]>(environment.endpoints.company.getAllCompanies());
  }
}

Let's say, ComponentA gets this service injected through the component's instructor. When ComponentA calls the CompanyService.getAllCompanies() method, the results will be stored in companyCache. Now the user navigates to a new URL, ComponentA get destroyed, as well as the instance of the service. Navigate back to ComponentA, and a new instance of the service is injected, and companyCache is once again empty.

Is my assumption correct? Can I make a service a singleton accross the entire application?


Solution

  • Services are a singleton at the level of the app.

    Take this simple stackblitz demo

    I have a trivial service that sets a random number from the constructor.

    @Injectable({
      providedIn: 'root'
    })
    export class Service {
      constructor() {
        this.rnd = Math.random();
      }
    
      private rnd: number;
    
      getRand(): number {
        return this.rnd;
      }
    }
    

    I have a component structure like this with the service being injected into 2 "child" components (not actual children, but that's the pattern of navigation I have set up):

    |- home
    
    |- component a
    |--  inject service
    
    |- component b
    |--  inject service
    

    When navigating from home to component a, it will display the random number that was set in the constructor of the service.

    When I navigate back to home (check the console to see that component a was destroyed), and then navigate to component b, it will display the random number that was set in the constructor of the service.

    For the lifetime of the app, the random number remains the same. To me this proves that even when a service isn't "in scope", it is still in memory.

    Not to mention that the docs talk about this on the very first line:

    A singleton service is a service for which only one instance exists in an app.