내용으로 건너뛰기
다양한 유형의 Angular 서비스 제공자 단순화

다양한 유형의 Angular 서비스 제공자 단순화

Angular 서비스 공급자는 종속성 값의 런타임 버전을 제공합니다. 따라서 서비스를 주입할 때 Angular 인젝터는 공급자를 확인하여 서비스 인스턴스를 생성합니다.

6min read

Angular 서비스 공급자는 종속성 값의 런타임 버전을 제공합니다. 따라서 서비스를 주입할 때 Angular 인젝터는 공급자를 확인하여 서비스 인스턴스를 생성합니다.

이는 제공자가 컴포넌트, 파이프, 명령어에 런타임에 어떤 인스턴스나 값을 주입할지 결정합니다. 여기에는 많은 전문 용어가 포함되어 있으니, 제공자 유형의 목적을 이해하기 위해 먼저 서비스를 만드는 것부터 시작해 보겠습니다.  예를 들어, ErrorService 라는 서비스가 있다고 가정해 봅시다. 이 서비스는 오류 메시지를 기록하는 역할만 합니다.

import { Injectable } from '@angular/core';
 
@Injectable()
export class ErrorService {
 
    logError(message: string) {
        console.log(message);
    }
}

이제 아래 목록과 같이 이 서비스를 컴포넌트에서 사용할 수 있습니다:

import { Component } from '@angular/core';
import { ErrorService } from './errormessage.service';
 
@Component({
    selector: 'app-root',
    template: `
  <input [(ngModel)]='err' type='text'/>
  <button (click)='setError()'>set error</button>
  `,
    providers: [ErrorService]
})
export class AppComponent {
    constructor(private errorservice: ErrorService) { }
    err: string;
    setError() {
        this.errorservice.logError(this.err);
    }
 
}

우리는 서비스를 가져오고, 이를 제공자 배열에 전달한 뒤, 컴포넌트의 생성자에 주입합니다. 버튼 클릭 시 서비스 메서드를 호출하고 있으며, 콘솔에서 오류 메시지가 전달되는 것을 확인할 수 있습니다. 아주 간단하죠?

여기서 Angular 컴포넌트(또는 모듈)의 제공자 배열에 전달되는 값을 바탕으로 런타임에 어떤 인스턴스를 주입해야 할지 찾습니다.

"공급자는 특정 토큰의 객체가 어떻게 생성될 수 있는지를 결정한다."

예를 들어, 아래와 같이 컴포넌트나 모듈의 프로바이저 배열에 서비스 이름을 전달할 때:

providers: [ErrorService]

여기서 Angular 토큰 값 ErrorService를 사용하고, 토큰 ErrorService에 대해 ErrorService 클래스의 객체를 생성합니다. 위 문법은 아래 문법의 지름길입니다:

providers: [{
    provide: ErrorService, useClass: ErrorService
}]

provide 속성은 의 키 역할을 하는 토큰을 담고 있습니다.

  1. 의존성 값을 찾는 방법.
  2. registering the dependency.

두 번째 속성(네 가지 유형으로 구성됨)은 의존성 값을 생성하는 데 사용됩니다. 두 번째 매개변수에는 네 가지 가능한 값이 있으며, 다음과 같습니다:

  1. useClass
  2. 사용 기존
  3. useValue
  4. useFactory

방금 useClass의 예시를 봤습니다. 이제 더 나은 오류 기록을 위한 새로운 클래스인 NewErrorService가 있다고 가정해 봅시다.

import { Injectable } from '@angular/core';
 
@Injectable()
export class NewErrorService {
 
    logError(message: string) {
        console.log(message);
        console.log('logged by DJ');
    }
 
}

사용 기존

이제 ErrorService 인스턴스 대신 NewErrorService 인스턴스를 주입해야 합니다.  또한, 이상적으로는 두 클래스가 동일한 인터페이스를 구현해야 하며, 이는 동일한 메서드 시그니처를 가지지만 구현이 다른 방식이어야 합니다.  이제 토큰 ErrorService에 대해 NewErrorService 인스턴스를 주입하고자 합니다. 아래와 같이 useClass를 사용하여 수행할 수 있습니다:

providers: [
    NewErrorService,
    { provide: ErrorService, useClass: NewErrorService }
]

위 접근법의 문제는 NewErrorService 인스턴스가 두 개나 존재한다는 점입니다. 이 문제는 useExisting을 사용하여 해결할 수 있습니다.

providers: [
    NewErrorService,
    { provide: ErrorService, useExisting: NewErrorService }
]

