Injeksi warisan dan ketergantungan

101

Saya memiliki satu set komponen angular2 yang semuanya harus mendapatkan layanan yang disuntikkan. Pikiran pertama saya adalah yang terbaik adalah membuat kelas super dan memasukkan layanan di sana. Salah satu komponen saya kemudian akan memperluas superclass itu tetapi pendekatan ini tidak berhasil.

Contoh yang disederhanakan:

export class AbstractComponent {
  constructor(private myservice: MyService) {
    // Inject the service I need for all components
  }
}

export MyComponent extends AbstractComponent {
  constructor(private anotherService: AnotherService) {
    super(); // This gives an error as super constructor needs an argument
  }
}

Saya bisa menyelesaikan ini dengan menyuntikkan MyServicedalam setiap komponen dan menggunakan argumen itu untuk super()panggilan tetapi itu pasti semacam tidak masuk akal.

Bagaimana cara mengatur komponen saya dengan benar sehingga mereka mewarisi layanan dari kelas super?

maxhb
sumber
Ini bukan duplikat. Pertanyaan yang direferensikan adalah tentang bagaimana membangun kelas DERIVED yang dapat mengakses layanan yang dimasukkan oleh kelas super yang sudah ditentukan. Pertanyaan saya adalah tentang bagaimana membangun kelas SUPER yang mewarisi layanan ke kelas turunan. Ini hanya sebaliknya.
maxhb
Jawaban Anda (sebaris dalam pertanyaan Anda) tidak masuk akal bagi saya. Dengan cara ini Anda membuat injektor yang independen dari injektor yang digunakan Angular untuk aplikasi Anda. Menggunakan new MyService()alih-alih menyuntikkan memberi Anda hasil yang persis sama (kecuali lebih efisien). Jika Anda ingin berbagi contoh layanan yang sama di berbagai layanan dan / atau komponen, ini tidak akan berfungsi. Setiap kelas akan mendapatkan MyServicecontoh lain .
Günter Zöchbauer
Anda benar, kode saya akan menghasilkan banyak contoh myService. Menemukan solusi yang menghindari hal ini tetapi menambahkan lebih banyak kode ke kelas turunan ...
maxhb
Menyuntikkan injektor hanya merupakan perbaikan bila ada beberapa layanan berbeda yang perlu disuntikkan di banyak tempat. Anda juga dapat memasukkan layanan yang memiliki dependensi ke layanan lain dan menyediakannya menggunakan getter (atau metode). Dengan cara ini Anda hanya perlu menginjeksi satu layanan tetapi dapat menggunakan sekumpulan layanan. Solusi Anda dan alternatif yang saya usulkan memiliki kelemahan yaitu membuat lebih sulit untuk melihat kelas apa yang bergantung pada layanan apa. Saya lebih suka membuat alat (seperti template langsung di WebStorm) yang membuatnya lebih mudah untuk membuat kode boilerplate dan eksplisit tentang dependensi
Günter Zöchbauer

Jawaban:

73

Saya bisa menyelesaikan ini dengan menyuntikkan MyService di dalam setiap komponen dan menggunakan argumen itu untuk panggilan super () tapi itu pasti semacam tidak masuk akal.

Itu tidak masuk akal. Ini adalah cara kerja konstruktor dan injeksi konstruktor.

Setiap kelas yang dapat diinjeksi harus mendeklarasikan dependensi sebagai parameter konstruktor dan jika superclass juga memiliki dependensi, ini juga perlu dicantumkan dalam konstruktor subclass dan diteruskan ke superclass dengan super(dep1, dep2)panggilan tersebut.

Meneruskan injektor dan memperoleh ketergantungan sangat penting memiliki kerugian yang serius.

Ini menyembunyikan dependensi yang membuat kode lebih sulit dibaca.
Itu melanggar ekspektasi orang yang akrab dengan cara kerja Angular2 DI.
Ini merusak kompilasi offline yang menghasilkan kode statis untuk menggantikan DI deklaratif dan imperatif untuk meningkatkan kinerja dan mengurangi ukuran kode.

