how to extend service with dependencies in angular 2
Asked Answered
O

3

42

I have a parent service which has some dependencies like

@Injectable()
export class ParentService{
  constructor(private http:Http, private customService:CustomService){}
}

and I want to extend the service

@Injectable()
export class ChildService extends ParentService{
  constructor (){
    super(??) <= typescript now asking to enter two parameters according to ParentServie's constructor
  }
}

Edit-----------------

@Injectable()
export class ParentService{
  constructor(private http:Http, private customService:CustomService){}
  get(){this.http.get(...)}
}

@Injectable()
export class ChildService extends ParentService{
  constructor (private http:Http, private customService:CustomService){
    super(http, customService)
  }
}

Then I can use in components?

export class Cmp {
  constructor(private childService:ChildService){
    this.childService.get()
  }
}
Orling answered 29/8, 2016 at 14:12 Comment(0)
L
44

The parameters of the super class need to be repeated and passed to the super call:

@Injectable()
export class ChildService extends ParentService{
  constructor (http:Http, customService:CustomService){
    super(http, customService);
  }
}

There are some "hacks" to work around like Inheritance and dependency injection

Lewin answered 29/8, 2016 at 14:15 Comment(6)
no, the constructor misses the parameters. Angular2 DI reads from the constructor parameters what dependencies it has to pass to a component. ChildService won't get passed anything in because it doesn't have constructor parameters. super(http, customService) is invalid because http and customService are not known in the sub class.Stunk
sorry forgot to add the paramenters to child components constructor. no correct?Orling
This should work. It's similar to what I showed in my answer but now there are also overridden fields http and customService in the ChildService as well.Stunk
Hey, how aboust skipping private keyword in ChildService constructor? super() shall store them as propertiesTankoos
I can't get this to work without the ParentService not being @Injectable. Is that correct? Can't tell from the example, which seems to suggest that the ParentService can be @InjectableBrasil
I wozld expect @Injectable() on ParentService` or not to not matter, but havenßt tried.Stunk
M
24

Simply make base service... non-@Injectable()! For example, you need to supply a method to retrieve user profile in LoginService. This method will be different for different instances of LoginService, and parent class must not know anything about where from this method came: it can be lambda, it can be exported function, it can be a method of another service. To achieve this, you can declare parent service which Parent service:

// Don't annotate it with @Injectable() ! It's a simple class.
export abstract class BaseLoginService {
  constructor(
    // This won't be injected automatically, 
    // child class must call super() and provide value for this argument.
    // Note: it's private, can't be accessed outside of BaseLoginService class
    private http: HttpClient,
    // Method used to retrieve user profile
    private profileGetter: () => Observable<UserDetails>, 
    private router: Router
  ) {
    this.profileGetter().subscribe(); // Do something with this method
  }

Then extend it in child class:

// Child class must be annotated with @Injectable(),
// if you don't need to extend it further
@Injectable() 
export class LoginService extends BaseLoginService {

  constructor(
    // Note: no public/private/protected here - "http"
    // will be just an argument
    http: HttpClient, 
    private profileService: ProfileService, // This is the private field of LoginService class
    router: Router,
    // Some service used by LoginService class, parent class BaseLoginService
    // doesn't need to know about SomeOtherService
    private someOtherService: SomeOtherService
  ) {
    super(
      http,
      // Need lambda here to capture profileService instance. If
      // profileService.getUserDetailsMethod doesn't use instance
      // fields or methods, then we can simply write 
      // "profileService.getUserDetailsMethod" (without quotes, obviously).
      () => profileService.getUserDetailsMethod(), 
      router
    );
    this.someOtherService.doSomething(); // Custom initialization code
  }

Note: in providers section of module specify LoginService instead of parent BaseLoginService:

providers: [
  LoginService,

and use it in component classes:

export class LoginComponent implements OnInit {
  constructor(
    private loginService: LoginService
  ) {
  }

If you need to use parent service (e.g. in shared components which only need functionality from parent service class), then provide BaseLoginService this way:

providers: [
  {provide: BaseLoginService, useExisting: LoginService}, // For those components which need functionality from base class only
  LoginService, // Still need this one for those components which need functionality from child class
Match answered 12/5, 2018 at 17:27 Comment(2)
I know it depends, but say you have N isolated entities in your app and they have to implement common methods. In your opinion, what is the recommended directory/place to store such classes?Afraid
@AndreiGătej I would suggest to start from asking yourself: why do you have such a question? Is there a code style you have to adhere to, but this aspect is not covered by it? If you're not working on this project alone, ask your project manager. In any way, if you are using some IDE, you can rename the directory later and IDE will automatically adjust all paths / includes in all places. Personally, I stored those base classes in the separate Angular project which was the shared dependency for others.Match
G
5

If you make the DI services in the abstract class protected and omit the constructor in the subclass, then the services are available in the subclass.

Gallup answered 18/7, 2019 at 7:42 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.