상속 및 종속성 주입


97

모든 서비스가 주입되어야하는 angular2 구성 요소 집합이 있습니다. 첫 번째 생각은 수퍼 클래스를 만들고 거기에 서비스를 주입하는 것이 가장 좋을 것이라고 생각했습니다. 내 구성 요소 중 하나가 해당 수퍼 클래스를 확장하지만이 접근 방식은 작동하지 않습니다.

단순화 된 예 :

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

나는 MyService각각의 모든 구성 요소 내에 주입하여이 문제를 해결할 수 있고 그 인수를 super()호출에 사용할 수 있지만 그것은 분명히 일종의 터무니없는 일입니다.

수퍼 클래스에서 서비스를 상속하도록 구성 요소를 올바르게 구성하는 방법은 무엇입니까?


이것은 중복이 아닙니다. 참조되는 질문은 이미 정의 된 수퍼 클래스에 의해 주입 된 서비스에 액세스 할 수있는 DERIVED 클래스를 구성하는 방법에 관한 것입니다. 내 질문은 파생 클래스에 서비스를 상속하는 SUPER 클래스를 구성하는 방법에 관한 것입니다. 그것은 단순히 그 반대입니다.
maxhb

귀하의 답변 (질문에 인라인)이 의미가 없습니다. 이렇게하면 Angular가 애플리케이션에 사용하는 인젝터와 독립적 인 인젝터를 만들 수 있습니다. new MyService()주입하는 대신 사용 하면 정확히 동일한 결과를 얻을 수 있습니다 (더 효율적 임 제외). 다른 서비스 및 / 또는 구성 요소간에 동일한 서비스 인스턴스를 공유하려는 경우 작동하지 않습니다. 모든 클래스는 다른 MyService인스턴스 를 갖게됩니다 .
Günter Zöchbauer

당신 말이 맞습니다. 제 코드는 myService. A 본을 피할 수 있지만 파생 클래스에 더 많은 코드를 추가 솔루션 ... 찾을 수
maxhb

주입기 주입은 여러 곳에 주입해야하는 여러 서비스가있을 때만 개선됩니다. 다른 서비스에 대한 종속성이있는 서비스를 삽입하고 getter (또는 메서드)를 사용하여 제공 할 수도 있습니다. 이렇게하면 하나의 서비스 만 주입하면되지만 서비스 집합을 사용할 수 있습니다. 귀하의 솔루션과 내가 제안한 대안은 어떤 클래스가 어떤 서비스에 의존하는지 확인하기가 더 어렵다는 단점이 있습니다. 차라리 상용구 코드를 더 쉽게 만들고 종속성에 대해 명시 할 수있는 도구 (WebStorm의 라이브 템플릿과 같은)를 만들고
싶습니다

답변:


72

각각의 모든 구성 요소 내에 MyService를 삽입하고 해당 인수를 super () 호출에 사용하여이 문제를 해결할 수 있지만 이는 분명히 일종의 터무니없는 일입니다.

터무니없는 일이 아닙니다. 이것이 생성자와 생성자 주입이 작동하는 방식입니다.

모든 주입 가능한 클래스는 종속성을 생성자 매개 변수로 선언해야하며 수퍼 클래스에도 종속성이있는 경우 하위 클래스의 생성자에도 나열되어야하며 super(dep1, dep2)호출 과 함께 수퍼 클래스에 전달되어야합니다 .

인젝터를 우회하고 종속성을 획득하는 것은 심각한 단점이 있습니다.

코드를 읽기 어렵게 만드는 종속성을 숨 깁니다.
Angular2 DI 작동 방식에 익숙한 사람의 기대에 어긋납니다.
선언적 및 명령 적 DI를 대체하는 정적 코드를 생성하는 오프라인 컴파일을 중단하여 성능을 개선하고 코드 크기를 줄입니다.


4
명확하게하기 위해서 : 저는 어디서나 필요합니다. 각 파생 클래스가 각 파생 클래스에 개별적으로 주입 할 필요없이 서비스에 액세스 할 수 있도록 해당 종속성을 내 수퍼 클래스로 이동하려고합니다.
maxhb

