AngularJS and TypeScript services: A powerful combination

I have created an $http service to read a json file. I am looking to implement modularization, but I am unsure how to properly call my service in the controller and if my service implementation is correct.

You can find a jsFiddle example here: https://jsfiddle.net/aqbmdrvn/.

Thank you!

/// <reference path="../../typings/angularjs/angular.d.ts" />
/// <reference path="../../typings/angularjs/angular-route.d.ts" />
/// <reference path="../app.ts" />  
/// <reference path="servicePets.ts" />
"use strict";

module AnimalPlanet {
    // pet interface
    export interface IPet {
        type: string;
        name: string;
        age: number;
        color: string;
        specialCare: boolean;
        availableForAdoption: boolean;
        ldAdoption: boolean;
        history: string;
        featured: boolean;
        newest: boolean;
        imageUrl: string;
    }

    export interface RootObject {
        pets: IPet[];
    }
    // pet controller with ui-grid
    export class petsCtrl implements RootObject {
        pets: IPet[];
        constructor(private $http: ng.IHttpService, public petsService,  private $scope: any, uiGridConstants: any, filterldAdoption: any) {
            $scope.pets = {};
            // ui grid option
            $scope.gridOptions = {
                enableSorting: true,
                enableFiltering: true,
                paginationPageSizes: [5, 10, 15],
                paginationPageSize: 5,
                onRegisterApi: (gridApi) => {
                    $scope.gridApi = gridApi;
                },
                columnDefs: [
                    {
                        name: 'type',
                        cellTooltip: true,
                        headerTooltip: true
                    },
                    {
                        name: 'name',
                        cellTooltip: true,
                        headerTooltip: true
                    },

                    {
                        name: 'age',
                        // filters: [{
                        //     condition: uiGridConstants.filter.GREATER_THAN,
                        //     placeholder: 'greater than'
                        // }, {
                        //         condition: uiGridConstants.filter.LESS_THAN,
                        //         placeholder: 'less than'
                        //     }
                        // ],
                        cellTooltip: true,
                        headerTooltip: true
                    },
                    {
                        name: 'color',
                        cellTooltip: true,
                        headerTooltip: true

                    },
                    {
                        name: 'specialCare',
                        cellTooltip: true,
                        headerTooltip: true
                    },
                    {
                        name: 'availableForAdoption',
                        cellTooltip: true,
                        headerTooltip: true
                    },
                    {
                        name: 'history',
                        cellTooltip: true,
                        headerTooltip: true
                    },
                    {
                        name: 'featured',
                        cellTooltip: true,
                        headerTooltip: true
                    },
                    {
                        name: 'newest',
                        cellTooltip: true,
                        headerTooltip: true
                    },
                    {
                        name: 'imageUrl',
                        cellTooltip: true,
                        headerTooltip: true,
                        enableFiltering: false,
                        enableHiding: false,
                        cellTemplate: "<img width=\"50px\" ng-src=\"{{grid.getCellValue(row, col)}}\" lazy-src>"
                    }
                ]
            };
            // read json using http service
            this.$http.get('/app/pets/pets.json').success((data) => { // pune te rog asta intr-un serviciu

                // fill ui grid using http service
                $scope.filterPets = data;
                var uiGridPets = [];
                angular.forEach($scope.filterPets, (item) => {
                    if (item.ldAdoption) {
                        uiGridPets.push(item);
                    }
                });

                $scope.gridOptions.data = uiGridPets;

                // filter for main page with 3 pets
                $scope.pets = data;
                $scope.quantity = 3;
                var featuredPets = [];
                var newestPets =[];
                angular.forEach($scope.pets, (item) => {
                    if (item.featured) {

                        featuredPets.push(item);

                    }
                    if(item.newest){
                        newestPets.push(item);
                    }

                });
                $scope.featuredPets = featuredPets;
                $scope.newestPets = newestPets;
           });
            $scope.fromService = petsService.weatherChange();
        }

    }

    petsCtrl.$inject = ['$http', '$scope', 'uiGridConstants', 'petsService'];
    app.controller("petsCtrl", petsCtrl);


}
/// <reference path="../../typings/angularjs/angular.d.ts" />
/// <reference path="../../typings/angularjs/angular-route.d.ts" />
/// <reference path="../app.ts" />  

"use strict";

