Stéphane de Luca
Stéphane de Luca

Reputation: 13611

How to write a service that requires constructor parameters?

I have a component that declares the MetricsService service. This service requires both HttpModule plus two strings that defines the host and the auth key to use.

The metrics service is as follows:

    @Injectable()
    export class MetricsService {
        constructor(
            private http: Http,
            public wsAuthKey: string,
            public wsHost: string
            ) {
            this.wsAuthKey  = wsAuthKey || "blahblahblahblahblahblahblahblah=";
            this.wsHost     = wsHost    || "https://preprod-admin.myservice.ws";
        }

The component that uses it is written as follows:

    export class DatavizComponent implements OnInit, OnChanges {
        constructor(
            private zms: MetricsService,
        ) { 
        }

My question is how should I write the component constructor so that the whole thing works, including passing the host and key (but not passing the http)?

Note : The code as currently written does not compile.

To be more precise, I would expect the component to provided app-depending data something like this:

     export class DatavizComponent implements OnInit, OnChanges {
            constructor(
                private zms = MetricsService("http://myhost.com", "mykey"),
            ) { 
            }

But if this works, how to pass http?

UPDATE AFTER PROPOSED SOLUTION:

    export class MetricsService {
    
        constructor(
            private http: Http,
            @Inject('wsAuthKey') @Optional() public wsAuthKey?: string,
            @Inject('wsHost') @Optional() public wsHost?: string
            ) {
            this.wsAuthKey  = wsAuthKey || "blahblah=";
            this.wsHost     = wsHost    || "https://preprod-admin.host.ws";
    
    
            console.log("MetricsService constructor="
                + " wsAuthKey="+this.wsAuthKey
                + ", wsHost="+this.wsHost
            );
    
        }

In the component:

    @Component({
        selector:    'dataviz-offers-volumes',
        templateUrl: 'app/dataviz.component.html',
        styleUrls:  ['app/dataviz.component.css'],
        encapsulation: ViewEncapsulation.None,
        providers: [
            {provide: 'wsAuthKey',  useValue: 'abc'}, 
            {provide: 'wsHost',     useValue: 'efg'}, 
        ],
    })
    export class DatavizComponent implements OnInit, OnChanges {
      
        @ViewChild('chart') private chartContainer: ElementRef;
        @Input() private graphId:string;
        @Input() private wsAuthKey:string;
        @Input() private wsHost:string;
        @Input() private maxSamples=12;
    
        constructor(
            private zms: MetricsService
        ) { 
        }

In the constructor, the log are as follows (value are not passed):

    MetricsService constructor= wsAuthKey=blahblah=, wsHost=https://preprod-admin.host.ws

where it should show 'abc' and 'efg'.

But I wonder if there is not an issue with the component that use dataviz componenet. In this component, the following information have been passed:

    @Input() private wsAuthKey:string;
    @Input() private wsHost:string;

As I would like the tag to optionally preset the host and key:

                    <h1>dataviz volume</h1>
                    <div class="chartContainer left" title="Simultaneous offers via dataviz directive">
                        <dataviz-offers-volumes 
                            id="dataviz-volumes1"
                            [graphId]="graphId"
                            [wsAuthKey]="'myauthkey'"
                            [wsHost]="'http://myhost.com'"
                            [maxSamples]="123"
                        >
                        </dataviz-offers-volumes>
                    </div>

Upvotes: 48

Views: 70328

Answers (3)

Gus
Gus

Reputation: 7555

From the official docs: https://angular.io/guide/dependency-injection-in-action#injectiontoken

Use an @Optional decorator in the constructor:

export class MyService {
  constructor( @Optional() public var: type = value ) { }
}

Upvotes: 3

G&#252;nter Z&#246;chbauer
G&#252;nter Z&#246;chbauer

Reputation: 658235

You can make the parameters optional by adding @Optional() (DI) and ? (TypeScript), and @Inject(somekey) for primitive values that are not supported as provider keys

@Injectable()
export class MetricsService {
    constructor(
        private http: Http,
        @Inject('wsAuthKey') @Optional() public wsAuthKey?: string,
        @Inject('wsHost') @Optional() public wsHost?: string
        ) {
        this.wsAuthKey  = wsAuthKey || "blahblahblahblahblahblahblahblah=";
        this.wsHost     = wsHost    || "https://preprod-admin.myservice.ws";
    }
providers: [
  {provide: 'wsAuthKey', useValue: 'abc'}, 
  {provide: 'wsHost', useValue: 'efg'}, 
]

If they are provided, they are passed, otherwise they are ignored, but DI still can inject the MetricsService.

Upvotes: 54

Estus Flask
Estus Flask

Reputation: 223259

This is a common recipe that is described in this question in particular. It should be a service that holds the configuration:

@Injectable()
export class MetricsConfig {
  wsAuthKey = "blahblahblahblahblahblahblahblah=";
  wsHost = "https://preprod-admin.myservice.ws";
}

@Injectable()
export class MetricsService {
    constructor(
        private http: Http,
        metricsConfig: MetricsConfig
    ) {
        this.wsAuthKey  = metricsConfig.wsAuthKey;
        this.wsHost     = metricsConfig.wsHost;
    }
}

In the case when it needs to changed, it can be overridden or extended for entire module or for particular component:

@Component(
  ...
  { provide: MetricsConfig, useClass: class ExtendedMetricsConfig { ... } }
)
export class DatavizComponent ...

There's no real need to make MetricsConfig a class in this case. It can be an OpaqueToken value provider as well. But a class can be conveniently extended, it is easier to inject and already provides an interface for typing.

Upvotes: 21

Related Questions