Creating a service in AngularJS 1.8 with ES6 modules that acts as a bridge to a class based API interface

As I continue to enhance a codebase that originally consisted of a mix of different versions of AngularJs and some unstructured code utilizing various versions of a software API, I encounter an interesting quirk. It appears that this API is accessible through $window.external in the context of AngularJS loaded application pages - quite peculiar.

During my pre-ES6 phase with AngularJs 1.8, I have three services (referred to as someAPIget, someAPIset, and someAPIforms) that interact with the software's API. The structure looks something like this:

// someAPIget.service.js 
;(function () {
  var APIget = function ($window, helperfunctions) {
  function someFunc (param) {
    // Performing operations using $window.external.someExternalFunc
    return doSomethingWith(param)
    }
  return {
    someFunc: someFunc
    }
  }
  angular.module('someAPIModule').factory('someAPIget', ['$window', 'helperfunctions', someAPIget])
})()

I also had a service and module located one level above these services, with someAPIModule declared as a dependency. This setup aggregated the functions and passed them through under a unified name, as shown below:

// apiinterface.service.js 
;(function () {
  var APIget = 'someAPIget'
  var APIset = 'someAPIset'
  var APIforms = 'someAPIforms'
  var APIInterface = function (APIget, APIset, APIforms) {
    return {
      someFunc: APIget.someFunc,
      someSettingFunc: APIset.someSettingFunc,
      someFormLoadingFunc: APIforms.someFormLoadingFunc
    }
  }
  angular.module('APIInterface').factory('APIInterface', [APIget, APIset, APIforms, APIInterface])
})()

This architecture allowed me to call these functions across other controllers and services by utilizing APIInterface.someFunc(etc). While this approach was effective and modular, it would enable us to seamlessly transition to a new software provider without overhauling everything except for the interface logic.

However, in my pursuit of transitioning to TypeScript and ES6, aiming to leverage import/export functionalities and facilitate command line accessibility, I decided to refactor someAPIget into a class:

// someAPIget.service.ts
export class someAPIget {
  private readonly $window
  private readonly helperfunctions
  static $inject = ['$window', 'helperfunctions']

  constructor ($window, helperfunctions) {
    this.$window = $window
    this.helperfunctions = helperfunctions
    }

  someFunc (param) {
    // Implementing actions involving this.$window.external.someExternalFunc
    return doSomethingWith(param)
    }
  }
}

angular
  .module('someAPImodule')
  .service('someAPIget', ['$window', 'helperfunctions', someAPIget])

Initially, it seemed like the transition went smoothly (my tests were passing after cleaning up the TypeScript compilation issues), but upon integration into the live app, I encountered a perplexing issue - this.$window was not defined. Oddly enough, directly invoking someAPIget.someFunc(param) bypassed the problem, while using APIInterface.someFunc(param) caused the issue to resurface. Rewriting thousands of lines to replace references to APIInterface wasn't ideal, and it defeated the purpose of encapsulating the functions within an interface.

I experimented with converting APIInterface into a class, setting getters for each function that returned the imported function, yet $window remained undefined. Through console.log statements, I confirmed that within someFunc itself, the property this.$window was defined, as well as inside the getter in APIInterface. However, when attempting to access it via APIInterface, it appeared to skip executing the constructor on someAPIget, even when utilizing $onInit() for pertinent calls.

I sense that I am overlooking a simple solution here. Is there a correct method to aggregate and rename these functions for widespread program usage? How can I alias them appropriately to ensure post-construction usability?

Edit: I've attempted multiple configurations, such as having someAPIget and APIInterface as both factories and services, and calling APIInterface within the .run() block of the overarching app.module.ts file - unfortunately, none yielded success (the latter simply shifting the undefined error location).

Another edit: I also explored using static in this scenario, which clearly proved erroneous; however, it at least triggered the informative VSCode error highlight stating "Property 'someProp' is used before its initialization.ts(2729)".

How should a property initialized in the constructor be properly utilized? Is there a way to compel AngularJS to execute the constructor before accessing the class members? Any insights or guidance would be greatly appreciated.

