After days of attempting to grasp 'the Angular paradigm', I still find myself struggling to understand something about services that are not singletons. It seems impossible for me to pass a runtime-determined value to a service constructor, as I am only able to use hardcoded values.
Let's say I want to develop a service that maintains a persistent connection to various remote APIs for different objects - such as on/off switches. How can I make the component dynamically provide the unique connection URL to the service at runtime, without knowing it during compile-time? The URL is provided to the component upon instantiation, but I'm stuck on how to relay it further.
//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: 'needs to be replaced by baseUrl[1,2,or 3]'},
{provide: 'name', useValue: 'needs to be 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;
//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;
}
}
I find myself in a paradox where the chicken and egg scenario plays out. Angular indicates the component relies on the service while I need the service to be instantiated/constructed with values passed by the component instance. How do I change the following part of toggler-control.component.ts to utilize variables instead?
providers: [
TogglerCommsService,
{provide: 'url', useValue: 'needs to be replaced by baseUrl[1,2,or 3]'},
{provide: 'name', useValue: 'needs to be Fan1, Lamp1 or Valve1'}
]
There must be an obvious and essential aspect that I'm just overlooking.