Günter Zöchbauer
sumber
6
Hanya untuk memperjelas: Saya membutuhkannya DI MANA SAJA. Mencoba memindahkan ketergantungan itu ke kelas super saya sehingga SETIAP kelas turunan dapat mengakses layanan tanpa perlu menyuntikkannya secara individual ke setiap kelas turunan.
maxhb
9
Jawaban atas pertanyaannya sendiri adalah peretasan yang buruk. Pertanyaan tersebut sudah menunjukkan bagaimana itu harus dilakukan. Saya menguraikan lebih banyak.
Günter Zöchbauer
7
Jawaban ini benar. OP menjawab pertanyaan mereka sendiri tetapi melanggar banyak kesepakatan dalam melakukannya. Bahwa Anda mencantumkan kerugian sebenarnya juga membantu dan saya akan menjaminnya - saya memikirkan hal yang sama.
dudewad
6
Saya benar-benar ingin (dan terus) menggunakan jawaban ini atas "hack" OP. Tetapi saya harus mengatakan bahwa ini tampaknya jauh dari KERING dan sangat menyakitkan ketika saya ingin menambahkan ketergantungan di kelas dasar. Saya hanya perlu menambahkan suntikan ctor (dan superpanggilan yang sesuai ) ke sekitar 20+ kelas dan jumlah itu hanya akan bertambah di masa depan. Jadi dua hal: 1) Saya tidak suka melihat "basis kode besar" melakukan ini; dan 2) Terima kasih Tuhan untuk vim qdan vscodectrl+.
6
Hanya karena tidak nyaman bukan berarti itu adalah praktik yang buruk. Konstruktor tidak nyaman karena sangat sulit untuk melakukan inisialisasi objek dengan andal. Saya berpendapat praktik yang lebih buruk adalah membangun layanan yang membutuhkan "kelas dasar yang memasukkan 15 layanan dan diwarisi oleh 6".
Günter Zöchbauer
66

Solusi yang diperbarui, mencegah beberapa contoh myService dibuat dengan menggunakan injektor global.

import {Injector} from '@angular/core';
import {MyServiceA} from './myServiceA';
import {MyServiceB} from './myServiceB';
import {MyServiceC} from './myServiceC';

export class AbstractComponent {
  protected myServiceA:MyServiceA;
  protected myServiceB:MyServiceB;
  protected myServiceC:MyServiceC;

  constructor(injector: Injector) {
    this.settingsServiceA = injector.get(MyServiceA);
    this.settingsServiceB = injector.get(MyServiceB);
    this.settingsServiceB = injector.get(MyServiceC);
  }
}

export MyComponent extends AbstractComponent {
  constructor(
    private anotherService: AnotherService,
    injector: Injector
  ) {
    super(injector);

    this.myServiceA.JustCallSomeMethod();
    this.myServiceB.JustCallAnotherMethod();
    this.myServiceC.JustOneMoreMethod();
  }
}

Ini akan memastikan bahwa MyService dapat digunakan dalam semua kelas yang memperluas AbstractComponent tanpa perlu memasukkan MyService di setiap kelas turunan.

Ada beberapa kontra untuk solusi ini (lihat Komentar dari @ Günter Zöchbauer di bawah pertanyaan asli saya):

  • Menyuntikkan injektor global hanya merupakan perbaikan bila ada beberapa layanan berbeda yang perlu disuntikkan di banyak tempat. Jika Anda hanya memiliki satu layanan bersama maka mungkin lebih baik / lebih mudah untuk memasukkan layanan itu ke dalam kelas turunan
  • Solusi saya dan alternatif yang diusulkannya memiliki kelemahan yaitu membuat lebih sulit untuk melihat kelas mana yang bergantung pada layanan apa.

Untuk penjelasan yang ditulis dengan sangat baik tentang injeksi ketergantungan di Angular2 lihat posting blog ini yang sangat membantu saya untuk menyelesaikan masalah: http://blog.thoughtram.io/angular/2015/05/18/dependency-injection-in-angular- 2.html

maxhb
sumber
7
Ini membuatnya sangat sulit untuk memahami layanan apa yang sebenarnya disuntikkan.
Simon Dufour
1
Bukankah seharusnya itu this.myServiceA = injector.get(MyServiceA);dll?
jenson-button-event
10
Jawaban @ Gunter Zochbauer benar. Ini bukan cara yang benar untuk melakukan ini dan melanggar banyak konvensi sudut. Mungkin lebih sederhana dalam pengkodean semua panggilan injeksi itu adalah "rasa sakit", tetapi jika Anda ingin mengorbankan keharusan menulis kode konstruktor untuk dapat mempertahankan basis kode yang besar, maka Anda menembak diri sendiri. Solusi ini tidak dapat diskalakan, IMO, dan akan menyebabkan banyak bug yang membingungkan di masa mendatang.
dudewad
3
Tidak ada risiko beberapa contoh layanan yang sama. Anda hanya perlu menyediakan layanan di root aplikasi Anda untuk mencegah beberapa contoh yang dapat terjadi di berbagai cabang aplikasi. Meneruskan layanan ke bawah perubahan warisan tidak membuat instance baru dari kelas. Jawaban @ Gunter Zochbauer benar.
ktamlyn
@maxhb pernahkah Anda menjelajahi perluasan Injectorsecara global untuk menghindari keharusan merantai parameter apa pun AbstractComponent? fwiw, saya pikir properti menyuntikkan dependensi ke kelas dasar yang banyak digunakan untuk menghindari rangkaian konstruktor yang berantakan adalah pengecualian yang benar-benar valid untuk aturan biasa.
Quentin-starin
5

Alih-alih memasukkan semua layanan secara manual, saya membuat kelas yang menyediakan layanan, misalnya, ia mendapatkan layanan yang disuntikkan. Kelas ini kemudian dimasukkan ke dalam kelas turunan dan diteruskan ke kelas dasar.

