What is the best way to pass dynamic values to a service constructor from a component?

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.

Answer №1

You have the ability to configure things using standard methods - it may not be extravagant, but it gets the job done:

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

By specifying

providers: [ TogglerCommsService ]
, a new service instance is generated for each component, allowing each component to manipulate its own service as needed.

If there are multiple components simultaneously, multiple services will be created. https://stackblitz.com/edit/angular-6e6ry7?file=src%2Fapp%2Fapp.component.ts

This concept is analogous to having a field in the component itself, but Angular handles dependency injection, executes OnInit and OnDestroy functions automatically, and grants access to child components.

Answer №2

From my current understanding, when using a service to provide values to another service's constructor, the service is constructed before the component utilizing it. The service's token is then injected into the component's construction. This means that the component cannot pass construction arguments to the service directly. Instead, the recommended approach, as hinted by Petr, is to set the values in the service instance within the OnInit() function of the consuming component.

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

The advice here is to remove all injection-related code from the service constructor and leave it empty, like in the TogglerCommsService constructor below.

  //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;
  }

Also, eliminate any extra providers from the TogglerControlComponent specification as shown in the snippet below.

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

Lastly, ensure each component receives its own instance of the service by including the service in the providers array of the component metadata. This is known as "sandboxing," as explained in the Angular DI Guide.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Utilizing icons for form-control-feedback in Bootstrap 4

I am struggling to achieve the desired display using Bootstrap 4 form-control feedback styling. The goal is to include an icon inside the input field along with an error message. My current setup includes Angular 5.2.9, Bootstrap 4, and Fontawesome icons. ...

Improve your code by avoiding the use of multiple ngIf statements

Looking for ways to shorten my code for better readability. I have multiple ngIf statements with various conditions for numbering lists (e.g., 1, 1.1, 1.1.1) Here is a snippet of the code from my template: <span *ngIf="...">{{...}}.</span> .. ...

Is there a method to incorporate absolute paths in SCSS while working with Vite?

Currently, I am experimenting with React + Vite as webpack seems to be sluggish for me. My goal is to create a project starter, but I am facing difficulties in getting SCSS files to use absolute paths. Despite including vite-tsconfig-paths in my vite.confi ...

Navigate the nested route of a child page starting from the main Root component

I am facing an issue with enabling nesting routes on the BarcodeScannerComponent component. I have attempted the following method, but it does not seem to work. The main challenge lies in accessing the nested route within the root component, which is app.c ...

Highcharts - Customize Pie Chart Colors for Every Slice

I'm working on an angular app that includes highcharts. Specifically, I am dealing with a pie chart where each slice needs to be colored based on a predefined list of colors. The challenge is that the pie chart is limited to 10 slices, and I need to a ...

What steps should I take to ensure the successful function of the App Routing system in this scenario?

After creating an Angular App, I encountered a challenge in one of my services. When I call the http.post method and subscribe to it, I aim to redirect to the previous page with a parameter (e.g., "http://localhost:3000/profile/aRandomName"). Unfortunately ...

Is it possible to modify the default behavior of a sensitive region within a button?

I created a calculator application in React and overall, it's working fine, however... I've noticed that when I hold a click longer, it only registers as a click if the mouse was pressed down and released on the button itself. Although I unders ...

What is the best way to configure distinct proxy and backend API URLs for development and production environments?

My goal is to seamlessly link an Angular / C# Web Api project on localhost while developing. For this, I typically use the following URL in the Angular app: http://localhost:5000/api/something However, this setup does not work once deployed. Ideally, I w ...

Is there a method to globally import "typings" in Visual Code without having to make changes to every JS file?

Is there a method to streamline the process of inputting reference paths for typings in Visual Studio Code without requiring manual typing? Perhaps by utilizing a configuration file that directs to all typings within the project, eliminating the need to ...

Google+ login is functional on browsers but not on smartphones when using Ionic 2

Successfully logged in using Google+ on my app. It works fine when checked on Chrome/Explorer, but on my smartphone or Android emulator, after pressing "login with Google+", it shows a successful login message but stays on the same login page without progr ...

I am encountering a problem with HttpClient Angular POST when trying to communicate with Google FCM Server. How can I

I have encountered an issue while trying to send FCM messages using Angular HttpRequest, even though I am able to do so successfully via a POST and HTTP v1 Firebase API through Postman: Error Below are the imports I am using: import { Injectable } from & ...

Encountering a ReferenceError while attempting to implement logic on a newly created page

I've been experimenting with building a website using the Fresh framework. My goal was to add a simple drop-down feature for a button within a navigation bar, but I'm struggling to figure out where to place the necessary code. I attempted creatin ...

I'm having trouble inputting text into my applications using React.js and TypeScript

I am encountering an issue where I am unable to enter text in the input fields even though my code seems correct. Can anyone help me figure out what might be causing this problem? Below is the code snippet that I am referring to: const Login: SFC<LoginP ...

Retrieving Data from Repeated Component in Angular 6

Need Help with Reading Values from Repeating Control in Angular 6 I am struggling to retrieve the value of a form field in the TS file. Can someone please assist me with this? This section contains repeating blocks where you can click "add" and it will g ...

Sorting an array of objects in TypeScript may result in a type error

My data includes various categories, ages, and countries... const data = [ { category: 'Fish', age: 10, country: 'United Kingdom' }, { category: 'Fish', age: 9, country: 'United Kingdom' }, { category: ...

In TypeScript version 2.4.1, the fontWeight property encounters an error where a value of type 'number' cannot be assigned to the types of '"inherit", 400'

When attempting to set the fontWeight property in TypeScript, I encounter the following error: Types of property 'test' are incompatible. Type '{ fontWeight: number; }' is not assignable to type 'Partial<CSSProperties>&a ...

Exploring ngTemplateOutlet and ngtemplate in complex nested forms with Angular

I am currently working on generating reactive forms from JSON data. My goal is to create a formGroup and its nested forms based on the provided data, and automatically generate an HTML form using templates. I have outlined the steps I took with sample data ...

An issue (TC2322) has been encountered during the compilation of the Angular4 application

Encountered an issue while running the "ng build" command: ERROR in src/app/model/rest.datasource.ts(34,5): error TS2322: Type 'Observable<Product | Order | Product[] | Order[]>' is not assignable to type 'Observable<Product[]>& ...

Display Google font as SVG path but encapsulate within a promise

I'm facing an issue with the following script, where it performs an async operation. My goal is to wrap it in a promise, but I'm unsure about the right approach. static convertGoogleFontToSVG(): Promise<string> { const url = 'htt ...

Having trouble customizing the toolbar on ngx-quill? Quill seems to be having trouble importing modules

UPDATE: I jumped ship when I discovered that PrimeNg had a quill implementation, and since I was already using PrimeNg, I switched over. Initially had some issues, but upgrading to angular 7 and ngrx 7 beta resolved them. https://www.primefaces.org/primeng ...