Kalıtım ve bağımlılık ekleme


102

Hepsine biraz servis enjekte etmesi gereken bir dizi angular2 bileşenim var. İlk düşüncem, bir süper sınıf yaratmanın ve hizmeti oraya enjekte etmenin en iyisi olacağıydı. Bileşenlerimden herhangi biri bu süper sınıfı genişletir ancak bu yaklaşım işe yaramaz.

Basitleştirilmiş örnek:

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
  }
}

Bunu MyServiceher bir bileşenin içine enjekte ederek çözebilirim ve bu argümanı super()arama için kullanabilirim ama bu kesinlikle bir tür saçmalık.

Bileşenlerimi süper sınıftan bir hizmeti devralmaları için doğru şekilde nasıl düzenleyebilirim?


Bu bir kopya değil. Başvurulan soru, önceden tanımlanmış bir süper sınıf tarafından enjekte edilen bir hizmete erişebilen bir DERIVED sınıfının nasıl oluşturulacağıyla ilgilidir. Sorum, türetilmiş sınıflara bir hizmeti miras alan bir SUPER sınıfının nasıl oluşturulacağıyla ilgili. Bu sadece tam tersi.
maxhb

Cevabınız (sorunuzda satır içi) bana mantıklı gelmiyor. Bu şekilde, uygulamanız için Angular'ın kullandığı enjektörden bağımsız bir enjektör oluşturursunuz. new MyService()Enjekte etmek yerine kullanmak size tam olarak aynı sonucu verir (daha verimli olması dışında). Aynı hizmet örneğini farklı hizmetler ve / veya bileşenler arasında paylaşmak istiyorsanız bu çalışmayacaktır. Her sınıf başka bir MyServiceörnek alacak .
Günter Zöchbauer

Tamamen haklısın, benim kodum çok sayıda örnek oluşturacak myService . Bundan kaçınan ancak türetilmiş sınıflara daha fazla kod ekleyen bir çözüm
buldum

Enjektörün enjekte edilmesi, yalnızca birçok yerde enjekte edilmesi gereken birkaç farklı hizmet olduğunda bir gelişmedir. Ayrıca diğer hizmetlere bağımlılıkları olan bir hizmeti de enjekte edebilir ve bunları bir alıcı (veya yöntem) kullanarak sağlayabilirsiniz. Bu şekilde yalnızca bir hizmeti enjekte etmeniz gerekir, ancak bir dizi hizmeti kullanabilirsiniz. Sizin çözümünüz ve benim önerdiğim alternatifin dezavantajı, hangi sınıfın hangi hizmete bağlı olduğunu görmeyi zorlaştırıyor.
Standart

Yanıtlar:


73

Bunu, MyService'i her bileşene enjekte ederek ve bu argümanı super () çağrısı için kullanarak çözebilirim ama bu kesinlikle bir tür saçmadır.

Saçma değil. Yapıcılar ve yapıcı enjeksiyonu bu şekilde çalışır.

Enjekte edilebilir her sınıf, bağımlılıkları yapıcı parametreleri olarak bildirmek zorundadır ve eğer üst sınıfın da bağımlılıkları varsa, bunların da alt sınıfın yapıcısında listelenmesi ve super(dep1, dep2)çağrı ile üst sınıfa iletilmesi gerekir .

Bir enjektörün etrafından dolaşmanın ve bağımlılıklar edinmenin zorunlu olarak ciddi dezavantajları vardır.

Kodun okunmasını zorlaştıran bağımlılıkları gizler.
Angular2 DI'nin nasıl çalıştığını bilen birinin beklentilerini ihlal ediyor.
Performansı artırmak ve kod boyutunu azaltmak için bildirime dayalı ve zorunlu DI yerine statik kod üreten çevrimdışı derlemeyi bozar.


6
Sadece açıklığa kavuşturmak için: Her yerde ihtiyacım var. Bu bağımlılığı süper sınıfıma taşımaya çalışıyorum, böylece HER türetilmiş sınıf, her türetilmiş sınıfa ayrı ayrı enjekte etmeye gerek kalmadan hizmete erişebilir.
maxhb