module AnimalPlanet {
    export interface IPetsService {
        http: ng.IHttpService;
        uiGridConstants: any;
    }
    export class servicePets implements IPetsService {
       http: ng.IHttpService;
       uiGridConstants: any;
        constructor( $scope:any , $http: ng.IHttpService, uiGridConstants: any )
        {
             // read json using http service
            $scope.pets = {};
            this.http = $http;
        }
        public get() {
            this.http.get('/app/pets/pets.json').success((data) => { 

                // fill ui grid using http service
                var filterPets = data;
                var uiGridPets = [];
                angular.forEach(filterPets, (item) => {
                    if (item.ldAdoption) {
                        uiGridPets.push(item);
                    }
                });

                var gridOptions.data = uiGridPets;

                // filter for main page with 3 pets
                var pets = data;

                var quantity = 3;
                var featuredPets = [];
                var newestPets =[];
                angular.forEach(pets, (item) => {
                    if (item.featured) {

                        featuredPets.push(item);

                    }
                    if(item.newest){
                        newestPets.push(item);
                    }

                });
                var featuredPets = featuredPets;
                var newestPets = newestPets;
            });
    }

    }

    servicePets.$inject = ['$http', '$scope', 'uiGridConstants'];
    app.service('servicePets', servicePets);

}

Answer №1

Below is my approach for boilerplate controller and factory usage:

Controller snippet:

declare var app: angular.IModule;

class MyController {
    static $inject = ['myService'];

    localData: IGetDataResult;

    constructor(private myService: MyService) {
        this.myService.getData('key1')
            .then(result => {
                this.localData = result.data;
            });
    }
}

app.controller('myController', MyController);

Factory code example:

declare var app: angular.IModule;

interface IGetDataResult {
    field1: string;
}

class MyService {
    static $inject = ['$http'];

    constructor(private $http: angular.IHttpService) {
    }

    getData(key: string): angular.IHttpPromise<IGetDataResult> {
        return this.$http({
            method: 'POST',
            data: {
                key: key
            },
            url: '/WebService/GetData'
        });
    }
}

function MyServiceFactory($rootElement) : MyService {
    const inj = $rootElement.injector();
    return inj.instantiate(MyService);
}
app.factory('myService', ['$rootElement', $rootElement => MyServiceFactory($rootElement)]);

Key points to note:

  • The controller setup includes the use of a static injection variable $inject. The order in this array must match the parameter order in the constructor function.
  • The factory is triggered by a factory function, with a dependency on $rootElement. This was found crucial for potential future usage of $location.
  • A strongly typed return value is utilized for the $http call, aiding in code clarity.
  • All elements are properly typed, except for $rootElement, lacking a definitive type in angular.d.ts including the injector() function.

Furthermore, typescript aligns with the controllerAs methodology, removing the need to inject $scope, and prefixing controller alias before each scope expression in HTML. All scope variables and functions are consolidated as controller members.

Situations may arise where injecting $scope becomes necessary, like when using $watch or $on. However, handling this closure poses challenges worth exploring in a separate discussion.

Note that employing ui-grid mandates including the controller alias prefix in any string assigned to gridoptions.data member.

Answer №2

If you want to make your code more modular, it's a good idea to refactor the $http call and its dependency injection out of the controller and into the service.

When injecting the service into the controller, remember to consider the module "namespace". For example, your $inject should look like: "AnimalPlanet.petsService". If you're having trouble invoking the service from your controller, it may not be properly wired.

I personally use a different pattern for using $inject:

// Dependencies for this controller.
static $inject = ["$q",
    "$location",
    "$window",
    "$scope",
    "$log",
    "common",
    "RL.SS.Portal.RLToday.Services.AroundTheIndustryService",
    "RL.SS.Portal.RLToday.Services.LocationFilterService"];

This is inside the class definition itself. It may or may not be more accurate, but you might find it helpful.

