Angular 5 - Service provider injection in all comp

2019-08-27 13:06发布

问题:

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.

回答1:

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.



标签: angular