135

I created a service SocketService, basically it initializes the socket to let the app listen on the port. This service also interacts with some components.

// socket.service.ts

export class SocketService {
    constructor() {
        // Initializes the socket
    }
    ...
}

I know the code in SocketService's constructor() only starts to run when a component use SocketService.

And usually the code in app.ts looks like this:

// app.ts

import {SocketService} from './socket.service';
...
class App {
    constructor () {}
}
bootstrap(App, [SocketService]);

However, I want this service run when the app starts. So I made a trick, just add private _socketService: SocketService in App's constructor(). Now the code looks like this:

// app.ts (new)

import {SocketService} from './socket.service';
...
class App {
    constructor (private _socketService: SocketService) {}
}
bootstrap(App, [SocketService]);

Now it works. The problem is sometimes the code in SocketService's constructor() runs, and sometimes not. So how should I do it correctly?

starball
  • 20,030
  • 7
  • 43
  • 238
Hongbo Miao
  • 45,290
  • 60
  • 174
  • 267
  • This guide has helped me: https://angular.io/docs/ts/latest/tutorial/toh-pt4.html#don-t-use-new-with-the-heroservice- – Marian07 May 04 '17 at 10:56

6 Answers6

172

Stuart's answer points in the right direction, but it's not easy to find information on APP_INITIALIZER. The short version is you can use it to run initialization code before any of your other application code runs. I searched for a while and found explanations here and here, which I will summarize in case they disappear from the web.

APP_INITIALIZER is defined in angular/core. You include it in your app.module.ts like this.

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

APP_INITIALIZER is an OpaqueToken (or an InjectionToken since Angular 4) that references the ApplicationInitStatus service. ApplicationInitStatus is a multi provider. It supports multiple dependencies and you can use it in your providers list multiple times. It is used like this.

@NgModule({
  providers: [
    DictionaryService,
    {
      provide: APP_INITIALIZER,
      useFactory: (ds: DictionaryService) => () => return ds.load(),
      deps: [DictionaryService],
      multi: true
    }]
})
export class AppModule { }

This provider declaration tells the ApplicationInitStatus class to run the DictionaryService.load() method. load() returns a promise and ApplicationInitStatus blocks the app startup until the promise resolves. The load() function is defined like this.

load(): Promise<any> {
  return this.dataService.getDiscardReasons()
  .toPromise()
  .then(
    data => {
      this.dictionaries.set("DISCARD_REASONS",data);
    }
  )
}

Set up like that the dictionary gets loaded first and the other parts of the app can safely depend on it.

Edit: Be aware that this will increase the up-front load time for you app by however long the load() method takes. If you want to avoid that you could use a resolver on your route instead.

GMK
  • 2,890
  • 2
  • 20
  • 24
  • Thank you for this... very helpful – Gaurav Joshi Sep 11 '17 at 07:58
  • 6
    This should be the accepted answer. The current one only moves one line of code from a constructor to an `init` method. While constructors should indeed be kept as simple as possible, that thought alone does not make it a proper solution. Using `APP_INITIALIZER` does. – J.P. Aug 26 '18 at 10:36
  • I don't think the selected answer is wrong, since it solves OP's problem. **BUT**, as I have a similar problem on some library's development, I've openned [another question](https://stackoverflow.com/questions/57361724/how-to-auto-run-a-service-from-a-module-when-its-imported-in-angular/57361725#57361725) where **this** answer would fit perfectly. – Machado Aug 05 '19 at 15:31
  • The best way to do – Renil Babu Nov 26 '19 at 10:12
  • I think this solution is only works if the dataService is not using any Angular service. Because otherwise you get an "Cannot instantiate cyclic dependency! ApplicationRef" error. [More Details](https://stackoverflow.com/questions/46441139/cannot-instantiate-cyclic-dependency-applicationref-error-in-ngmodule) – Gabor Jan 30 '21 at 22:13
62

Move the logic in your SocketService constructor to a method instead and then call that in your main component's constructor or ngOnInit

SocketService

export class SocketService{
    init(){
        // Startup logic here
    }
}

App

import {SocketService} from './socket.service';
...
class App {
    constructor (private _socketService: SocketService) {
        _socketService.init();
    }
}
bootstrap(App, [SocketService]);
SnareChops
  • 13,175
  • 9
  • 69
  • 91
  • 1
    i don't understand whats the logic behind do stuff in the method instead of constructor could you please explain this, whats the advantage of doing logic in the method ? – Pardeep Jain Feb 04 '16 at 04:51
  • 1
    A cleaner approach imho – inoabrian Feb 04 '16 at 05:39
  • 14
    Constructors should be as simple as possible (normally only injection points), in case you need to add extra logic use the ngOnInit hook. – Sergio May 31 '16 at 21:29
  • 1
    Yet another thing the team didnt thought of.. The more I work on Angular 4 I realise how brilliantly built is the Aurelia framework. It has all of these possiblities right out of the box by simply adding a decorator. Those guys know what they're doing. – Joel Hernandez Sep 26 '17 at 12:39
  • What if the content of the initialization function is asynchronous? How do you force it to be synchronous? – CodyBugstein Mar 12 '18 at 23:56
  • 1
    @CodyBugstein It depends on your use case. If it's just fire-and-forget then just call the async method. If you need to wait for the result you can return a `Promise` from your `init()` method and then chain as needed. Whatever the case it **can** be done, but will probably be difficult and will be up to you to work out the details. If you need further help you can always post a question with details of your exact issue and the community would be happy to help you out. – SnareChops Mar 26 '18 at 00:46
  • @Sergio, [Angular does not yet support OnInit for services](https://github.com/angular/angular/issues/23235) – kolypto Oct 08 '20 at 21:08
11

Also see APP_INITIALIZER, which is described as;

A function that will be executed when an application is initialized.

Stuart Hallows
  • 8,795
  • 5
  • 45
  • 57
1

socket.service.ts

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

@Injectable({
  providedIn: 'root',
})
export class SocketService {
  init() {
    console.log("socket service initialized");
  }
}

app.component.ts

import { Component } from '@angular/core';
import {SocketService} from './socket.service';

@Component({
  selector: 'app-root',
  templateUrl: 'app.component.html',
  styleUrls: ['app.component.scss'],
})
export class AppComponent {
  constructor(private _socketService: SocketService) {
    this._startupService.init();
  }
}
Mo D Genesis
  • 5,187
  • 1
  • 21
  • 32
0

Try Creating service constructor & then call it in ngOnInit() of your component.

  • Service Module

 export class SocketService {
    constructor() { }
        getData() {
            //your code Logic
        }
}
  • Component

export class AppComponent {
    public record;  
    constructor(private SocketService: DataService){ }
    ngOnInit() {        
        this.SocketService.getData()
        .subscribe((data:any[]) => {
            this.record = data;
        });   
  }  
}       

Hope this Helps.

Bhushan Gadekar
  • 13,485
  • 21
  • 82
  • 131
0
in config.teml.json
{Domain:'your url'}

in app.module.ts

import * as mydata from '../assets/config.teml.json';
const data:any=(mydata as any).default;
let config: SocketIoConfig = { url: data.Domain, options: { } };
K24
  • 1
  • 1
  • 2
  • 2
    Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Oct 17 '21 at 08:09