วิธีการอัดฉีดบริการในชั้นเรียน (ไม่ใช่ส่วนประกอบ)


91

ฉันต้องการที่จะฉีดให้บริการในชั้นเรียนที่มีไม่ได้องค์ประกอบ

ตัวอย่างเช่น:

บริการ

import {Injectable} from '@angular/core';
@Injectable()
export class myService {
  dosomething() {
    // implementation
  }
}

ห้องเรียนของฉัน

import { myService } from './myService'
export class MyClass {
  constructor(private myservice:myService) {

  }
  test() {
     this.myservice.dosomething();
  }
}

วิธีนี้ไม่ได้ผล (ฉันคิดว่าเพราะMyClassยังไม่ได้สร้างอินสแตนซ์)

มีวิธีอื่นในการใช้บริการในชั้นเรียน (ไม่ใช่ส่วนประกอบ) หรือไม่? หรือคุณคิดว่าการออกแบบโค้ดของฉันไม่เหมาะสม (เพื่อใช้บริการในคลาสที่ไม่ใช่ส่วนประกอบ)

ขอขอบคุณ.

คำตอบ:


57

การฉีดใช้งานได้กับคลาสที่สร้างอินสแตนซ์โดย Angulars dependency injection (DI) เท่านั้น

  1. คุณต้อง
    • เพิ่ม@Injectable()ไปMyClassและ
    • ให้MyClassเหมือนproviders: [MyClass]ในคอมโพเนนต์หรือ NgModule

เมื่อคุณฉีดที่MyClassใดที่หนึ่งMyServiceอินสแตนซ์จะถูกส่งผ่านไปยังMyClassเมื่ออินสแตนซ์โดย DI (ก่อนที่จะฉีดในครั้งแรก)

  1. อีกทางเลือกหนึ่งคือการกำหนดค่าหัวฉีดแบบกำหนดเองเช่น
constructor(private injector:Injector) { 
  let resolvedProviders = ReflectiveInjector.resolve([MyClass]);
  let childInjector = ReflectiveInjector.fromResolvedProviders(resolvedProviders, this.injector);

  let myClass : MyClass = childInjector.get(MyClass);
}

วิธีนี้myClassจะเป็นMyClassอินสแตนซ์ที่สร้างขึ้นโดย Angulars DI และmyServiceจะถูกฉีดเข้าไปMyClassเมื่อสร้างอินสแตนซ์
ดูเพิ่มเติมการพึ่งพาจาก Injector ด้วยตนเองภายในคำสั่ง

  1. อีกวิธีหนึ่งคือการสร้างอินสแตนซ์ด้วยตัวคุณเอง:
constructor(ms:myService)
let myClass = new MyClass(ms);

2
ฉันคิดว่าการฉีดบริการในชั้นเรียนแบบสแตนด์อโลนเป็นการต่อต้านรูปแบบ
tomexx

11
แน่นอน แต่บางครั้งก็ยังสมเหตุสมผลและเป็นเรื่องดีเสมอที่จะรู้ว่าอะไรเป็นไปได้
GünterZöchbauer

ทำไมต้องสร้างหัวฉีดใหม่ในตัวสร้าง? ทำไมไม่ใช้อันที่ฉีดล่ะ? หากคุณกำลังจะสร้างใหม่ก็ไม่มีเหตุผลที่จะต้องฉีดตั้งแต่แรก
smac89

ตัวใหม่คือผู้ฉีดยาเด็กที่มีผู้ให้บริการเพิ่มเติม หากผู้ให้บริการที่เพิ่มไปยังหัวฉีดลูกขึ้นอยู่กับผู้ให้บริการที่มีให้ที่ส่วนประกอบหลักหรือที่ระดับโมดูล DI จะสามารถแก้ไขได้ทั้งหมดโดยอัตโนมัติ ดังนั้นจึงมีสถานการณ์ที่เหมาะสม
GünterZöchbauer

1
@TimothyPenz ขอบคุณสำหรับการแก้ไข ในตัวอย่างprivateนี้ไม่จำเป็นเนื่องจากinjectorไม่ได้ใช้ภายนอกตัวสร้างดังนั้นจึงไม่จำเป็นต้องเก็บข้อมูลอ้างอิงไว้ในฟิลด์
GünterZöchbauer

30

ไม่ใช่คำตอบโดยตรงสำหรับคำถาม แต่ถ้าคุณกำลังอ่าน SO นี้ด้วยเหตุผลที่ว่านี้อาจช่วยได้ ...

สมมติว่าคุณใช้ng2-translateและคุณต้องการให้User.tsชั้นเรียนมีมันจริงๆ คุณคิดได้ทันทีคือใช้ DI ใส่เข้าไปคุณกำลังทำ Angular แต่นั่นเป็นการคิดที่มากเกินไปคุณสามารถส่งผ่านมันไปในตัวสร้างของคุณหรือทำให้เป็นตัวแปรสาธารณะที่คุณตั้งค่าจากคอมโพเนนต์ (โดยที่คุณน่าจะเป็น DI)

เช่น:

import { TranslateService } from "ng2-translate";

export class User {
  public translateService: TranslateService; // will set from components.

  // a bunch of awesome User methods
}

จากส่วนประกอบที่เกี่ยวข้องกับผู้ใช้บางส่วนที่แทรก TranslateService

addEmptyUser() {
  let emptyUser = new User("", "");
  emptyUser.translateService = this.translateService;
  this.users.push(emptyUser);
}

