Tips for effectively exchanging information among angular services

Currently, I am in the process of refactoring a complex and extensive form that primarily operates within a controller. To streamline the process, I have started segregating related functions into separate modules or services. However, I am grappling with the issue of managing form data effectively without cluttering the controller or having to pass an overwhelming number of arguments to service functions.

My existing approach involves setting variables on the service, then attempting to access this saved data in other services. Unfortunately, this method doesn't seem to be yielding the desired results as injecting the service into another creates a new instance void of any saved values.

To showcase this methodology, here is a plunker demonstrating my implementation: https://plnkr.co/edit/vyKtlXk8Swwf7xmoCJ4q

let app = angular.module('myApp', []);

app.service('productService', [function() {
  let products = [
    { name: 'foo', value: 'foo' },
    { name: 'bar', value: 'bar' },
    { name: 'baz', value: 'baz' }
  ];

  let selectedProduct = null;

  this.getAvailableProducts = function() {
    return products;
  }

  this.setSelectedProduct = function(product) {
    selectedProduct = product;
  }
}]);
app.service('storeService', ['productService', function(productService) {
  let states = [
    { name: 'SC', value: 'SC' },
    { name: 'GA', value: 'GA' },
    { name: 'LA', value: 'LA' }
  ];

  let selectedState = '';

  this.getAvailableStates = function() {
    return states;
  }

  this.setSelectedState = function(state) {
    selectedState = state;
  }

  this.getPrice = function() {
    // This console.log will always return undefined.
    // productService.selectedProduct is not available.
    console.log(productService.selectedProduct);
    if (productService.selectedProduct == "foo" && selectedState == 'SC') {
      return 10;
    }
    return 5;
  }
}]);
app.controller('myController', function($scope, storeService, productService) {
  $scope.name = '';
  $scope.deliveryState = '';
  $scope.selectedProduct = null;
  $scope.price = 0;

  $scope.productSelection = productService.getAvailableProducts();
  $scope.states = storeService.getAvailableStates();

  $scope.productChanged = function() {
    productService.setSelectedProduct($scope.selectedProduct);
    $scope.price = storeService.getPrice();
  }

  $scope.stateChanged = function() {
    storeService.setSelectedState($scope.deliveryState);
    $scope.price = storeService.getPrice();
  }
});

I am trying to avoid something like this:

$scope.price = storeService.getPrice(
     $scope.state,
     $scope.selectedProduct,
     $scope.servicePackage,
     $scope.serviceFee,
     $scope.shippingSelection,
     // etc…
);

Perhaps I should consider creating a third service to manage and transfer all data between the other services?

Or maybe it would be better to maintain all the data solely within the controller?

Answer №1

Why is the variable on the injected service returning undefined?

When using the let declaration, a private variable is created.

To access the variable, add a getter method:

app.service('productService', [function() {
  let products = [
    { name: 'foo', value: 'foo' },
    { name: 'bar', value: 'bar' },
    { name: 'baz', value: 'baz' }
  ];

  let selectedProduct = null;

  this.getAvailableProducts = function() {
    return products;
  }

  this.setSelectedProduct = function(product) {
    selectedProduct = product;
  }

  //ADD getter

  this.getSelectedProduct = function() {
      return selectedProduct;
  }

}]);

Then utilize the getter method:

this.getPrice = function() {
    // This console.log will always return undefined.
    // productService.selectedProduct is not available.
    console.log(productService.selectedProduct);
     ̶i̶f̶ ̶(̶p̶r̶o̶d̶u̶c̶t̶S̶e̶r̶v̶i̶c̶e̶.̶s̶e̶l̶e̶c̶t̶e̶d̶P̶r̶o̶d̶u̶c̶t̶ ̶=̶=̶ ̶"̶f̶o̶o̶"̶ ̶&̶&̶ ̶s̶e̶l̶e̶c̶t̶e̶d̶S̶t̶a̶t̶e̶ ̶=̶=̶ ̶'̶S̶C̶'̶)̶ ̶{̶
     if (productService.getSelectedProduct() == "foo" && selectedState == 'SC') {
      return 10;
    }
    return 5;
 }

Update

Is there a better way for services to communicate rather than directly setting variables?

I want to avoid clutter like this:

$scope.price = storeService.getPrice(
     $scope.state,
     $scope.selectedProduct,
     $scope.servicePackage,
     $scope.serviceFee,
     $scope.shippingSelection,
     // etc…
);

One solution is to use an object as an argument for multiple options:

$scope.options = {};

$scope.price = storeService.getPrice(
     $scope.selectedProduct,
     $scope.options
);

The form can then populate the options object directly:

<select ng-model="options.state">
    <option ng-repeat="state in states">{{ state.name }}</option>
</select><br>

<select ng-model="options.serviceFee">
    <option ng-repeat="fee in feeList">{{ fee.name }}</option>
</select><br>

<!-- //etc... -->

Directly setting variables in one service before computing in another can lead to unwanted coupling that makes code harder to understand and maintain. It's best practice to provide all necessary information to the pricing service in a cohesive manner.

Answer №2

Avoid injecting $scope in your AngularJs development as it is considered outdated. Instead, focus on utilizing components or the controllerAs syntax for a more modern approach.

Controllers should primarily handle data communication between services and your view.

Services are responsible for providing data functions such as retrieving a product or creating a new one, while the controller should manage actions like:

$ctrl = this;
$ctrl.product = productService.new();

or

$ctrl.product = productService.get(productId);

In your view, bind to properties of the product like:

<input name="name" ng-model="$ctrl.product.name">

When saving a product, submit the entire object back to the service through:

<form name="productForm" ng-submit="productForm.$valid && $ctrl.save()">

And within the controller:

$ctrl.save = function() {
  productService.save($ctrl.product);
}

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

Modifying the chart width in Chart.js: A step-by-step guide

After creating a chart using Chart Js, I encountered an issue where the chart did not fit within the specified width. Adjusting the attributes of canvas proved to be ineffective, specifically with regards to the width attribute. Despite changing the value, ...

Display a list of items using ReactJS by mapping through an array of objects and rendering based on

How can I render a set of <div> </div> based on the items in an object without having to specify their names? In the code snippet below, I want the display of WorkObjectID and WorkObjectVal to be dynamic rather than static. If I include TempOb ...

Is there a way to update the color of a button once the correct answer is clicked? I'm specifically looking to implement this feature in PHP using CodeIgniter

Within my interface, I have multiple rows containing unique buttons. Upon clicking a button, the system verifies if it corresponds to the correct answer in that specific row. The functionality of validating the responses is already functional. However, I a ...

Selenium unfortunately does not fully function with JavascriptExecutor

When I attempt to input text using JavascriptExecutor, the code snippet below is what I use: private void inputWorkDescription(WebDriver driver, int rawNumber) throws IOException, GeneralSecurityException { if (!getWorkDescriptionFromSheets(rawNum ...

Attempting to create a JavaScript function that will show a JSON array when the next button is clicked

I am facing an issue with displaying a JSON array based on specific start and end indices. Even though I managed to display the entire array, the first button click does not seem to work as expected. Upon clicking the first button, an error message "this.d ...

Utilizing Node.js to retrieve streams in conjunction with OpenAI

I am currently working on setting up a node/react setup to stream results from openai. I came across an example project that accomplishes this using next.js. While I have successfully made the API call and received the results as expected, the challenge li ...

Enclose this within Stencil.js components

Is there a more efficient way to utilize a nested "this" in a Stencil.js component? Currently, I find myself following this approach: render() { let thisNested = this; return <Host> {this.images ? this.imagesArray.map(fu ...

I am puzzled as to why my DataGrid MUI component is not functioning properly

I am taking my first steps with MUI, the MaterialUI React Component library. After following the installation instructions in the documentation, I am now attempting to integrate the DataGrid component into my React project. My goal is to replicate the fir ...

The current layout of the div is hindering the ability to switch from vertical scrolling to horizontal scrolling

I'm currently utilizing this scroll converter tool to transform vertical scrolling into horizontal scrolling. However, despite correct script inclusion and initialization, the conversion is not working as expected. It seems like there might be an issu ...

In search of a highly efficient webservices tutorial that provides comprehensive instructions, yielding successful outcomes

I've reached a point of extreme frustration where I just want to break things, metaphorically speaking, of course. For the past week, I've been trying to learn how to create a web service using C# (whether it's WCF or ASMX, I don't rea ...

Data Malfunction Leads to Failure of Ajax POST Request in Client-Side Operation

I am facing an issue where my Ajax POST calls are failing when I attempt to assign complex JavaScript objects to the [data] key/value pair after using JSON.stringify() for serialization. Could anyone advise on what additional ajax call configuration is re ...

Assistance with Ajax for content loading

Greetings, I am encountering an issue with the following code snippet (located in a js file named ajax.js) $(function(){ $("#loading").hide(); $("ul#nav a").click(function(){ page = "content/"+$(this).attr('href') ...

Sporadic UnhandledPromiseRejectionWarning surfacing while utilizing sinon

Upon inspection, it appears that the objects failApiClient and explicitFailApiClient should be of the same type. When logging them, they seem to have identical outputs: console.log(failApiClient) // { getObjects: [Function: getObjects] } console.log(expli ...

Refresh the page and witness the magical transformation as the div's background-image stylish

After browsing the internet, I came across a JavaScript script that claims to change the background-image of a div every time the page refreshes. Surprisingly, it's not functioning as expected. If anyone can provide assistance, I would greatly appreci ...

Retrieve the encrypted URL

I'm facing an issue with extracting parameters from an encrypted URL. When using the queryparams function, it only retrieves a portion of the URL after being decrypted. For instance, consider this example URL: http://localhost:4200/househouse? MGRjYjQ ...

Instead of using a string in the createTextNode() function, consider utilizing a variable

I am attempting to use JavaScript to add another list item to an unordered list. I want the list item to display dynamic content based on a pre-existing variable. While I can successfully append a list item by using a string, things go awry when I try to i ...

When the state of the grandparent component is updated, the React list element vanishes in the grandchild component. Caution: It is important for each child in a list to have a unique

In my development project, I've crafted a functional component that is part of the sidebar. This component consists of 3 unique elements. ProductFilters - serves as the primary list component, fetching potential data filters from the server and offer ...

Error: The property "id" cannot be destructured from req.params because it is not defined

I am attempting to retrieve a user profile from a database and return it as a JSON object when the profile URL (localhost:3000/Profile/1) is accessed. However, I am encountering an error: TypeError: Cannot destructure property id of req.params as it is un ...

Docusaurus font loading specifically optimized for body text, excluding headings

I've added the following CSS code to my Docusaurus custom stylesheet: @import url("https://fonts.googleapis.com/css2?family=Montserrat:ital,wght@0,400;0,500;0,600;0,700;1,400;1,500;1,600;1,700&display=swap"); :root { --ifm-color- ...

Facing an issue with webpack-dev-server where it displays a blank screen

Hello! I am currently in the process of creating my first MERN stack application, using Webpack as the build tool. The main objective is to have Express serving the APIs for the app and webpack-dev-server handling the static content (from my static directo ...