Kelas turunan:

@Component({
    ...
    providers: [ProviderService]
})
export class DerivedComponent extends BaseComponent {
    constructor(protected providerService: ProviderService) {
        super(providerService);
    }
}

Kelas dasar:

export class BaseComponent {
    constructor(protected providerService: ProviderService) {
        // do something with providerService
    }
}

Kelas penyedia layanan:

@Injectable()
export class ProviderService {
    constructor(private _apiService: ApiService, private _authService: AuthService) {
    }
}
Leukipp
sumber
3
Masalahnya di sini adalah Anda berisiko membuat layanan "laci sampah" yang pada dasarnya hanya proxy untuk layanan Injector.
kpup
1

Daripada memasukkan layanan yang memiliki semua layanan lain sebagai dependensi, seperti:

class ProviderService {
    constructor(private service1: Service1, private service2: Service2) {}
}

class BaseComponent {
    constructor(protected providerService: ProviderService) {}

    ngOnInit() {
        // Access to all application services with providerService
        this.providerService.service1
    }
}

class DerivedComponent extends BaseComponent {
    ngOnInit() {
        // Access to all application services with providerService
        this.providerService.service1
    }
}

Saya akan melewatkan langkah ekstra ini dan cukup menambahkan suntikan semua layanan di BaseComponent, seperti:

class BaseComponent {
    constructor(protected service1: Service1, protected service2: Service2) {}
}

class DerivedComponent extends BaseComponent {
    ngOnInit() {
        this.service1;
        this.service2;
    }
}

Teknik ini mengasumsikan 2 hal:

  1. Perhatian Anda sepenuhnya terkait dengan pewarisan komponen. Kemungkinan besar, alasan Anda mendapatkan pertanyaan ini adalah karena banyaknya kode non-dry (WET?) Yang perlu Anda ulangi di setiap kelas turunan. Jika Anda ingin memanfaatkan satu titik masuk untuk semua komponen dan layanan Anda, Anda perlu melakukan langkah tambahan.

  2. Setiap komponen memperluas BaseComponent

Ada juga kerugiannya jika Anda memutuskan untuk menggunakan konstruktor dari kelas turunan, karena Anda perlu memanggil super()dan meneruskan semua dependensi. Meskipun saya tidak benar-benar melihat kasus penggunaan yang mengharuskan penggunaan constructoralih-alih ngOnInit, sangat mungkin bahwa kasus penggunaan seperti itu ada.

maxedupre
sumber
2
Kelas dasar kemudian memiliki ketergantungan pada semua layanan yang dibutuhkan anak-anaknya. ChildComponentA membutuhkan ServiceA? Sekarang ChildComponentB mendapatkan ServiceA juga.
knallfrosch
1

Dari apa yang saya pahami untuk mewarisi dari kelas dasar, pertama-tama Anda harus membuat instance-nya. Untuk membuatnya, Anda perlu meneruskan parameter yang diperlukan konstruktornya sehingga Anda meneruskannya dari anak ke orang tua melalui panggilan super () sehingga masuk akal. Injector tentu saja adalah solusi lain yang layak.

ihorbond.dll
sumber
0

Jika kelas induk telah didapat dari plugin pihak ketiga (dan Anda tidak dapat mengubah sumbernya), Anda dapat melakukan ini:

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

export MyComponent extends AbstractComponent {
  constructor(
    protected injector: Injector,
    private anotherService: AnotherService
  ) {
    super(injector.get(MyService));
  }
}

atau cara yang paling baik (tetap hanya satu parameter dalam konstruktor):

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

export MyComponent extends AbstractComponent {
  private anotherService: AnotherService;

  constructor(
    protected injector: Injector
  ) {
    super(injector.get(MyService));
    this.anotherService = injector.get(AnotherService);
  }
}
dlnsk.dll
sumber
0

HACK JELEK

Beberapa waktu lalu beberapa klien saya ingin bergabung dengan dua proyek sudut BESAR sampai kemarin (sudut v4 ke sudut v8). Project v4 menggunakan kelas BaseView untuk setiap komponen dan berisi tr(key)metode terjemahan (di v8 saya menggunakan ng-translate). Jadi untuk menghindari mengganti sistem terjemahan dan mengedit ratusan template (di v4) atau menyiapkan 2 sistem terjemahan secara paralel, saya menggunakan peretasan jelek berikut (saya tidak bangga) - di AppModulekelas saya menambahkan konstruktor berikut:

export class AppModule { 
    constructor(private injector: Injector) {
        window['UglyHackInjector'] = this.injector;
    }
}

dan sekarang AbstractComponentAnda bisa menggunakan

export class AbstractComponent {
  private myservice: MyService = null;

  constructor() {
    this.myservice = window['UglyHackInjector'].get(MyService);
  }
}
Kamil Kiełczewski
sumber
ya saya setuju itu jelek,
CMS