9
자신의 질문에 대한 답은 추악한 해킹입니다. 이 질문은 이미 어떻게해야하는지 보여줍니다. 좀 더 자세히 설명했습니다.
Günter Zöchbauer

7
정답입니다. OP는 그들 자신의 질문에 답했지만 그렇게하면서 많은 관습을 어겼습니다. 실제 단점을 나열한 것도 도움이되며 보증 할 것입니다. 저도 같은 생각을했습니다.
dudewad

6
나는 OP의 "hack"에 대해이 대답을 정말로 사용하고 싶다 (그리고 계속해서). 그러나 이것은 DRY와는 거리가 멀고 기본 클래스에 종속성을 추가하고 싶을 때 매우 고통 스럽습니다. super약 20 개 이상의 클래스 에 ctor 주입 (및 해당 호출) 을 추가해야 했으며 그 수는 앞으로 만 증가 할 것입니다. 그래서 두 가지 : 1) "대규모 코드베이스"가이 작업을 수행하는 것을보고 싶지 않습니다. 그리고 2) vim q과 vscode 를 주셔서 감사합니다ctrl+.

5
불편하다고해서 나쁜 습관은 아닙니다. 생성자는 객체 초기화를 안정적으로 수행하기가 매우 어렵 기 때문에 불편합니다. 나는 더 나쁜 관행은 "15 개의 서비스를 주입하고 6에 의해 상속되는 기본 클래스"를 필요로하는 서비스를 구축하는 것이라고 주장한다.
Günter Zöchbauer

64

업데이트 된 솔루션은 전역 인젝터를 사용하여 myService의 여러 인스턴스가 생성되는 것을 방지합니다.

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

이렇게하면 모든 파생 클래스에 MyService를 주입 할 필요없이 AbstractComponent를 확장하는 모든 클래스 내에서 MyService를 사용할 수 있습니다.

이 솔루션에는 몇 가지 단점이 있습니다 (원래 질문 아래 @ Günter Zöchbauer의 Ccomment 참조).

  • 글로벌 인젝터 주입은 여러 곳에 주입해야하는 여러 서비스가있을 때만 개선됩니다. 공유 서비스가 하나만있는 경우 파생 클래스 내에 해당 서비스를 삽입하는 것이 더 낫거나 더 쉬울 것입니다.
  • 내 솔루션과 제안 된 대안은 어떤 클래스가 어떤 서비스에 의존하는지 확인하기가 더 어렵다는 단점이 있습니다.

: Angular2의 의존성 주입의 매우 잘 쓰여진 설명은 크게 문제를 해결하기 위해 나에게 도움이 블로그 게시물을 참조 http://blog.thoughtram.io/angular/2015/05/18/dependency-injection-in-angular-을 2.html


7
이것은 실제로 어떤 서비스가 주입되는지 이해하기 어렵게 만듭니다.
Simon Dufour

this.myServiceA = injector.get(MyServiceA);등 이어야하지 않습니까?
젠슨 버튼 이벤트

9
@Gunter Zochbauer의 대답이 정답입니다. 이것은 이것을 수행하는 올바른 방법이 아니며 많은 각도 규칙을 위반합니다. 이러한 모든 인젝션 호출을 코딩하는 것이 "고통"이라는 점에서 더 간단 할 수 있지만, 대규모 코드베이스를 유지하기 위해 생성자 코드를 작성해야하는 것을 희생하고 싶다면 스스로를 쏘는 것입니다. 이 솔루션은 확장 가능하지 않으며 IMO이며 앞으로 많은 혼란스러운 버그를 유발할 것입니다.
dudewad

3
동일한 서비스의 여러 인스턴스가 발생할 위험이 없습니다. 애플리케이션의 서로 다른 분기에서 발생할 수있는 여러 인스턴스를 방지하려면 애플리케이션의 루트에서 서비스를 제공하기 만하면됩니다. 상속 변경에 서비스를 전달해도 클래스의 새 인스턴스가 생성 되지 않습니다 . @Gunter Zochbauer의 대답이 맞습니다.
ktamlyn

