9

I'm having a hard time understanding how to extend services in Angular.

I have a service that connects to Firebase and does all sorts of common tasks (get, set, update, list, etc.) and instead of re-writing it for my special components I tried just extending it.

The idea was I could pass just the new part of the path but that throws an error:

Cannot resolve all parameters for 'FirebaseService'(?). Make sure that all the parameters are decorated with Inject or have valid type annotations and that 'FirebaseService' is decorated with Injectable.

The issue is in the constructor and my lack of OOP brains. I can pass other services or providers into my service but I can no longer pass simple string parameters unless I'm missing something. I tried setting properties but I don't think I'm getting the context right.

I was thinking it was an issue with the @Injectable but I'm not sure.

Here's a simplified version of what I tried first:

UPDATED TO INCLUDE PLUNKER LINKS:

Plunker for passing with parameters

Plunker for passing with constructor

@Injectable()
export class FirebaseService {
  rootPath:string = "https://mysite.firebase.com/";
  childPath:string;
  pathToUse: string;
  constructor() {
    if(this.childPath){
        this.pathToUse = this.rootPath + this.childPath;
    }else{
        this.pathToUse = this.rootPath;
    }
    console.log(this.pathToUse);
  }
}
//The in project_service.ts
@Injectable()
export class ProjectService extends FirebaseService{
    childPath:string = "projects";
    constructor(){
        super();
    }
}

I expected it to have the "projects" line attached. It doesn't, it just repeats.

So Then I tried passing through the constructor:

@Injectable()
export class FirebaseService {
  rootPath:string = "https://mysite.firebase.com";
  pathToUse: string;
  constructor(childPath:string) {
    if(childPath){
        this.pathToUse = this.rootPath + childPath;
    }else{
        this.pathToUse = this.rootPath;
    }
    console.log(this.pathToUse);
  }
}
//The in project_service.ts
@Injectable()
export class ProjectService extends FirebaseService{
    constructor(){
        super('projects');
    }
}

Which just blows everything up. I have a way around it but it seems like a total hack.

What is the correct way to pass the "projects" parameter to the parent class?

2
  • I think you're understanding of OOP is fine, it's just angular2 does some smart, but whacky things with dependency injection, so injected/injectable needs extra syntax. I would start here: blog.thoughtram.io/angular/2015/05/18/… Commented Jan 20, 2016 at 1:23
  • CH, Yeah I saw that and it totally makes sense for injecting services into each other and to components. What I want to do is pass a parameter either in the constructor or from a child to a parent. In regular ES6 classes both ways seem possible, but with the Angular DI it eats them and I'm not sure why.. Commented Jan 20, 2016 at 23:22

4 Answers 4

10

So after some good work by CH Buckingham I've resolved that doing it the "typical" way is impossible.

Angular2 simply takes over the constructor() function with the injector. What does work however is make an alternate "init" function that you can then pass parameters to.

@Injectable()
export class ParentService {
  root:string = "This content comes from: ";
  myString:string = "The Parent";
  resultString:string;
  constructor(){
    this.init();
  }
  init() {
    this.resultString = this.root + this.myString;
  }
}


@Injectable()
export class ChildService extends ParentService {
  constructor(){
    super();
  }
  init() {
    this.myString = "The Child";
    super.init();
  }
}

In this way you can set values on the child object or pass them through to the parent.

Plunker of this in action

Sign up to request clarification or add additional context in comments.

5 Comments

Hi @Dennis Smolek, can I ask you two questions. 1. if a component class (named Child) extends from a Parent class, how to pass parameter to the constructor in the Child class? 2. how can a child component extends from a parent component and pass parameters?
For the service: Because the injector handles all of the initiating of the services you can't pass anything dynamically in the constructor at runtime but you can make a flexible service code wise. For example, my main service has all the code to run my Firebase functions, then a child service sets its own firebase URL and inherits all the parent functions. My child service overwrites the "init" function of the parent. In my code this would be the "myString" parameter.
For the component: It depends on when you are extending it. If you want to make a button component and then want to extend it for some special function it's pretty straight forward like how I extended the service. If you want to do it at runtime (probably the easier idea) you can just set some input() values when you call for the component. If you meant parent/child nested components there are a ton of ways to pass data, eventEmitter, the parent Object, even a service..
Dennis you're plunker print an exception.
mautrok, this answer is SUPER old (in regards to ng2 anyway) so I'm sure some things are moved/broken. I'll take a look at rc.5 and see if this still applies and try to update the plunker at some point.
5

