Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular: Extending Services and Passing Parameters

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?

like image 528
Dennis Smolek Avatar asked Jan 19 '16 21:01

Dennis Smolek


People also ask

How does Angular pass a service as a parameter?

You can pass parameters to Angular 10 (and previous versions) services, using the @Inject decorator to create injection tokens. It allows you to pass parameters to the service via the Angular dependency injector.

What is@ Injectable in Angular service?

The @Injectable() decorator defines a class as a service in Angular and allows Angular to inject it into a component as a dependency. Likewise, the @Injectable() decorator indicates that a component, class, pipe, or NgModule has a dependency on a service. The injector is the main mechanism.

How does dependency injection work in Angular?

Dependency injection (DI) is a paradigm. The way it works in Angular is through a hierarchy of injectors. A class receives its resources without having to create or know about them. Injectors receive instruction and instantiate a service depending on which one was requested.

What is Injectable Angular?

Dependency injection, or DI, is one of the fundamental concepts in Angular. DI is wired into the Angular framework and allows classes with Angular decorators, such as Components, Directives, Pipes, and Injectables, to configure dependencies that they need.


3 Answers

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();
  }
}
like image 195
Scottmas Avatar answered Oct 20 '22 20:10

Scottmas


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

like image 39
Dennis Smolek Avatar answered Oct 20 '22 22:10

Dennis Smolek


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);
   }
   ....
}
like image 5
Aaron Boteler Avatar answered Oct 20 '22 20:10

Aaron Boteler