Guide on creating a service in AngularJS that calls a function from a module utilizing the base module

I am currently developing a versatile service intended for use across various independent websites. However, there are certain instances where the service needs to execute different code based on the specific website it is implemented in. My goal is to maintain the separate per-website code from the core service.

Below is an example of the design I am aiming for (although it is not functioning as expected):

var baseModule = angular.module('baseModule', []);
baseModule.service('baseService', function() {
    this.func = function() {
        return ["first", 
                /* TODO somehow get from appropriate 
                service in website module */ 
                "FIXME", 
                "end"];
    };
});

var website1 = angular.module('website1', ['baseModule']);
website1.service('website1Service', function() {
    this.someCustomValue = function() { 
        // Note that while this is a constant value, in 
        // the real app it will be more complex,
        // so replacing this service with a constant provider won't work.
        return "someValue"; 
    }
});

// TODO : somehow link website1Service.someCustomValue to baseService

var website2 = angular.module('website2', ['baseModule']);
website2.service('website2Service', function() {
    this.anotherValue = function() { return "anotherValue"; }
});
// TODO : somehow link website2Service.anotherValue to baseService

// Testing code:

function makeTestController(expected) {
    return ['$scope', 'baseService', function($scope, baseService) {
      var result = baseService.func();
  
      if (angular.equals(result, expected)) {
          $scope.outcome = "Test Passed!";
      } else {
          $scope.outcome = 'Test failed...\n' + 
            "Expected: " + angular.toJson(expected) + '\n' +
            "But got : " + angular.toJson(result);
      }
    }];
  }

website1.controller('TestController1', 
                    makeTestController(['first', 'someValue', 'end']));
website2.controller('TestController2', 
                    makeTestController(['first', 'anotherValue', 'end']));

// since this test uses multiple angular apps, bootstrap them manually.
angular.bootstrap(document.getElementById('website1'), ['website1']);
angular.bootstrap(document.getElementById('website2'), ['website2']);
    
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<h3>Website 1</h3>
<div id='website1'>
  <div ng-controller='TestController1'>
    <pre>{{outcome}}</pre>
  </div>
</div>
<div id='website2'>
  <h3>Website 2</h3>
  <div ng-controller='TestController2'>
    <pre>{{outcome}}</pre>
  </div>
</div>

I have explored several potential solutions to address this issue, but none seem ideal.

The most straightforward approach would involve replacing the baseService service with a provider and allowing it to be configured within each module. This is commonly used for configuring services in other modules. However, I encountered limitations when attempting to access the website1Service and website2Service within the provider functions, as services cannot be accessed in provider functions during the configuration phase according to the documentation:

During application bootstrap, before Angular goes off creating all services, it configures and instantiates all providers. We call this the configuration phase of the application life-cycle. During this phase, services aren't accessible because they haven't been created yet.

Another workaround involves using angular.injector to locate the appropriate service. However, the documentation for angular.injector suggests that this method is primarily reserved for interacting with third-party libraries rather than internal implementations.

Lastly, I could introduce a dependency on a non-existent service (e.g., "baseServiceActions") in baseModule, requiring a service with that name to be implemented in website1 and website2. The dependency injection should then manage the connections when utilizing baseService. However, this unconventional approach may lead to confusing error messages if the baseServiceActions module is missing in a new website implementing the baseModule.

Is there a more effective strategy to achieve this? If so, is it possible to modify the provided sample code to ensure that all tests pass without altering the testing logic?

Answer №1

After some trial and error, I came up with a solid solution to the problem at hand. I decided to create a service called "<serviceName>Settings", where I implemented a setup function. This setup function is then invoked within a module run block of the specific module in which I intend to utilize it. Additionally, I included a validate method within the service to verify that the settings are properly configured and provide a user-friendly error message if they are not. This approach effectively resolved all the issues I encountered with previous solutions.

Here is an example showcasing how this solution would address my initial problem:

var baseModule = angular.module('baseModule', []);
baseModule.service('baseService', ['baseServiceSettings', function(baseServiceSettings) {
    baseServiceSettings.validate();
    this.func = function() {
        return ["first", 
                baseServiceSettings.getValue(),
                "end"];
    };
}]);
baseModule.service('baseServiceSettings', function() {
   this.setup = function(getter) {
      this.getValue = getter;
   };
  this.validate = function() {
    if (!this.getValue) {
      throw "baseServiceSettings not setup! Run baseServiceSettings.setup in a module run block to fix";
    }
  };
});


var website1 = angular.module('website1', ['baseModule']);
website1.run(['baseServiceSettings', 'website1Service', function(baseServiceSettings, website1Service) {
  baseServiceSettings.setup(website1Service.someCustomValue);
}]);


website1.service('website1Service', function() {
    this.someCustomValue = function() { 
        // Note that while this is a constant value, in 
        // the real app it will be more complex,
        // so replacing this service with a constant provider won't work.
        return "someValue"; 
    }
});


var website2 = angular.module('website2', ['baseModule']);
website2.service('website2Service', function() {
    this.anotherValue = function() { return "anotherValue"; }
});
website2.run(['baseServiceSettings', 'website2Service', function(baseServiceSettings, website2Service) {
  baseServiceSettings.setup(website2Service.anotherValue);
}]);

// Testing code:

function makeTestController(expected) {
    return ['$scope', 'baseService', function($scope, baseService) {
      var result = baseService.func();
  
      if (angular.equals(result, expected)) {
          $scope.outcome = "Test Passed!";
      } else {
          $scope.outcome = 'Test failed...\n' + 
            "Expected: " + angular.toJson(expected) + '\n' +
            "But got : " + angular.toJson(result);
      }
    }];
  }

website1.controller('TestController1', 
                    makeTestController(['first', 'someValue', 'end']));
website2.controller('TestController2', 
                    makeTestController(['first', 'anotherValue', 'end']));

// since this test uses multiple angular apps, bootstrap them manually.
angular.bootstrap(document.getElementById('website1'), ['website1']);
angular.bootstrap(document.getElementById('website2'), ['website2']);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<h3>Website 1</h3>
<div id='website1'>
  <div ng-controller='TestController1'>
    <pre>{{outcome}}</pre>
  </div>
</div>
<div id='website2'>
  <h3>Website 2</h3>
  <div ng-controller='TestController2'>
    <pre>{{outcome}}</pre>
  </div>
</div>

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

Can you send JSON data and redirect simultaneously using Express?

