Exploring prototypal inheritance through Angularjs module.service设计(Note: This

Having worked with Angularjs for a few months now, I am curious about implementing efficient OOP within this framework.

Currently, I am involved in a project where I need to create instances of a "terminal" class that includes constructor properties and various methods. In regular JS, I typically use the pseudoclassical pattern to separate constructor properties from methods in the prototype chain.

Based on what I know about Angularjs, using model.service() seems like the best option as it allows me to create a new instance of the service each time it is called. However, the common implementation I have come across when defining methods looks like this:

myApp.service('helloWorldFromService', function() {
    this.sayHello = function() {
        return "Hello, World!"
    };
});

But wouldn't this create the function sayHello() every time the class is called? I am contemplating whether it would be more effective to separate the functions. For example:

myApp.service('helloWorldFromService', function() {
    this.msg= "Hello, World!";
});

helloWorldFromService.prototype.showMessage = function() {
    return this.msg;
};

In this way, the showMessage() function would only be created once in memory and shared across all instances of the services created.

If it is indeed possible (and increases code efficiency), how would one go about implementing this? (Note: the code above is just a speculation)

Thank you

Answer №1

After receiving a comment, it seems that by simply returning the constructor function for an object, you can access the prototype through the service call.

In this example scenario, clicking "Get Again" will trigger the prototype function changeHelloWorldString to update the string with the control's name. Once "Change Prototype" is clicked, the changeHelloWorldString function appends " [PROTOTYPE CHANGE]" to the string. Demonstrating that the prototype change made in the second controller affects the prototype chain for the object in both controllers when either "Get Again" button is clicked.

Refer to the code below:

angular.module('myModule2', [])
  .factory('myService', function() {
    function FactoryConstructor(thirdFunction) {
      this.helloWorldFunction = function() {
        return this.helloWorldString;
      }
      this.thirdFunction = thirdFunction;
    }
    FactoryConstructor.prototype.helloWorldString = 'Hello World';
    FactoryConstructor.prototype.changeHelloWorldString = function(newString) {
      this.helloWorldString = newString;
    };
    FactoryConstructor.prototype.changeThirdFunction = function(newFunction) {
      this.thirdFunction = newFunction;
    }
    return FactoryConstructor;
  })
  .controller('ctrl1', function($scope, myService) {
    var factoryResult = new myService(function() {
      this.helloWorldString += ' first';
    });
    $scope.hwString = factoryResult.helloWorldString;
    $scope.hwString2 = factoryResult.helloWorldFunction();
    
    $scope.getAgain = function() {
      factoryResult.changeHelloWorldString('ctrl1 String');
      factoryResult.thirdFunction();
      $scope.hwString = factoryResult.helloWorldString;
      $scope.hwString2 = factoryResult.helloWorldFunction();
    }
  })
  .controller('ctrl2', function($scope, myService) {
    var factoryResult = new myService(function() {
      this.helloWorldString += ' second';
    });
    $scope.hwString = factoryResult.helloWorldString;
    $scope.hwString2 = factoryResult.helloWorldFunction();
    
    $scope.getAgain = function() {
      factoryResult.changeHelloWorldString('ctrl2 String');
      factoryResult.thirdFunction();
      factoryResult.changeThirdFunction(function() {
        this.helloWorldString += ' third';
      });
      $scope.hwString = factoryResult.helloWorldString;
      $scope.hwString2 = factoryResult.helloWorldFunction();
    }
    
    $scope.changePrototype = function() {
      myService.prototype.changeHelloWorldString = function(newString) {
        this.helloWorldString = newString + " [PROTOTYPE CHANGE]";
      }
    }
  });
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app='myModule2'>
  <div ng-controller='ctrl1'>
    <div>{{hwString}}</div>
    <div>{{hwString2}}</div>
    <button ng-click='getAgain()'>Get again</button>
  </div>
  <div ng-controller='ctrl2'>
    <div>{{hwString}}</div>
    <div>{{hwString2}}</div>
    <button ng-click='getAgain()'>Get again</button>
    <button ng-click='changePrototype()'>Change Prototype</button>
  </div>
</div>

An additional explanation on this topic can be found in this answer. It may present a more efficient way to achieve what is demonstrated here by utilizing a provider (which both service and factory are derived from, as noted by the author).

Original post background continues below

In Angular, services act as singletons that can be injected into various parts of the application. For instance, if you implement the following code:

angular.module('myModule', [])
.service('myService', function() {
  var myService = this;
  this.helloWorldString = 'Hello World String';
  this.helloWorldFunction = function() {
    return myService.helloWorldString;
  }
})
.controller('main', function($scope, myService) {
  $scope.getAgain = function() {
    $scope.hwString = myService.helloWorldString;
    $scope.hwString2 = myService.helloWorldFunction();
  }
  $scope.getAgain();
})
.controller('notMain', function($scope, myService) {
  myService.helloWorldString = 'edited Hello World String';
  $scope.hwString = myService.helloWorldString;
  $scope.hwString2 = myService.helloWorldFunction();
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app='myModule'>
  <div ng-controller='main'>
    <div>{{hwString}}</div>
    <div>{{hwString2}}</div>
    <button ng-click='getAgain()'>Get again</button>
  </div>
  <div ng-controller='notMain'>
    <div>{{hwString}}</div>
    <div>{{hwString2}}</div>
  </div>
</div>

You will notice initially that the two outputs differ since the first pair was obtained before the change. However, the modification made in the second controller does impact the first one. Simply click the "Get Again" button to refresh the information from the service, confirming they refer to the same object despite being injected into different controllers.

If a factory is better suited to your requirements (although semantically equivalent, replacing "factory" with "service" in the example yields the same outcome), you can create a new instance of the factory object when calling 'myService(...)'. Utilizing function parameters, you can tailor properties, including functions, of the returned object, as depicted in the example.

angular.module('myModule2', [])
  .factory('myService', function() {
    return function(stringInput, thirdFunction) {
      return {
        helloWorldString: stringInput,
        helloWorldFunction: function() {
          return this.helloWorldString;
        },
        thirdFunction: thirdFunction
      }
    }
  })
  .controller('ctrl1', function($scope, myService) {
    var factoryResult = myService('Hello World String', function () {
      this.helloWorldString += ' first';
    });
    $scope.hwString = factoryResult.helloWorldString;
    $scope.hwString2 = factoryResult.helloWorldFunction();
  
    $scope.getAgain = function() {
      factoryResult.thirdFunction();
      $scope.hwString = factoryResult.helloWorldString;
      $scope.hwString2 = factoryResult.helloWorldFunction();
    }
  })
  .controller('ctrl2', function($scope, myService) {
    var factoryResult = myService('new Hello World String', function () {
      this.helloWorldString += ' second';
    });
    $scope.hwString = factoryResult.helloWorldString;
    $scope.hwString2 = factoryResult.helloWorldFunction();
  
    $scope.getAgain = function() {
      factoryResult.thirdFunction();
      $scope.hwString = factoryResult.helloWorldString;
      $scope.hwString2 = factoryResult.helloWorldFunction();
    }
  });
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app='myModule2'>
  <div ng-controller='ctrl1'>
    <div>{{hwString}}</div>
    <div>{{hwString2}}</div>
    <button ng-click='getAgain()'>Get again</button>
  </div>
  <div ng-controller='ctrl2'>
    <div>{{hwString}}</div>
    <div>{{hwString2}}</div>
    <button ng-click='getAgain()'>Get again</button>
  </div>
</div>

Answer №2

When it comes to the service function, it will only be triggered once, meaning your "class methods" will also only be generated once regardless of which approach you take. However, I would advise against using the first method for several reasons:

  • It restricts inheritance
  • You are required to encapsulate your entire class within one large function
  • You need to ensure that functions are not called before they are defined in the constructor

Instead, I recommend a structure more similar to your second approach, like so:

myApp.service('helloWorld', HelloWorldService);

function HelloWorldService() {
    this.msg = "Hello, World!";
}

HelloWorldService.prototype.showMessage = function() {
    return this.msg;
};

Alternatively, in ES6, you could opt for:

myApp.service('helloWorld', HelloWorldService);

class HelloWorldService {
    constructor() {
        this.msg = "Hello, World!";
    }

    showMessage() {
        return this.msg;
    }
}

UPDATE

If you desire the ability to obtain a new instance of your class each time it is injected, you can encase it within a factory. Below is an example with $log included to demonstrate Dependency Injection:

myApp.factory('buildHelloWorld', function($log) {
    return function() {
        return new HelloWorld($log);
    }
});

function HelloWorld($log) {
    this.msg = "Hello, World!";
    $log.info(this.msg);
}

HelloWorld.prototype.showMessage = function() {
    return this.msg;
};

This way, you can inject the buildHelloWorld() function into a controller and invoke it to acquire an instance of HelloWorld.

//controller
var myCtrl = function(buildHelloWorld) {
    this.helloWorld = buildHelloWorld();
}

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

Vue automatically refreshes momentjs dates prior to making changes to the array

I am dealing with a situation where my child component receives data from its parent and, upon button click, sends an event to the parent via an event bus. Upon receiving the event, I trigger a method that fetches data using a Swagger client. The goal is ...

Can you provide a way to directly calculate the total length of all arrays within an array of objects?

How can I find the total length of arrays in an array of objects based on a specific property? var myArray = [{ "a" : 1, "b" : another Array }, { "c" : 2, "b" : another Array } ..... ] Is there a more efficient way to achieve this instea ...

Interactive carousel featuring responsive image zoom effect on hover

Utilizing the flickity carousel, I have crafted an example which can be found at this link to codepen.io. Here is the CSS code that has been implemented: CSS .image-hoover { overflow: hidden; } .image-hoover img { -moz-transform: scale(1.02); -web ...

Choose2 - Dynamic search with auto-complete - keep track of previous searches

Currently, I am utilizing Select2 version 3.5.1 and have successfully implemented remote data loading with the plugin. However, I have a query regarding enhancing the search functionality. Below is a step-by-step explanation of what I aim to achieve: Cre ...

Process the results from an http.get call into a new array and then return the array to the calling

I am aiming to centralize business logic within the service and return an array object of desired values to my controller. service.retrieveBookingDetails($scope.bookingId).success(function (data) { $scope.booking = data; console ...

How to submit the next row using jQuery AJAX only when the previous submission is successful without using a loop - could a counter

Currently, I am dealing with loops and arrays. My goal is to submit only the table rows that are checked, wait for the success of an Ajax call before submitting the next row. Despite trying various methods, I have not been successful in achieving this yet. ...

What is the best way to duplicate all elements and their contents between two specified elements, and store them in a temporary

context = document.createElement("span"); start_element = my_start_element; end_element = my_end_element; // I need to find a way to iterate through a series of elements between start and end [start_element .. end_element].forEach(function(current_element ...

Issue with Vue and Firebase data transmission

I am currently working on an app that tracks user visits, and I am using vue.js 3 and firebase for this project. During testing, I encountered an error message stating "firebase.database is not a function" when trying to send data to firebase. Can anyone ...

When using SweetAlert2, a single button will automatically be highlighted as soon as the modal opens

I recently made the switch from using SweetAlert to SweetAlert 2. It took some time to get used to, but I finally achieved my desired outcome, with one small exception. Upon opening the modal, if there is only one button and no other inputs, the button ap ...

Is there a way to give a ReactJS button a pressed appearance when a key is pressed?

I recently developed a Drum Machine using ReactJS, but I'm facing an issue with making the buttons look like they've been clicked when I press the corresponding key. I came across a similar query on this site, however, I'm having trouble imp ...

What could be causing the issue with updating a js file using ajax?

I've been dealing with a php file called users. Initially, everything was going smoothly as I wrote some JavaScript code for it. However, after making updates to the JavaScript code, it seems to have stopped functioning. Below is the content of the p ...

Customizing React-Data-Grid styles using Material-UI in a React application

Imagine a scenario where we have a file containing themes: themes.js: import {createMuiTheme} from "@material-ui/core/styles"; export const myTheme = createMuiTheme({ palette: { text: { color: "#545F66", }, }, }); In ...

Encountering an error in Angular UI when trying to utilize the $modal within my service dependency injection, causing

I have encountered a problem with my AngularJS and UI-Bootstrap code. In the following service, I am injecting $modal but it is showing as undefined. Interestingly, when I inject $modal into a controller instead, everything works perfectly. Can someone ex ...

Defining the flow and functionality

I'm currently attempting to define a function signature using Flow. I had anticipated that the code below would generate an error, but surprisingly, no errors are being thrown. What could be causing this issue? // This function applies another functi ...

AngularJS - Custom directive to extract a property value from an object

Currently, I am using the following for loop to retrieve the parent category: angular.forEach(queryTicketCategories, function(category) { if(category.id === $scope.ticketCategory.parentId) { $scope.parent = category; } }); I am looking fo ...

Transform the jQuery Mobile slider into a jQuery UI slider and update the text

<script src="http://code.jquery.com/jquery-1.9.1.min.js"></script> <script> function modifycontent() { if (document.getElementById("slider").value<33) { $("#1").fadeIn("slow"); $("#2").fadeOut("slow"); ...

Adjust the DIV shape to fit perfectly within the browser window without overlapping with any other elements

http://jsbin.com/iGIToRuV/1/edit Currently, I am working on developing a WYSIWYG website designer as part of an experimental project for various purposes. The ultimate goal is to ensure that it is both desktop and mobile-friendly. However, I have encount ...

Embed the variable directly within the JSON structure

My goal is to dynamically create a JavaScript object using object initializer notation, but with keys taken from a config object. Instead of the traditional method: var obj = { 'key' : 'some value' }; I envision something like this: ...

Executing a cURL request using Node.js

Looking for assistance in converting the request below: curl -F <a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="1a777f7e737b275a73777b7d7f34706a7d">[email protected]</a> <url> to an axios request if possible. ...

Drawbacks of adjusting design according to screen width via javascript detection

When creating a website, I want it to be compatible with the most common screen resolutions. Here is my proposed method for achieving this: -Develop CSS classes tailored to each screen resolution, specifying properties such as width and position. -Write ...