@maxhb Injector매개 변수를 연결할 필요가 없도록 전역으로 확장하는 방법을 탐색 한 적이 AbstractComponent있습니까? fwiw, 복잡한 생성자 체인을 피하기 위해 널리 사용되는 기본 클래스에 종속성을 주입하는 속성은 일반적인 규칙에 대한 완벽하게 유효한 예외라고 생각합니다.
quentin-starin

4

모든 서비스를 수동으로 주입하는 대신 서비스를 제공하는 클래스를 만들었습니다. 예를 들어 서비스가 주입되었습니다. 그런 다음이 클래스는 파생 클래스에 주입되고 기본 클래스로 전달됩니다.

파생 클래스 :

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

기본 클래스 :

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

서비스 제공 클래스 :

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

3
여기서 문제는 기본적으로 Injector 서비스에 대한 프록시 인 "정크 드로어"서비스를 생성 할 위험이 있다는 것입니다.
kpup 2017-10-17

1

다음과 같이 다른 모든 서비스를 종속성으로 포함하는 서비스를 삽입하는 대신 :

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

이 추가 단계를 건너 뛰고 BaseComponent에 모든 서비스를 다음과 같이 삽입하기 만하면됩니다.

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

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

이 기술은 다음 두 가지를 가정합니다.

  1. 귀하의 우려는 전적으로 구성 요소 상속과 관련이 있습니다. 대부분의 경우이 질문에 도달 한 이유는 각 파생 클래스에서 반복해야하는 비 건조 (WET?) 코드의 양이 너무 많기 때문입니다. 모든 구성 요소 및 서비스에 대해 단일 진입 점의 이점 을 얻으려면 추가 단계를 수행해야합니다.

  2. 모든 구성 요소는 BaseComponent

파생 클래스의 생성자를 사용하기로 결정하면 super()모든 종속성 을 호출 하고 전달 해야하므로 단점도 있습니다 . constructor대신을 사용해야하는 사용 사례를 실제로 보지는 못했지만 ngOnInit그러한 사용 사례가 존재할 가능성은 전적으로 가능합니다.


2
그러면 기본 클래스는 자식이 필요로하는 모든 서비스에 대한 종속성을 갖습니다. ChildComponentA에 ServiceA가 필요합니까? 이제 ChildComponentB도 ServiceA를 얻습니다.
knallfrosch

0

상위 클래스가 타사 플러그인에서 가져온 경우 (소스를 변경할 수 없음) 다음을 수행 할 수 있습니다.

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

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

또는 가장 좋은 방법 (생성자에서 하나의 매개 변수 만 유지) :

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

기본 클래스에서 상속하려면 먼저 인스턴스화해야합니다. 인스턴스화하려면 생성자 필수 매개 변수를 전달해야합니다. 따라서 super () 호출을 통해 하위에서 상위로 전달하므로 의미가 있습니다. 물론 인젝터는 또 다른 실행 가능한 솔루션입니다.


0

못생긴 해킹

얼마 전에 내 고객 중 일부가 어제 두 개의 BIG 앵귤러 프로젝트에 참여하기를 원합니다 (앵귤러 v4에서 앵귤러 v8로). Project v4는 각 구성 요소에 대해 BaseView 클래스를 사용 tr(key)하며 번역 방법을 포함합니다 (v8에서는 ng-translate를 사용합니다). 따라서 번역 시스템 전환을 피하고 수백 개의 템플릿 (v4에서)을 편집하거나 2 개의 번역 시스템을 병렬로 설정하기 위해 추악한 해킹 (자랑스럽지 않음)을 사용합니다. AppModule수업 중에 다음 생성자를 추가합니다.

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

이제 AbstractComponent사용할 수 있습니다

export class AbstractComponent {
  private myservice: MyService = null;

  constructor() {
    this.myservice = window['UglyHackInjector'].get(MyService);
  }
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.