I'm attempting to rewrite a large and complex form that is doing everything in a controller. I started by separating related functions into their own modules/services. I don't understand how I am supposed to maintain the form data without crowding up the controller or requiring an excessive amount of arguments to be passed to the service function.
My current approach is to set variables on the service, then use that service in other services and try to access the saved data. This doesn't seem to be working. I think this is because injecting the service into another creates a new instance without all the saved values.
Here is a plunker that summarizes this approach: 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…
);
Should I be creating a third service that sets and gets all the data on the other services?
Should I just maintain all the data on the controller?
why do I get
undefined
when accessing a variable on the injected service?
The let
declaration creates a private variable.
Add a getter for the variable:
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;
}
}]);
And use the getter:
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;
}
Should my services be communicating like that or is there a different, more accepted method?
I am trying to avoid something like this:
$scope.price = storeService.getPrice( $scope.state, $scope.selectedProduct, $scope.servicePackage, $scope.serviceFee, $scope.shippingSelection, // etc… );
One way to avoid this is use an object as an argument to provide multiple options:
$scope.options = {};
$scope.price = storeService.getPrice(
$scope.selectedProduct,
$scope.options
);
The form can 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... -->
The setting of a variable in one service before computing something in another service creates an undesirable coupling that makes the code difficult to understand, debug, maintain, and test.
Instead all the information needed from the controller should be provided to the pricing service in a coherent manner.