Answer №1

Although I can't say for sure that my solution is the optimal one, I have managed to find a working approach that I am sharing here in the hopes that it may benefit others.

What I ended up doing was invoking each imported function within a class method on the APIInterface class with the same name, as shown below:

// apiinterface.service.ts 
// Modify these lines to switch to a different API service for all functions.
const APIget = 'someAPIget'
const APIset = 'someAPIset'
const APIforms = 'someAPIforms'

export class APIInterface {
  private readonly APIget
  private readonly APIset
  private readonly APIforms

  constructor (APIget, APIset, APIforms) {
    this.APIget = APIget
    this.APIset = APIset
    this.APIforms = APIforms
  }

  someFunc(param: string): string {
    return this.APIget.someFunc(param)
  }
  someSettingFunc(param: string): string {
    return this.APIset.someSettingFunc(param)
  }
  someFormLoadingFunc(param: string): string {
    return this.APIforms.someFormLoadingFunc(param)
  }
    
}

angular
  .module('APIInterface')
  .factory('APIInterface', [APIget, APIset, APIforms, APIInterface])

While it may not be the most elegant solution, it does get the job done.

Update: I've since transitioned to using Angular12 instead of AngularJS, so there are some slight differences. Recently, I've been considering utilizing the public-api.ts file generated by Angular12 to achieve the same result (i.e.,

export { someAPIget as APIget } from './filename'
). However, I haven't yet tested this out as it would still require either consolidating my functions or rewriting the consuming code to accommodate one of three potential solutions. It would be beneficial to avoid duplicating function signatures and doc strings. This remains an ongoing challenge that I hope to address more effectively; I will provide further updates if I discover a more solid solution.

Answer №2

It is not recommended to add the array of dependencies as the second parameter in .factory(). Instead, use the $inject array to set dependencies:

class TipService {
    tips = {};

    /**
    * @param {ng.IHttpService} $http
    * @param {ng.ITimeoutService} $timeout
    */
    constructor ($http, $timeout) {
        this.httpService = $http;
        this.timeoutService = $timeout;

        this.fetch();
    }

    async show(eid, element) {
        // ...
    }

    hide() {
        // ...
    }

    async fetch() {
        // ...
    }
}

TipService.$inject = ['$http', '$timeout'];

angular.module('module').service('TipService', TipService);

Components, Controllers and Directives works too. In directives you have to add extra compile method:

class TrackDirective {
    this.restrict = 'A';
    this.scope = false;

    constructor() {}

    compile() {
        // to use static link function as class method
        return this.link.bind(this);
    }

    link(scope, element, attrs) {
        // ...
    }
}

TrackDirective.$inject = [];

angular.module('module').directive('track', TrackDirective);

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 props in makeStyles for React styling