หวังว่าสิ่งนี้จะช่วยคนที่อยู่ตรงนั้นอย่างฉันที่กำลังจะเขียนโค้ดยากขึ้นมากเพราะบางครั้งเราก็ฉลาดเกินไป =]

(หมายเหตุ: เหตุผลที่คุณอาจต้องการตั้งค่าตัวแปรแทนที่จะทำให้เป็นส่วนหนึ่งของวิธีการสร้างของคุณคือคุณอาจมีบางกรณีที่คุณไม่จำเป็นต้องใช้บริการดังนั้นการที่คุณต้องส่งผ่านไปเสมอจะหมายถึงการนำเข้าเพิ่มเติม / รหัสที่ไม่เคยใช้จริงๆ)


และอาจจะสร้างtranslateServiceget / set ที่getส่งข้อผิดพลาดที่มีความหมายแทนข้อยกเว้น NullReference หากคุณลืมตั้งค่า
Simon_Weaver

1
บางทีมันสมเหตุสมผลกว่าที่จะทำให้ translateService เป็นพารามิเตอร์ที่จำเป็นในตัวสร้างคลาส? รูปแบบนี้สอดคล้องกับรูปแบบ DI imho มากกว่า
Icycool

16

นี่เป็นเรื่องแฮ็ก( มาก ) แต่ฉันคิดว่าฉันจะแบ่งปันวิธีแก้ปัญหาของฉันด้วย โปรดทราบว่าสิ่งนี้จะใช้ได้กับบริการ Singleton เท่านั้น (ฉีดที่รูทแอปไม่ใช่คอมโพเนนต์!) เนื่องจากใช้งานได้นานพอ ๆ กับแอปพลิเคชันของคุณและมีเพียงอินสแตนซ์เดียวเท่านั้น

อันดับแรกในบริการของคุณ:

@Injectable()
export class MyService {
    static instance: MyService;
    constructor() {
        MyService.instance = this;
    }

    doSomething() {
        console.log("something!");
    }
}

จากนั้นในชั้นเรียนใด ๆ :

export class MyClass {
    constructor() {
        MyService.instance.doSomething();
    }
}

วิธีนี้ดีหากคุณต้องการลดความยุ่งเหยิงของโค้ดและไม่ได้ใช้บริการที่ไม่ใช่ซิงเกิลตัน


แต่คุณจะใช้ MyService ใน MyClass โดยไม่มีการประกาศใด ๆ ในตัวสร้างได้อย่างไร?
Akhil V

@AkhilV คุณเพียงแค่นำเข้า MyService เหมือนที่คุณจะนำเข้าคลาสอื่น ๆ จากนั้นคุณสามารถเรียกใช้เมธอดตามที่อธิบาย
Kilves

13

locator.service.ts

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

export class ServiceLocator {
    static injector: Injector;
}

app.module.ts

@NgModule({ ... })

export class AppModule {
    constructor(private injector: Injector) {
        ServiceLocator.injector = injector;
    }
 }

poney.model.ts

export class Poney {

    id: number;
    name: string;
    color: 'black' | 'white' | 'brown';

    service: PoneyService = ServiceLocator.injector.get(PoneyService); // <--- HERE !!!

    // PoneyService is @injectable and registered in app.module.ts
}

1
ไม่แน่ใจว่าแนวทางปฏิบัติที่ดีที่สุดสำหรับสถานการณ์ของ OP คืออะไร แต่คำตอบนี้ดูเหมือนง่ายกว่าคำตอบชั้นนำมาก การย้ายการinjector.get()โทรไปยังโมดูลจะใช้งานได้หรือไม่ (สมมติว่าบริการไร้สัญชาติเพื่อให้หลายชั้นเรียนสามารถ "แชร์" อินสแตนซ์เดียวกันได้)
G0BLiN

ฉันคิดว่าโค้ดนั้นจะจบลงด้วยการที่อินสแตนซ์ poney ทั้งหมดที่ใช้บริการ poney เดียวกัน คุณคิดอย่างไร?
Julien

Julien - นั่นเป็นผลลัพธ์ที่ยอมรับได้ (อันที่จริงเป็นที่ต้องการ) สำหรับสถานการณ์ของฉัน - ลองนึกถึงสิ่งต่างๆเช่นตัวตรวจสอบอินพุตตัวจัดการสิทธิ์ผู้ใช้หรือบริการโลคัลไลเซชันซึ่งคุณต้องการฟังก์ชันเดียวกันในแอป แต่ไม่จำเป็นต้องมีอินสแตนซ์เฉพาะสำหรับแต่ละอินสแตนซ์ ผู้บริโภคแต่ละรายของบริการ
G0BLiN

มีปัญหาในการพยายามทำให้สิ่งนี้ใช้ได้ผลในการทดสอบจัสมิน จะกำหนดการตั้งค่าการทดสอบได้อย่างไร? ServiceLocator.injector ยังคงคืนค่าว่างแม้ว่าฉันจะฉีด "PoneyService" ลงใน TestBed?
GGizmos

3

หากวิธีการบริการของคุณเป็นฟังก์ชันที่บริสุทธิ์วิธีแก้ปัญหานี้ได้อย่างชัดเจนคือการมีสมาชิกคงที่ในบริการของคุณ

บริการของคุณ

import {Injectable} from '@angular/core';
@Injectable()
export class myService{
  public static dosomething(){
    //implementation => doesn't use `this`
  }
}

ชั้นเรียนของคุณ

export class MyClass{
  test(){
     MyService.dosomething(); //no need to inject in constructor
  }
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.