이제 NewErrorService 인스턴스는 하나만 존재하며, 토큰 ErrorService 인스턴스로 NewErrorService 인스턴스가 생성됩니다.

NewErrorService를 사용하도록 컴포넌트를 수정해 보겠습니다.

import { Component } from '@angular/core';
import { ErrorService } from './errormessage.service';
import { NewErrorService } from './newerrormessage.service';
 
@Component({
    selector: 'app-root',
    template: `
  <input [(ngModel)]='err' type='text'/>
  <button (click)='setError()'>set error</button>
  <button (click)='setnewError()'>Set New eroor</button>
  `,
    providers: [
        NewErrorService,
        { provide: ErrorService, useExisting: NewErrorService }
    ]
})
export class AppComponent {
    constructor(private errorservice: ErrorService, private newerrorservice: NewErrorService) { }
    err: string;
    setError() {
        this.errorservice.logError(this.err);
    }
    setnewError() {
        this.newerrorservice.logError(this.err);
    }
 
}

여기서는 시연을 위해 컴포넌트에 서비스를 주입하지만, 모듈 수준에서 서비스를 사용하려면 모듈 자체에 인젝트할 수 있습니다. 이제 set error 버튼을 클릭하면 NewErrorService가 호출됩니다.

useValue

useClass와 useExisting 모두 특정 토큰에 대해 주입할 서비스 클래스 인스턴스를 생성하지만, 때로는 인스턴스를 생성하는 대신 값을 직접 전달하고 싶을 때가 있습니다. 따라서 클래스 인스턴스 대신 readymade object를 전달하고 싶다면, 아래 목록과 같이 useValue를 사용할 수 있습니다:

providers: [
    {
        provide: ErrorService, useValue: {
            logError: function (err) {
                console.log('inhjected directly ' + err);
            }
        }
    }

여기서는 useValue를 사용해 readymade 객체를 주입합니다. 그래서 토큰 ErrorService의 경우, Angular 객체를 주입합니다.

useFactory

런타임 전까지 어떤 인스턴스가 필요한지 모를 수도 있습니다. 마지막 순간까지 알지 못하는 정보를 바탕으로 의존성을 만들어야 합니다. 예를 들어, 사용자가 로그인되어 있으면 ServiceA 인스턴스를, 사용자가 로그인하지 않았다면 ServiceB 인스턴스를 만들어야 할 수도 있다고 가정해 봅시다. 로그인한 사용자 정보는 마지막 시점까지 제공되지 않을 수 있으며, 애플리케이션 사용 중 변경될 수 있습니다.

의존성 정보가 동적일 때는 useFactory를 사용해야 합니다. 앞서 예시에서 사용한 두 가지 서비스를 살펴보겠습니다:

  1. ErrorMessageService
  2. NewErrorMessageService

특정 조건에서 토큰 ErrorService의 경우, ErrorMessageService 또는 newErrorMessageService 인스턴스 중 하나를 원합니다. 아래 목록에서 보듯이 useFactory를 사용하면 이를 달성할 수 있습니다:

providers: [
    {
        provide: ErrorService, useFactory: () => {
            let m = 'old'; // this value can change
            if (m === 'old') {
                return new ErrorService();
            } else {
                return new NewErrorService();
            }
        }
    }
]

여기서는 매우 하드코딩된 조건을 취했습니다. 특정 토큰에 대해 동적으로 인스턴스를 생성하는 복잡한 조건이 있을 수 있습니다.  또한, useFactory에서는 의존도를 넘을 수 있다는 점을 기억하세요. 따라서 ErrorService 토큰 인스턴스를 생성하기 위해 LoginService에 의존한다고 가정하세요. 이 절차를 위해 LoginService는 아래 목록에 나타난 deps 배열입니다:

providers: [
    {
        provide: ErrorService, useFactory: () => {
            let m = 'old'; // this value can change
            if (m === 'old') {
                return new ErrorService();
            } else {
                return new NewErrorService();
            }
        },
        deps: [LoginService]
    }
]

다음은 Angular 내 제공자와 협력하는 네 가지 방법입니다.  이 글이 도움이 되길 바랍니다. 읽어주셔서 감사합니다.  이 글이 마음에 드셨다면 꼭 공유해 주세요. 또한, 아직 Infragistics Ignite UI for Angular Components를 확인하지 않으셨다면 꼭 확인해 보세요! 웹 앱을 더 빠르게 코딩할 수 있도록 30+ 자료 기반 Angular 컴포넌트가 있습니다.

데모 요청