I have a component that looks like this: const MyComponent = (props) => { const classes = useStyles(props); return ( <div className={classes.divBackground} backgroundImageLink={props.product?.image} sx={{ position: "r ...

Having trouble loading items in a <select> tag with Jquery?

Dealing with a seemingly simple issue, I am struggling to figure out how to load items into a select using jQuery. Working with the Materialize theme available at: The code snippet in question: <div class="input-field col s12"> <s ...

Unusual Interactions between Angular and X3D Technologies

There is an unusual behavior in the x3d element inserted into an Angular (version 4) component that I have observed. The structure of my Angular project is as follows: x3d_and_angular/ app/ home/ home.component.css hom ...

Webpage Text Animation

This is the visual representation of my webpage: https://i.stack.imgur.com/tYq34.png I am seeking to implement a uniform animation effect for the text "Carlos Qiano" and "Lorem Ipsum", similar to the link below: https://i.stack.imgur.com/XVnBS.jpg Note: ...

Minor Chrome compatibility problems with CSS alignment

As someone who is new to stackoverflow, I've always found it to be a valuable resource for answers. I've had success building HTML 5 banner ads using GSAP (Greensock Animation Platform) in the past, but now I'm facing a CSS alignment issue t ...

Create an Android application using Node.js

While developing my mobile application, I am utilizing html5 with node.js to create a chat box for users connected on the same wireless network. However, I encounter an issue when coding on my desktop using node.js software. How can I overcome this challen ...

Module '../../third_party/github.com/chalk/supports-color' not found in the directory

Within my tutoring-frontend-main project folder There is a file named package.json { "name": "app-frontend", "version": "0.0.0", "license": "MIT", "scripts": { "ng": "ng", "start": "ng serve", "build": "ng build --prod", "test": "n ...

Is it possible to determine whether a path leads to a directory or a file?

Is it possible to distinguish between a file and a directory in a given path? I need to log the directory and file separately, and then convert them into a JSON object. const testFolder = './data/'; fs.readdir(testFolder, (err, files) => { ...

Dynamic Visualizations with D3.js: Live Charts for Real-Time

This is my first time using D3.js and I am attempting to create a basic sparkline graph. The graph should have time on the X-axis and numbers up to 1000 on the Y-axis. I have a web socket server that sends random numbers up to 1000 to clients. My goal is t ...

What is the best way to set up a server-sent-events broadcast feature in totaljs?

Let's imagine this situation: Client1 and Client2 are currently in session1 Meanwhile, Client3 and Client4 are part of session2 My aim now is to send event "a" to all clients in session1 exclusively. I came across this example: https://github ...

Populate a dropdown list with array elements using Javascript in ReactJS

I am encountering an issue while trying to populate a dropdown with data from the 'options' array. The error message states that it cannot read property appendChild of null. Using 'document.getElementByClassName' instead of document.ge ...

Guide to verifying the presence of cookies by name in the browser and granting access to the specific page accordingly

In the process of developing an authorization system with Express, Node, and MySQL, I decided to utilize JWT tokens for user authorization. After successfully storing the JWT token in cookies, my next step is to verify if the token exists in the cookie b ...

Developing a transparent "cutout" within a colored container using CSS in React Native (Layout design for a QR code scanner)

I'm currently utilizing react-native-camera for QR scanning, which is functioning properly. However, I want to implement a white screen with opacity above the camera, with a blank square in the middle to indicate where the user should scan the QR code ...

Tips for updating the CSS properties of the "html" element

html { width:100%; } Looking to dynamically update the CSS of the html tag upon button click using JavaScript. The goal is to modify the existing HTML CSS code as shown below, but achieving this dynamically with a script. Is it possible? html { w ...

Is there a discrepancy in the value between data and computed properties in Vue components?

Upon reviewing my code, I have noticed that the value shown in the data of the component does not match the desired value. The code snippet is provided below: View.component('xxx-item',{ props:['item'], template:`xxxx`, computed: ...

Instructions on how to automatically navigate to a different tab upon clicking an <a> element on a separate webpage, and subsequently display the designated tab on that

I have a button on my home page that, when clicked, should open a specific tab section on another page. The button is located on one page and the tabs are on a different page. On the second page, I have multiple tabs but will only mention one here, which ...

Encountering the error "tsx is not defined" during a Jest test in a React/TypeScript project

I'm currently working on implementing Jest tests within a React project that has enforced TypeScript settings. In a simple test.tsx file located in the test folder, I have the following code: import React from 'react'; describe('Test& ...

Incorporating Javascript into a <script> tag within PHP - a step-by-step guide

I am trying to integrate the following code into a PHP file: if (contains($current_url, $bad_urls_2)) { echo '<script> $('body :not(script,sup)').contents().filter(function() { return this.nodeType === 3; ...

Avoid unintended double-tapping on mobile devices

On my main page, I have a button that triggers an overlay with item details, including buttons like one that reveals a phone number. An issue arises when a user accidentally double-clicks the main page button. The first click is processed on the main page ...

Issues with utilizing Fetch API and JSON Data

I'm encountering some difficulties while trying to interact with my json file. I am using the fetch API to retrieve my json file but, unfortunately, when I log the response to the console, I don't see any data returned. Instead, what appears is a ...