In the controller's constructor, there's no need (and it's probably best not to) make the petservice reference public, although that's more of a style choice than a problem.

Once you have the injection done correctly, you can call the following in your controller:

this.petsService.Get().then( () => { /* success */ }, () => { /* fail */});

Hope this helps.

(This is my first attempt at answering a stack overflow question, so any advice on how to improve is welcome).

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

Difficulty in binding large amounts of data with Angular causing Internet Explorer to become unresponsive

Whenever I try to bind the response of my service to a component variable, which contains more than 7000 records, my screen freezes and becomes unresponsive. This issue seems to occur only on Internet Explorer. Additionally, there are 3 dropdowns in the UI ...

Error: Unable to cast value to an array due to validation failure

I'm currently working on integrating Typegoose with GrqphQL, MongoDB, and Nest.js for a project. My goal is to create a mutation that will allow users to create a post. I have set up the model, service, and resolver for a simple Post. However, when I ...

Could memory leaks be caused by using the "angular.module" getter function?

When it comes to dealing with modules, John Papa suggests using chaining instead of creating variables (refer to the modules section) : It's recommended to avoid using a variable and opt for chaining with the getter syntax. According to him : T ...

An issue arose in Leaflet where drawing on the map became impossible after making an update to a marker's position

I have been working with Leaflet, Leaflet-draw, and Cordova Geolocation. Initially, when the map is loaded in globe view, drawing works perfectly. However, when the locate function is called to update the map center and marker position, drawing becomes imp ...

Error encountered with AngularJS code when attempting to load content from another page using ajax

I'm currently tackling a challenge with AngularJs and php. Whenever I try to load content from another page, AngularJs seems to stop working. Let me provide you with a sample code snippet to illustrate my issue. main-page.php <div id="form-secti ...

Get rid of the TypeScript error in the specified function

I am currently working on implementing a "Clear" button for a select element that will reset the value to its default state. Here is a snippet of my code: const handleChange = (e: React.ChangeEvent<HTMLSelectElement>) => { onChange( ...

Angular does not recognize the boolean variable

Within my Angular component, I have declared two boolean variables: editingPercent: boolean = true; editingCap: boolean = false; In the corresponding HTML file, there is a checkbox that updates these variables based on user input: checkedChanged(e) { ...

Monitoring changes to localstorage in AngularJS

How can I monitor changes to localStorage using $watch? I have created a factory to simplify setting and getting values .factory('$localstorage', ['$window', function($window) { return { set: function(key, value) { ...

Retrieve the complete paths for the keys within a deeply nested object in real-time

Trying to dynamically retrieve the keys of a nested object is proving to be quite challenging. Take for instance this sample object: { ts: "2021-05-06T11:06:18Z", pr: 94665, pm25: 5, co2: 1605, hm: 32, m: { isConnect ...

The functionality of $state.go within $stateChangeStart in the app.run is not functioning properly in AngularJS

Having some trouble getting $state.go() function to work. The $on('$stateChangeStart'...); is functioning properly, and I can see the console message when trying to access a protected state without permission. However, the $state.go('toState ...

Encountering an error while using $state.go function in Angular JS testing

Below is the code snippet for a Controller in Angular JS: describe('Controller: homeCtrl', function () { beforeEach(module('incident')); var homeCtrl, $state; beforeEach(inject(function ($controller, _$state_) { $state = _ ...

Working with nested objects in a React functional component's state using TypeScript

In my React functional component state, I have a nested object that I can only access the first level of if I use the any type. export default function Detail() { const [user, setUser] = useState<any>({}); const { id } = useParams(); us ...

Why is my data not being sent with the $http POST request in AngularJS?

using angularjs to fetch data with $http userId = '1'; $http({ url: "php/loadTab.php", method: "POST", data: userId }).success(function(data, status, headers, config) { console.log(data); }).error(function(data, status, headers, config) { } ...

Incorporate a fresh label for a function utilizing AngularJS

I want to insert a new HTML tag with an event attached to it. Here is an example of what I am trying to achieve: <html ng-app="module"> <head> <script src="http://code.jquery.com/jquery-latest.min.js" type="text/javascript"></script&g ...

The issue of Ajax response not triggering the ng-click function

When attempting to pass data through an AJAX return, I encountered an issue with a function that writes an ng-click in Angular 1. The code snippet is as follows: $.ajax({ 'method': 'GET', 'url': base_url +&apos ...

What could be causing the issue with my dependency injection in my Angular application?

Let's get started angular.module('app', [ 'ngCookies', 'ngResource', 'ngSanitize', 'ngRoute' ]) This is my simple factory. Nothing fancy here angular.module('app') .factory(&apos ...

Issues with $http service preventing view from updating

I am facing a situation where I have two controllers nested inside one another. The ParentController contains an object that I need to use in the ChildController, as it is directly accessible in the child. In the ChildController, I am making a call to the ...

Creating a global property access expression using the Typescript Compiler API

Currently, I'm grappling with the challenge of creating TypeScript code using the compiler API. Regrettably, official documentation on this subject is scarce, leaving me stranded on a seemingly straightforward task: All I want to do is generate a bas ...

When implementing the Parse Client Key in an Angular application, it may lead to encountering

I have been working on retrieving a class from Parse using a client key with the GET method. Successfully sent a request through Advanced Rest Client for Google Chrome by including X-Parse-Application-Id and X-Parse-Client-Key headers. [edit] [edit2] Resp ...

The property express.json() is not recognized

Why doesn't Typescript recognize the express.json() function, even though many tutorials claim it should compile without errors? Could I have overlooked something in my code? An example tutorial that suggests this code works: https://auth0.com/blog/n ...