0

I am trying to reuse some working code from AngularJS 1 services written in plain JavaScript in an Angular 2 environment.

The services look, for instance, like the following example:

(function () {
    angular.module('myapp.mysubmodule').factory('myappMysubmoduleNormalService', ['someOtherService',
        function (someOtherService) {
            var internalState = {
                someNumber: 0
            };
            var service = {};

            service.someFunction = function () {
                internalState.someNumber++;
            };

            someOtherService.getValues().forEach(function (v) {
                service[v] = function () {
                    console.log(v + internalState.someNumber);
                };
            });

            return service;
        }]);
})();

I have found various examples of how to convert AngularJS 1 services to Angular 2 services (such as this one), all of which have in common that instead of the service factory, I have to export a class.

This should look roughly as follows:

import { Injectable } from '@angular/core';

@Injectable()
export class myappMysubmoduleNormalService {
    someFunction: function () {
        // ?
    }
}

Now, the question is how to incorporate the internal state and the dynamically added properties. Is it really the way to go to do all that in the constructor, i.e. fill each instance of the class upon initialization, like so:

import { Injectable } from '@angular/core';

@Injectable()
export class myappMysubmoduleNormalService {
    constructor() {
        var internalState = {
            someNumber: 0
        };
        var service = {};

        this.someFunction = function () {
            internalState.someNumber++;
        };

        this.getValues().forEach(function (v) {
            service[v] = function () {
                console.log(v + internalState.someNumber);
            };
        });
    }
}

Or is there any other way? The above probably works (save for the missing dependency injection, that I still have to find out about how to do in Angular 2). However, i am wondering whether it is a good way because I have not come across any samples that did much of a member initialization in their constructor.

O. R. Mapper
  • 20,083
  • 9
  • 69
  • 114
  • You question is not about how to create a service in angular, your question is "How should I manage state in angular?". There are many options out there for you to choose among. – ChrisG Aug 15 '17 at 14:56
  • @ChrisG: Well, maybe the question is how to create a stateful service in Angular (whose API remains as close to its AngularJS 1 equivalent, because I would like to avoid changing code in dependent services that I'm pulling over, as well). – O. R. Mapper Aug 15 '17 at 14:58
  • You can do everything you want to do in JS. I think your problem is arising because you're using TypeScript and it is enforcing your properties to be declared which is not how you're handling it now. IMO it is better to have declared properties on your object (which is why you want TS in the first place), but you can get around the TS compiler by converting to any (would not recommend). – ChrisG Aug 15 '17 at 15:02
  • @ChrisG: Oh, I'm not using TypeScript. I'm using plain JavaScript. Many Angular 2 samples are using TypeScript; in fact, so many that I sometimes am not even sure any more what parts are specific to Angular 2 and which ones are specitic to TypeScript. – O. R. Mapper Aug 15 '17 at 15:13
  • If you're using JS, then everything should work as expected. You can leverage the DI in the constructor – ChrisG Aug 15 '17 at 15:34

2 Answers2

2

You can use just the same approach in Angular with factory providers:

  export function someServiceFactory(someOtherService) {
    var internalState = {
      someNumber: 0
    };
    var service = {};

    service.someFunction = function () {
      internalState.someNumber++;
    };

    someOtherService.getValues().forEach(function (v) {
      service[v] = function () {
        console.log(v + internalState.someNumber);
      };
    });

    return service;
  };

@NgModule({
  providers: [
    {
      token: 'myappMysubmoduleNormalService',
      useFactory: someServiceFactory,
      deps: ['someOtherService']
    }
  ]
})

Both in Angular and AngularJS the value returned by the factory function is cached.

Estus Flask
  • 206,104
  • 70
  • 425
  • 565
Max Koretskyi
  • 101,079
  • 60
  • 333
  • 488
  • This looks promising - but is there any reason why I have to export the factory function at all, rather than just declare it inline for the `useFactory` property? To avoid an overly long source file with several services, as I have to list all of them in the `providers` array? – O. R. Mapper Aug 16 '17 at 05:28
  • Hmm, also, Chrome complains about an "Unexpected token export". Upon doing some further research, it appears that [these keywords are not yet supported by browsers](https://stackoverflow.com/questions/32809728/ecma-6-not-working-although-experimental-js-is-enabled). While the linked question suggests a solution with a transpiler, is there any way to solve the issue without any such additional tools? – O. R. Mapper Aug 16 '17 at 05:45
  • defining the function otside descripor is required for AOT compilation.If you dont need AOT, you can inline it. Exporting is not required – Max Koretskyi Aug 16 '17 at 07:00
  • In the end, I kind of reimplemented the service injection logic from AngularJS 1.x and created wrappers for some Angular 2 services to imitate AngularJS 1.x services such as $q, $http, and $timeout. That way, I did not have to change anything in my existing codebase. – O. R. Mapper Aug 16 '17 at 17:38
0

A service is just a class that you can inject into components. It will create a singleton in the scope where it is named a provider.

import { Injectable. OnInit } from '@angular/core';

@Injectable()
export class myappMysubmoduleNormalService implements OnInit {
internalState: number;

 constructor() {}

ngOnInit(){

    this.internalState = 0;
}

incrementSomeNumber() {
  this.internalState++;
  console.log(this.internalState};
}


}

I realize this is not logging a distinct internal state for multiple functions but you get the idea.

Register this as a provider in the app.module (if you want a singleton for app scope)

When you import into a component and then inject in the constructor

constructor(private _myservice : myappMysubmoduleNormalService) {}

you can now use the _myservice methods

myNumber : number = 0 ;

componentFunction() {
     _myservice.incrementSomeNumber();
     this.myNumber = _myservice.internalState;
}

Of course you could have the service method return the incremented number (or data or a promise of data)

This is rough but gives you the idea. Very little code belongs in the constructor. A service should be injected. what is shown in component constructor is shorthand to a get private variable referencing the service. The service will be a singleton for the scope in which it is provided. (can be overridden within the scope but that seems a code smell to me)

To pass back a value :

In service

 incrementSomeNumber(): number {
 this._internalState++;
  console.log(this._internalState};
 return this._internalState;
 }

In component: mynumber: number;

componentFunction() {
    this.mynumber = _myservice.incrementSomeNumber();
}

Not sure what you're trying to accomplish but just wanted to show example of getting information from services. Most common use of services for me is a dataservice, so the code would be a little more complex as it is asynch.

Charles Hankey
  • 495
  • 1
  • 7
  • 22