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

Using JQuery with special characters in attributes

Within my jQuery script, I am required to dynamically translate certain content. As a part of this function, I have the following code: $('#password').attr('placeholder', 'Contraseña'); In this code snippet, I att ...

Reveal/Conceal footer upon vertical scrolling

I am attempting to achieve the following goals: Display the div element when the scrolling position is greater than 20 Apply a fadeOut effect after a certain delay Prevent the fadeOut effect when hovering over the sticky footer This is my implementation ...

Cypress: Unable to properly stub API with cy.intercept()

Whenever I utilize the cy.intercept() function, the API fails to stub. cy.intercept("GET", `${API}farm/list`, { body: { statusCode: 200, message: "Request successful", result: seededFarmList, }, }); The way I import the fixture file is as ...

Utilizing jQuery to fetch the source value of an image when the closest radio button is selected

On my website, I have a collection of divs that display color swatches in thumbnail size images. What I want to achieve is updating the main product image when a user clicks on a radio button by fetching the source value of the image inside the label eleme ...

Sending compiled parameters to directives for ng-click

I am attempting to implement ng-repeat in Angular version 1.6.x The keys returned by getToggle are ['card1', 'card2'] <li ng-repeat="fi in getToggleKeys()"> <a ng-click="actions.toggleMode($event, '{{fi}}&ap ...

Having difficulty maintaining the consistent size of the MathJax math font in relation to the surrounding text

Issue I am currently using MathJax to display mathematical equations on my webpage: The problem I am facing is that I want the math font to appear larger than the surrounding text, as depicted in the image above. However, when viewed on mobile devices, t ...

Variety of properties determined by a "type" prop, expanding variations based on a value from the interface

I am trying to enhance a type based on a value from the main interface. If the type == multiline, it will have a specific interface, and if the type == icon, it will have a different type. import React, { memo, useCallback, ReactNode } from 'react&apo ...

Determine whether the regex pattern is positioned at the start of the string or following a designated character

I am working with a regex pattern and need to verify if it matches at the start of a string, after a new line, or after a white space. Additionally, I want to ensure that the pattern also matches the end of the string, after a new line, or a white space. ...

Build a custom loader in Next JS that leverages Webpack to dynamically map URL paths to specific components

I am looking to implement a custom loader in Next.js that leverages Webpack through the next.config.js configuration file. This loader should route Blog.js for the /blog route and Tutorial.js for the /tutorial route. The MDX data is stored in the pages/ d ...

Avoiding uib-popover from getting clipped by uib-carousel

I have a small widget with a popover that works fine when placed inside a div, but gets clipped when inserted into a carousel. Here's an example to illustrate what I mean: (The top image shows the widget in a div without clipping) The second image d ...

When the value is blank, do not include the class attribute when appending

http://jsbin.com/guvixara/1/edit I have a situation where I am dynamically inserting a button... $(".confirm-add-button").on("click", function() { var $ctrl = $('<button/>').attr({ class: $('.newbtnclassname').val()}).html($(& ...

New to AngularJS: Issues with ng-controller implementation

I am currently delving into the fundamentals of AngularJS and attempting to grasp them. I recently attempted to create a basic MVC app, but encountered an issue with the controller not functioning properly. The file can locate the angular lib without any i ...

Converting Json arrays to HTML tables using the window.chrome.webview.addEventListener feature with a Server Delphi Application

My Delphi application is sending Json data to the embedded Edge browser, but Javascript is reporting a parser error. https://i.sstatic.net/Z2BUq.png https://i.sstatic.net/0IIzJ.png When I modify the value for Sender.DefaultInterface.PostWebMessageAsJson ...

Generating DOM elements at specific intervals using Jquery

I am looking to dynamically create 1 div container every x seconds, and repeat this process n times. To achieve this, I have started with the following code: $(document).ready(function() { for (var i = 0; i < 5; i++) { createEle(i); } }); f ...

Who is the father of Bootstrap Popover?

I'm currently facing an issue with getting a popover to be placed within a specific element. As of now, the popover is being inserted directly into the <body>, but I require it to be relative to other elements. I have been unable to locate a met ...

Error: The ng-click directive is encountering a parsing syntax error. The token 'Object' is unexpected and is causing the error, it is expected to be enclosed in

When a user clicks on a point on a Google map, I am conducting reverse geocoding in the following manner: geocoder.geocode({'location': latlng}, function(results, status) { if (status === google.maps.GeocoderStatus.OK) { ...

Several DIVs with the same class can have varying CSS values

I am looking to modify the left-margin value of various separate DIVs using JavaScript. The challenge is: I only want to use a single className, and I want the margin to increase by 100px for each instance of the class. This way, instead of all the DIVs ...

Adjust the size of an array based on the specified index

I have an array defined as... let myArray = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13] ...along with a starting index let start = 2 and an ending index let end = 5. I want to resize the array by taking elements from start to end, for example: start ...

Node and browser compatible JavaScript logging library for effective logging experience across platforms

Our team is utilizing Visionmedias debug library, as it seamlessly functions on both browsers and Node.js environments. We are in search of a more reliable alternative to debug that offers the same level of functionality (error, warn, info, etc) for both ...

Is there a way to generate a modal list of values by utilizing bootstrap and angular, while incorporating spring boot as the backend technology?

I'm working on developing a generic "List of Values" feature, which will be a searchable modal containing a natural identifier and description. I have successfully built an AngularJS application in Spring Boot to accomplish this task, but unfortunatel ...