9
Kendi sorusunun cevabı çirkin bir hile. Soru zaten bunun nasıl yapılması gerektiğini gösteriyor. Biraz daha detaylandırdım.
Günter Zochbauer

7
Bu cevap doğrudur. OP kendi sorularını yanıtladı, ancak bunu yaparken pek çok konvansiyonu bozdu. Gerçek dezavantajları listelemiş olmanız da yararlıdır ve buna kefil olacağım - ben de aynı şeyi düşünüyordum.
dudewad

6
Bu cevabı OP'nin "hack" yerine gerçekten kullanmak istiyorum (ve kullanmaya devam ediyorum). Ancak bunun DRY'den uzak göründüğünü ve temel sınıfa bir bağımlılık eklemek istediğimde çok acı verici olduğunu söylemeliyim. superYaklaşık 20+ sınıfa ctor enjeksiyonlarını (ve ilgili çağrıları) eklemem gerekiyordu ve bu sayı sadece gelecekte artacak. Yani iki şey: 1) "Büyük bir kod tabanı" nın bunu yaptığını görmekten nefret ederim; ve 2) vim qve vscode için Tanrıya şükürctrl+.

6
Rahatsız olması, kötü bir uygulama olduğu anlamına gelmez. Oluşturucular elverişsizdir çünkü nesne başlatmayı güvenilir bir şekilde yapmak çok zordur. En kötü uygulamanın, "15 hizmet enjekte eden ve 6 tarafından miras alınan bir temel sınıfa" ihtiyaç duyan bir hizmet oluşturmak olduğunu iddia ediyorum.
Günter Zöchbauer

66

Güncellenen çözüm, global enjektör kullanılarak birden fazla myService örneğinin oluşturulmasını engeller.

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();
  }
}

Bu, MyService'in, türetilmiş her sınıfa MyService'i enjekte etmeye gerek kalmadan AbstractComponent'i genişleten herhangi bir sınıf içinde kullanılabilmesini sağlayacaktır.

Bu çözümün bazı dezavantajları vardır (orijinal sorumun altında @ Günter Zöchbauer'den Ccomment'a bakın):

  • Global enjektörün enjekte edilmesi, yalnızca birçok yere enjekte edilmesi gereken birkaç farklı hizmet olduğunda bir gelişmedir. Yalnızca bir paylaşılan hizmetiniz varsa, bu hizmeti türetilmiş sınıf (lar) içine enjekte etmek muhtemelen daha iyi / daha kolaydır.
  • Benim çözümüm ve onun önerdiği alternatif, hangi sınıfın hangi hizmete bağlı olduğunu görmeyi zorlaştırmaları gibi iki dezavantaja sahiptir.

Angular2'de bağımlılık enjeksiyonunun çok iyi yazılmış bir açıklaması için, sorunu çözmeme büyük ölçüde yardımcı olan bu blog gönderisine bakın: http://blog.thoughtram.io/angular/2015/05/18/dependency-injection-in-angular- 2.html


7
Bu, gerçekte hangi hizmetlerin enjekte edildiğini anlamayı oldukça zorlaştırır.
Simon Dufour

1
Olması gerekmiyor this.myServiceA = injector.get(MyServiceA);mu?
jenson-button-event

10
@Gunter Zochbauer'in cevabı doğru olanıdır. Bu, bunu yapmanın doğru yolu değildir ve birçok açısal geleneği bozar. Tüm bu enjeksiyon çağrılarını kodlamanın bir "acı" olması daha basit olabilir, ancak büyük bir kod tabanını sürdürebilmek için kurucu kodu yazmak zorunda kalmaktan vazgeçmek istiyorsanız, o zaman kendinizi ayağınıza vuruyorsunuz. Bu çözüm ölçeklenebilir değil, IMO ve yolda birçok kafa karıştırıcı hataya neden olacak.
dudewad

3
Aynı hizmetin birden fazla örneği olma riski yoktur. Uygulamanın farklı dallarında oluşabilecek birden çok örneği önlemek için uygulamanızın kökünde bir hizmet sağlamanız yeterlidir. Servisleri miras değişikliğinden aşağıya aktarma gelmez sınıfların yeni örneklerini oluşturun. @Gunter Zochbauer'in cevabı doğru.
ktamlyn

