如何从组件向服务构造函数提供动态值



我几天来一直在努力弄清楚"Angular范式",但我仍然无法理解一些关于非单一服务的东西。我似乎无法向服务构造函数提供运行时确定的值(而只能提供硬编码的值(。

比方说,我想创建一个服务,为几个对象中的每个对象提供到某个远程API的持久连接,例如开关。如何使组件在运行时向服务提供唯一的连接URL,而在编译时却不知道?这个URL是在组件实例化时提供给组件的,但我不知道如何传递它

//app.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
template: `
<app-toggler-control *ngFor="let control of controls" 
name='{{ control.name }}' 
baseUrl='{{ control.baseUrl }}'
icon='{{ control.icon }}'
username = '{{ control.username }}'
password = '{{ control.password }}'
></app-toggler-control>
`
})
export class AppComponent {
title = 'testapp1';
controls:any[] = [
{
'name': 'Fan1',
'baseUrl': 'baseUrl1',
'icon': '../assets/images/Fan.png',
'username': 'authuser1',
'password': 'P@$$w0rd!'
},
{
'name': 'Lamp1',
'baseUrl': 'baseUrl2',
'icon': '../assets/images/Lamp.png',
'username': 'authuser1',
'password': 'P@$$w0rd!'
},
{
'name': 'Valve1',
'baseUrl': 'baseUrl3',
'icon': '../assets/images/Valve.png',
'username': 'authuser1',
'password': 'P@$$w0rd!'
},
]
}
//toggler-control.component.ts
import { Component, Input, OnInit } from '@angular/core';
import { TogglerCommsService } from '../toggler-comms.service'
@Component({
selector: 'app-toggler-control',
template: `
<button style="background-color:{{this.currentState==true?'green':'red'}};">
<img (click)="this.toggleState()" src="{{ this.icon }}" width="50px">{{ this.name }}
</button>
`,
providers: [
TogglerCommsService,
{provide: 'url', useValue: 'wishes it was the baseUrl[1,2,or 3]'},  // <== obviously not right
{provide: 'name', useValue: 'wishes it was Fan1, Lamp1 or Valve1'}
]
})
export class TogglerControlComponent implements OnInit {
@Input() name:string = '';
@Input() baseUrl:string = '';
@Input() icon:string = '';
@Input() username:string = '';
@Input() password:string = '';
currentState!:boolean;

constructor(private togglerComms:TogglerCommsService) { }
ngOnInit(): void {
console.log('init for: ', this.name);
this.togglerComms.getState().subscribe((val)=>{this.currentState=val;});
}
toggleState(): void {
this.currentState = !this.currentState;
this.togglerComms.setState(this.currentState);
}
}
//toggler-comms.service.ts
import { Inject, Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';
import { delay } from 'rxjs/operators';
@Injectable({
providedIn: 'any'
})
export class TogglerCommsService {
controlName:string = '';
remoteEndpoint:string = '';
mockState:boolean = Math.random() < 0.5;  //random boolean
//I want to provide the URL upon construction/instantiation
constructor(@Inject('url') url:string, @Inject('name') name:string) { 
console.log("Connecting to ",url);
this.remoteEndpoint = url;
this.controlName = name;
}
getState():Observable<boolean> {
console.log('Querying ' + this.remoteEndpoint + ' for state of ' + this.controlName + ' control.');
return of(this.mockState).pipe(delay(1000));
}
setState(newState:boolean) {
console.log('Updating ' + this.remoteEndpoint + ' with desired state of ' + this.controlName + ' control (' + (newState === true ? 'on':'off') + ').')
this.mockState = newState;
}
}

我陷入了每一只鸡和鸡蛋都需要另一只才能存在的困境。Angular表示,当我需要使用组件实例提供的值实例化/构造服务时,组件依赖于服务。如何将toggler-control.component.ts的以下部分替换为使用变量的部分?

providers: [
TogglerCommsService,
{provide: 'url', useValue: 'wishes it was the baseUrl[1,2,or 3]'},  // <== obviously not right
{provide: 'name', useValue: 'wishes it was Fan1, Lamp1 or Valve1'}
]

有一些明显而根本的东西,我只是没有看到。

您可以使用普通方法设置任何内容——它可能不是花哨的或smth,但有效:

ngOnInit(): void {
this.togglerComms.init(...whatever...);
}

当您为每个这样的组件编写providers: [ TogglerCommsService ]时,会创建新的服务实例,因此每个组件都有自己的服务,并且可以使用它做任何事情

如果同时拥有多个组件,则会创建多个服务。https://stackblitz.com/edit/angular-6e6ry6?file=src%2Fapp%2Fapp.component.ts

这就像在组件中有字段一样,但Angular为您注入依赖项,为您运行OnInit、OnDestroy,并且可以从子组件访问此服务。

正如我现在所理解的,虽然您可以使用另一个服务向服务的构造函数提供值,但服务必须在使用它的组件之前构造,并且所述服务的令牌被注入到组件的构造中。因此,组件无法向服务提供构造参数。相反,正如Petr所暗示的那样,它似乎只是在消费组件的OnInit((函数中设置服务实例中的值。

ngOnInit(): void {
console.log('init for: ', this.name);
this.togglerComms.controlName = this.name;
this.togglerComms.remoteEndpoint = this.baseUrl;
this.togglerComms.getState().subscribe((val)=>{this.currentState=val;});
}

删除服务构造函数中的所有注入内容,使ToggleCommonsService构造函数为空。

//I want to provide the URL upon construction/instantiation <== too bad!
constructor() { //@Inject('url') url:string, @Inject('name') name:string) { 
//console.log("Connecting to ",url);
//this.remoteEndpoint = url;
//this.controlName = name;
}

并从ToggleControlComponent规范中删除这些额外的提供程序。

providers: [
TogglerCommsService,
//{provide: 'url', useValue: 'wishes it was the baseUrl[1,2,or 3]'}, 
//{provide: 'name', useValue: 'wishes it was Fan1, Lamp1 or Valve1'}
]

最后,我们通过在组件的元数据中的providers数组中列出服务,确保每个组件都获得自己的服务实例(而不是所有组件共享一个(,正如我们在上面看到的那样,这是正确的。这被称为";沙箱;在本次讨论中:角度DI导向

最新更新