There are many solutions to such problems that depend on how you think. I prefer to think that each service (or class) has a purpose and some other service is required to achieve its goal, but its goal is one, clear and small. Let's see your code in this view.
Problem 1:
GridData strong>: Grid data is stored here. MainService comes here to get the necessary data, so we add this to mainService and we use the loadRowData function to get rowData data just like you do, but in $ interval you enter mainService inside gridData, but gridData doesn't need mainService to complete your goal (get items from the server).
I solve this problem using an observer design pattern (using $ rootScope). This means that I receive a notification when the data arrives, and the mainService arrives and receives it.
grid-data.service.js :
angular.module("gridApp").service("gridDataService", ["$injector", "$interval", "$timeout", "$rootScope", function ($injector, $interval, $timeout, $rootScope) { […] $interval(function () { [..] self.newItems = [rowToAdd]; // delete this code // var gridMainService = $injector.get('gridMainService'); // gridMainService.gridOptions.api.addItems(newItems); // notify the mainService that new data has come! $rootScope.$broadcast('newGridItemAvailable');
Grid-main.service.js
angular.module("gridApp").service("gridMainService", ["$log", "$q", "gridConfigService", "gridDataService", '$rootScope', function ($log, $q, gridConfigService, gridDataService, $rootScope) { [..] // new GridData data arrive go to GridData to get it! $rootScope.$on('newGridItemAvailable', function(){ self.gridOptions.api.addItems(gridDataService.getNewItems()); }) [..]
When using a real server, the most common solution is to use promises (not an observer pattern) such as loadRowData.
Problem 2 :
gridSettingsService . This service changes the settings of mainService, therefore it requires mainService, but mainService does not care about gridSettings when someone wants to change or find out that the internal state of mainService (data, form) must communicate with the mainService interface,
So, I remove the Preferences grid from gridMainService and provide an interface only for the callback function when the Grid is ready.
Grid-main.service.js
angular.module("gridApp").service("gridMainService", ["$log", "$q", "gridConfigService", "gridDataService", '$rootScope', function ($log, $q, gridConfigService, gridDataService, $rootScope) { […] // You want a function to run onGridReady, put it here! self.loadGridOptions = function (onGridReady) { [..] self.gridOptions = { columnDefs: gridConfigService.columnDefs, rowData: gridDataService.rowData, enableSorting: true, onGridReady: onGridReady // put callback here }; return self.gridOptions; }); [..]// I delete the onGridReady function , no place for outsiders // If you want to change my state do it with the my interface
Ag-grid-controller.js :
gridMainService.loadGridOptions(gridSettingsService.applyDefaults).then(function () { vm.gridOptions = gridMainService.gridOptions; vm.showGrid = true; });
here is the full code: https://plnkr.co/edit/VRVANCXiyY8FjSfKzPna?p=preview