Here is another way to extend a service in angular2 which works with the DI. The child service cannot use the same parameter name for injectable services used by the parent, hence the reason for anotherHttp parameter name.

The parent service:

@Injectable()
export class GenericStore {
   ....
   constructor(public config:GenericConfig,private http:Http) {
        ....
   }
   ....
}

The child service:

const DEFAULT_CONFIG:GenericConfig = { ... };
@Injectable()
export class ConfigStore extends GenericStore {
   ....
   constructor(private anotherHttp:Http) {
        super(DEFAULT_CONFIG,anotherHttp);
   }
   ....
}

2 Comments

There is no class that extends any other class. What is DEFAULT_CONFIG?
Thanks for the catch. I fixed the child service. The DEFAULT_CONFIG is used to indicate the source of the data and pipe which could be a simple URL or a websocket URL with event name.
5

Best way IMO is to use use an abstract property. This way, the extending class will be forced to provide the requisite value.

abstract class GenericStore{

  abstract configVal: string;

  constructor(){}

  someMethod(){
    console.log('The config value is:', this.configVal);
  }
}

class UserStore extends GenericStore{

  configVal = 'A config value. Typescript will yell if not provided.'; 

  constructor(){
    super();
  }
}

Comments

2

Update: if you want to use the standard Service interface without manual instantiation and pass simple values to the constructor, then the docs recommend setting up a factory, which takes an almost silly amount of code: Dependency Injection (scroll down to Factory Providers).

//this is all Google Example Code
constructor(private logger: Logger, private useCoolFeature: boolean) {}
let heroServiceFactory = (logger: Logger, userService: UserService) => {
  return new HeroService(logger, userService.user.isSpecial);
}
let heroServiceDefinition = {
   useFactory: heroServiceFactory,
   deps: [Logger, UserService]
};
let heroServiceProvider = provide(HeroService, heroServiceDefinition);
bootstrap(AppComponent, [heroServiceProvider, Logger, UserService]);

This code below works correctly, but doesn't use the provider system for services:

//things.service.ts
@Injectable()
export class ThingService {
  public myString: string = "base";
  constructor(str: string) {
    this.myString = str;
  }
}

@Injectable()
export class Thing2Service extends ThingService {
  constructor(){
    super('I AM CHILD');
  }
}

//app.component.ts
public thingService: ThingService = new ThingService("I AM PARENT");
public thing2Service: Thing2Service = new Thing2Service();

ThingService.myString === "I AM PARENT"; //true
Thing2Service.myString === "I AM CHILD"; //true

8 Comments

This code would work for a regular Javascript object, but not an Angular 2 Service. Here is a plunker with passed properties: plnkr.co/edit/6cXqsD9M1O19BjThv61p?p=preview, and here is one with the constructor like you suggest. It blows up: plnkr.co/edit/7UtV4VEEpR02xIsBqnGR?p=preview
I'm looking at the above code in my answer running, exactly as is. So you are missing something, or have something wrong in the compile step.
Oh, I see what you're saying. I'm instantiating the services as classes, not through the injector, even though they've been decorated with @Injectable.
Yeah if I instantiate them as classes it's fine but when I try to use the injector at all it throws errors. If I just needed simple services with no other injectors I like your idea and I suppose standard classes would work fine but if I try injecting something into the class I want to extend(like a UserService) it throws the errors again.
Good looking out @CH Buckingham, I messed with what you found a bit and it indeed allows me to pass other info but it doesn't allow me to simply "extend" the service. Tons of code involved to do that which kinda defeats the idea of extending. I'm posting an answer to what I figured out.. It's not perfect but it works
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.