Service in Angular is instantiated twice when extending the service with another service

129 views Asked by At

In my Angular project, I have a setup with multiple services. One service has some functions for which I want to restrict access, so that it can only be used from one specific other service.

To achieve this, I want to extend the service in the calling service, and use the protected access modifier to restrict access. However, upon implementing this, I see that the parent service has two instances when I start the application. How can I enforce that the parent service is instantiated only once, as singleton? Or, how should I approach this problem differently to ensure access to the functions in one service are only accesible from the calling service?

The parent service pseudo code:

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

  constructor() {
    // below is logged twice!
    console.log('new instance of ParentService created');
  }

  public someFunction() {}

  protected functionIWantToLimitAccessTo() {}

}

The child service pseudo code:

import { ParentService} from './parent.service';

@Injectable({
  providedIn: 'root'
})
export class ChildService extends ParentService {

  constructor() {
    super();  // <- this super() call is needed for class extension
  }

  public publicFunction() {
    super.functionIWantToLimitAccessTo();
  }

}

I would have expected there to be only one instance of ParentService in this setup.

3

There are 3 answers

0
joost On BEST ANSWER

Figured it out.

By providing the ParentService in app.module.ts instead of in the component itself, I can specify the class I want to inject for the ParentService. I my case, that's the ChildService.

This way I can inject the ParentService and ChildService everywhere in the application, but I have restricted the access op protected functions of the ParentService to the ChildService only.

parent service:

// No longer registered as injectable component here
// @Injectable({
//   providedIn: 'root'
// })
export abstract class ParentService {

  constructor() {
    // below is logged only once now!
    console.log('new instance of ParentService created');
  }

  public someFunction() {}

  protected functionIWantToLimitAccessTo() {}
}

child service:

import { ParentService} from './parent.service';

@Injectable({
  providedIn: 'root'
})
export class ChildService extends ParentService {

  constructor() {
    super();  // <- this super() call is needed for class extension

    // I can call protected function from ParentService from here
    super.functionIWantToLimitAccessTo();
  }

    
}

Usage in component:

import { ParentService} from './parent.service';

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

  constructor(private parentService: ParentService) {

    // I can call public functions from ParentService
    this.parentService.someFunction();

    // I can NOT call protected function from ParentService here
    this.parentService.functionIWantToLimitAccessTo(); // -> compiler error
  }
}

And in app.module.ts I have:

@NgModule({
  providers: [
    { provide: ParentService, useClass: ChildService}
  ]
})
export class AppModule { }
1
bik On

Why you don't use the power of dependencies injection? and inject the needed service to your constructor

 import { ParentService} from './parent.service';
    
    @Injectable({
      providedIn: 'root'
    })
    export class ChildService  {
    
    
      constructor(private parentService: ParentService) {
      }
    
    
      public publicFunction() {
        this.parentService.unctionIWantToLimitAccessTo();
      }
    
    }
1
Sir Leo On

The behaviour you experience is exactly how it's supposed to work. All the services that have the @Injectable annotation are managed by Angular's Dependency Injection engine. You can use a service by injecting it in the constructor of your component or service like this:

constructor(private serviceToInject: ServiceToInject) {}

Since both of your services are using this annotation, I guess that also your parent service is used like this somewhere in your application. Otherwise, it would not need the annotation.

The logging will occur twice, since you now have two different services, that both call the log-method on initialization:

  • ParentService (executes it's constructor directly)
  • ChildService of type ParentService (executes parents constructor)

When you use inheritance, you do not just reference an instance of your ParentService, you create a new service that inherits all the behaviour of the ParentService.

To solve your problem there are different approaches:

1st Approach

You can simply just get rid of the restricted methods of ParentService completely and copy them into the ChildService direclty, since you said, "it can only be used from one specific other service."

2nd Approach

Make use of Angular's DI engine and just follow the injection as described above.

Add-On to this:

By default, all the services are generated by the Angular CLI with the option {providedIn: 'root'}. When you want to restrict access, you can get rid of that parameter here and provide it to the components, that should be able to use this service manually. (See documentation for more details)