@maxhb hiç genişletmeyi keşfettin mi Injector herhangi bir parametreyi zincirlemek zorunda kalmamak için küresel olarakAbstractComponent ? fwiw, karmaşık yapıcı zincirlemesini önlemek için yaygın olarak kullanılan bir temel sınıfa özellik enjekte etmenin olağan kural için mükemmel bir istisna olduğunu düşünüyorum.
quentin-stariçinde

5

Tüm hizmetleri manuel olarak enjekte etmek yerine, hizmetleri sağlayan bir sınıf yarattım, örneğin, hizmetleri enjekte ediyor. Bu sınıf daha sonra türetilmiş sınıflara enjekte edilir ve temel sınıfa aktarılır.

Türetilmiş sınıf:

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

Temel sınıf:

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

Hizmet sağlayan sınıf:

@Injectable()
export class ProviderService {
    constructor(private _apiService: ApiService, private _authService: AuthService) {
    }
}

3
Buradaki sorun, aslında Enjektör hizmeti için sadece bir vekil olan bir "önemsiz çekmece" hizmeti oluşturma riskini almanızdır.
kpup

1

Diğer tüm hizmetleri bağımlılık olarak içeren bir hizmeti enjekte etmek yerine, şöyle:

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
    }
}

Bu ekstra adımı atlar ve BaseComponent'teki tüm hizmetleri enjekte ederdim, şöyle:

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

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

Bu teknik 2 şeyi varsayar:

  1. Endişeniz tamamen bileşen mirasıyla ilgilidir. Büyük olasılıkla, bu soruya gelmenizin nedeni, türetilmiş her sınıfta tekrarlamanız gereken çok büyük miktardaki kuru olmayan (WET?) Koddur. Tüm bileşenleriniz ve hizmetleriniz için tek bir giriş noktasından yararlanmak istiyorsanız, fazladan bir adım atmanız gerekecektir.

  2. Her bileşen, BaseComponent

Türetilmiş bir sınıfın yapıcısını kullanmaya karar verirseniz super(), tüm bağımlılıkları aramanız ve iletmeniz gerekeceğinden, bir dezavantaj da vardır . constructorBunun yerine yerine kullanımını gerektiren bir kullanım durumu görmesem de ngOnInit, böyle bir kullanım durumunun var olması tamamen mümkündür.


2
Bu durumda, temel sınıf, çocuklarının ihtiyaç duyduğu tüm hizmetlere bağımlıdır. ChildComponentA'nın ServiceA'ya ihtiyacı var mı? Şimdi ChildComponentB de ServiceA'yı alıyor.
knallfrosch

1

Anladığım kadarıyla temel sınıftan miras almak için önce onu somutlaştırmanız gerekir. Bunu somutlaştırmak için yapıcı için gerekli parametreleri iletmeniz gerekir, böylece bunları bir super () çağrısı aracılığıyla çocuktan ebeveyne iletirsiniz, böylece mantıklı olur. Elbette enjektör başka bir uygun çözümdür.


0

Ebeveyn sınıfı 3. taraf eklentiden alınmışsa (ve kaynağı değiştiremezseniz), bunu yapabilirsiniz:

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

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

veya en iyisi (yapıcıda yalnızca bir parametre kalın):

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);
  }
}

0

Çirkin HACK

Bir süre önce, müşterimden bazıları dün iki BÜYÜK açısal projeye katılmak istiyor (açısal v4'ten açısal v8'e). Project v4, her bileşen için BaseView sınıfını kullanır ve tr(key)çeviriler için yöntem içerir (v8'de ng-translate kullanıyorum). Bu nedenle, çeviri sistemini değiştirmekten ve yüzlerce şablonu (v4'te) düzenlemekten veya 2 çeviri sistemini paralel olarak kurmadan kaçınmak için çirkin hack'i takip ediyorum (bundan gurur duymuyorum) - AppModulesınıfa aşağıdaki kurucuyu ekliyorum:

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

ve şimdi AbstractComponentkullanabilirsin

export class AbstractComponent {
  private myservice: MyService = null;

  constructor() {
    this.myservice = window['UglyHackInjector'].get(MyService);
  }
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.