1

I'm kinda new with Angular and I'm trying to build my app around it, but I'm having some troubles when injecting services in my Components.

I've added a 3rd party module with a service provider for translations in multiple languages on the fly, as it is kinda "hard" to do it manually, and a cookie service provider. The main problem is that the translation is made during template compilation, so to achieve that for my component I have to use a code like this:

<!-- language: typescript -->
import { Component, OnInit } from '@angular/core';

import { TranslateService } from '@ngx-translate/core';
import { CookieService } from 'ngx-cookie-service';

@Component({
  selector: 'app-home',
  templateUrl: './home.component.html',
  styleUrls: ['./home.component.css']
})
export class HomeComponent implements OnInit {

  language: string;

  constructor(
    private translateService: TranslateService,
    private cookieService: CookieService) { }

  ngOnInit() {
    this.language = this.cookieService.get('lang') || 'en';
    this.translator.use(this.language);
  }

}

The main problem is that I have to pass those service providers everytime in the constructor function in every component, which is very repetitive. To prevent that, I've tried to create an abstract class then extend it in my components as a base component. Something like this:

<!-- language: typescript -->

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

import { TranslateService } from '@ngx-translate/core';
import { CookieService } from 'ngx-cookie-service';

@Injectable()
export abstract class DefaultComponent implements OnInit {

  language: string;

  constructor(private translator: TranslateService, private cookieService: CookieService) {
    this.language = this.cookieService.get('lang') || 'es';
    this.translator.use(this.language);
  }

  ngOnInit() {
    // 
  }

}

<!-- language: typescript -->
import { Component } from '@angular/core';

import { DefaultComponent } from '../default.component';

@Component({
  selector: 'app-home',
  templateUrl: './home.component.html',
  styleUrls: ['./home.component.css']
})
export class HomeComponent extends DefaultComponent {

  ngOnInit() {
    // Everything here replaces the ngOnInit from parent
  }

}

This solution works for many of my components, but I have one component called PostsComponent that uses a specific service provider to handle Posts. This is the code:

<!-- language: typescript -->
import { Component } from '@angular/core';

import { DefaultComponent } from '../default.component';

import { Post } from '../../models/post';
import { PostService } from '../../services/post/post.service';

@Component({
  selector: 'app-posts',
  templateUrl: './posts.component.html',
  styleUrls: ['./posts.component.css'],
  providers: [ PostService ]
})
export class PostsComponent extends DefaultComponent {

  posts: Post[];

  constructor(private postService: PostService) { }

  ngOnInit() {
    this.getPosts();
  }

  getPosts(): void {
    this.postService.getPosts().subscribe(posts => this.posts = posts);
  }

}

If I use the constructor function for that component to use service provider, the parent constructor is overriden. To prevent that, I should call to super() function in the constructor function, but I will have to send the TranslateService and CookieService to that function in order to make it work everything. As I said in the beginning, this is repetitive and I do not like that so much.

How can I achieve to load those common services for every component and still allowing to use specific services for specific components? I don't think setting up those common services as global variables is a good practice. Correct me if I'm wrong.

Thanks for your time.

DaGLiMiOuX
  • 889
  • 9
  • 28

1 Answers1

0

In order for child class to have additional dependencies, it should list parent's dependencies as well and pass them to super:

export class PostsComponent extends DefaultComponent {
  ...
  constructor(
    translator: TranslateService,
    cookieService: CookieService,
    private postService: PostService
  ) {
    super(translator, cookieService);
  }
  ...
}

Parent's dependencies should have no visibility modifier because they are already assigned to instance in parent constructor.

In case there are many dependencies, parent class can be refactored to use Injector in constructor to get them, Injector will be the only dependency that needs to be passed to super. If there is a possibility that list of parent's dependencies will be extended in future, it may be reasonable to use Injector right away.

There is no other good way around. This boilerplate code is the price for trouble-free Angular experience. Any other way can currently be considered a hack.

There is always a chance that a list of parent dependencies is too big. In this case there will always be boilerplate code because a component should contain business logic for switching languages, while it's role is presentation logic (notice that language choice in ngOnInit makes it impossible to switch languages without component tree recompilation, which is potential design flaw). It is preferable to merge TranslateService and CookieService into custom service that will be responsible for switching languages. This is an application of composition over inheritance principle.

Estus Flask
  • 206,104
  • 70
  • 425
  • 565
  • I've been reading about the `Injector` class. Do you think it is better to use an `Injector`? Can I call the injector and retrieve the desired services in parent class like `translateService = injector.get('TranslateService'); cookieService = injector.get('CookieService');` and when I need to inject the `PostService`, I call those injections in the child constructor and send them to parent constructor + call the `PostService` provider? – DaGLiMiOuX May 08 '18 at 23:23
  • You do `translateService = injector.get(TranslateService)` in parent constructor, so the only thing you need to pass from children constructor is `super(injector)`. Yes. It's a bit less convenient but more far-fetched step. I added the explanation about these services. There's a good chance that base component class is unneeded (I got rid of them in my apps in a similar way) – Estus Flask May 08 '18 at 23:29
  • Thanks for your time. I will mark it as answered and give it a try :) Thank you for all information. I've been reading in many sites that injector is really helpfull and even If I have to repeat the code, I could `export` a specific function that can be called in all components and remove the base component or use another `service`. Thanks for your help :) – DaGLiMiOuX May 08 '18 at 23:32
  • You're welcome. Regarding i18n, usually, you need to init it in AppComponent and get translations in child components with translate pipe, that's why it's generally unnecessary in base component.. TranslateService is needed only if you need to get them programmatically in component class, which is quite rare. But it's generally a good idea to have `Injector` injected in all components any way, just because you'll need it any way to use DI in decorators like https://stackoverflow.com/a/48875749/3731501 – Estus Flask May 08 '18 at 23:42
  • I don't know if it is because I'm using the third party module and it is not allowed to use the translation globally or because I'm using routes, but when I navigate between all different component routes, I have to call the `translatorService.use(languageCode);` again. I don't know if this module can be called for all component renderizations, because I could not make it work. It is like when a component is called, the translator is "reloaded". I'm kinda new in Angular, as I said. I will take a look into the module to see if it can be used in whole app somehow. – DaGLiMiOuX May 09 '18 at 00:01
  • ngx-translate is a nice lib and yes, it is supposed to work globally and accept language changes only when they are needed. Feel free to ask a new question if you will have problems with it, I'm sure they are solvable. – Estus Flask May 09 '18 at 00:13
  • Thank you for everything, again. I don't know if `@Component` has a property called `Injector` like the interface. I will give it a try to everything. – DaGLiMiOuX May 09 '18 at 07:23