In my application, there is a registration feature that involves sending a confirmation email to the user. I am looking to achieve a similar outcome as shown below: return res.status(200).redirect('/').json({ message: 'Successfully confir ...

What is the best way to activate an event using jQuery?

Currently, I am developing a web page where a modal window should open upon the user clicking on a specific radio button. To achieve this functionality through my jQuery code, I am trying to simulate the action of the user clicking on the radio button by: ...

Troubleshooting AJAX Problems in ASP.NET and C#

I am currently working on implementing a WebMethod call in my C# code behind using AJAX. I have a Bootstrap Modal that should be displayed when a linkbutton is clicked, triggering the execution of the WebMethod through AJAX to populate a table in the modal ...

The functionality of Jquery UI is not compatible with version 1.12

Incorporating jQuery UI into my current project has presented some challenges. Both the jquery-ui.min.css and jquery-ui.min.js files are version 1.12, so I opted for the latest jQuery version, jquery-3.2.1.min.js. Specifically, I decided to test the datep ...

JavaScript and PHP are successfully displaying a success message despite the data not being saved to the database

I recently added a new feature to my website where users can submit a form using Javascript without having to reload or refresh the page. This allows for a seamless experience and displays success messages instantly. However, being a newcomer to Javascript ...

Is your blockui overlay failing to cover the entire page?

I have implemented blockui to display a "Wait ... loading" popup on my webpage. It is mostly working fine, but I am facing a small issue where the overlay does not cover the entire width of the scroll window when I scroll right (although it covers the full ...

Issuing a straightforward GET request results in a significant delay in receiving a response

I have a simple app running on Node.js, Handlebars, and Express. The main page features a single button that triggers an asynchronous GET request when clicked, causing a console.log message to display. Initially, the first click on the Submit button shows ...

The async/await syntax can be finicky at times and may not always

Using Vue, I want to implement async/await to sequence my functions A and B. Result is set to false by default. mounted() { this.A(); this.B(); } async A() { this.result = await this.$api... } async B() { if(this.result) { let data = awa ...

Unable to trigger JavaScript function from ASP.NET MVC HTML view

I am encountering an issue with my ASP.NET MVC project where I am attempting to call a JavaScript function to record a user's selection from a listbox. Despite duplicating the JavaScript function in multiple places, it is still not being found. What c ...

Angular: Assigning a key from one variable to a value in another

I am currently facing a challenge with rendering a form on a page using ng-repeat, where the data for this form is dynamically fetched from a request. Within this data, there is a nested array called "categories" which contains IDs. I want to display the n ...

Ways to implement a single AJAX function for multiple buttons

I need to call the same AJAX function for multiple buttons. Please assist with the code provided below. This particular code will create buttons and upon clicking on them, it displays details... please assist with resolving this issue. The code generated ...

Angular2 - How to track or listen for (click) events on dynamically inserted HTML elements

I'm trying to inject a string with a dynamically retrieved (click) event into an Angular2 template. Since this string is fetched from the back-end after the DOM is loaded, Angular doesn't recognize the injected event. Here's an example of t ...

I recently implemented a delete function in my code that successfully removes a row, but now I am looking to also delete the corresponding data from localStorage in JavaScript

I have successfully implemented a delete function in JavaScript that deletes rows, but I also want to remove the data from local storage. This way, when a user reloads the page, the deleted row will not appear. The goal is to delete the data from local s ...

Unable to fetch JSON file from the local server

I'm trying to develop a server API using Koa. This server will have a single API endpoint, /api/data, which should read a local json file and display its content in the browser when a user accesses localhost:3000/api/data. const Koa = require('k ...

The modal form vanishes without any action when the form is clicked outside

Everything was working fine with the form submission until I turned it into a modal using Bootstrap. Now, when the form is rendered in the modal, users can tab and type without any issues. However, if they click on any element within the modal (including t ...

Looking to invoke a div element on a new line using JavaScript and AJAX?

How can I call div from JavaScript without them stacking on top of each other? Here is the code for page 1: <script> function jvbab(content) { var request = $.ajax({ type: "get", u ...

Invoking the HTML method from a WCF service

Currently, I am utilizing a callback in my WCF service to receive raw frames from the camera. I am currently working on developing an HTML application that will showcase these frames. However, my current approach involves using a button click event in HTM ...

Accessing API using Next.js 14

I am facing an issue with the following code which is written in next.js. The error displayed on the console is: GET http://localhost:3000/products/porducts.json 404 (not found) Additionally, I'm encountering this error: Uncaught (in promise) SyntaxE ...

Having trouble retrieving data from the database when passing input to a mongoose query using an href tag in Node.js

Welcome to My Schema const AutomationSchema=new mongoose.Schema( {EventName:String, EventDate:String, EventLocation:String, EventDetails:String } ) The Events Model const EventsModel=new mongoose.model("Events",AutomationSchema ...

Add a button to the MCE toolbar without replacing it (Plugins)

One challenge I encountered was integrating the textcolor plugin into tinyMCE. Although I managed to make it functional, I faced an issue with the toolbar configuration. I couldn't grasp how to include the plugin buttons without replacing the existing ...