How to write a service constructor that requires p

2019-03-12 10:28发布

问题:

I have a component that declares the MetricsService service. This service requires both http module plus two strings that defines the host ans 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>

回答1:

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.



回答2:

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.



回答3:

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 ) { }
}