개발 공부 일지/Angular

서비스 / 의존성 주입 / 옵저버블 Observable / http 통신

yelimu 2025. 5. 25. 22:07

https://nayotutorial.tistory.com/23 포스팅으로 학습한 내용입니다.


 

서비스 : 외부와 통신, 데이터 공유 등의 비즈니스 로직을 맡는 클래스 

https://www.angular.kr/guide/architecture-services

컴포넌트에는 화면에 사용되는 프로퍼티나 데이터 바인딩에 사용하는 메소드만 정의하는 것이 좋으며, 컴포넌트는 템플릿이 렌더링된 뷰와 모델 을 정의하는 애플리케이션 로직을 중개하는 역할만 하는 것이 좋습니다.

서버에서 데이터를 가져오는 로직이나 사용자의 입력을 검증하는 로직, 콘솔에 로그를 출력하는 로직은 컴포넌트에 구현하지 않고 서비스에게 맡기는 것이 좋습니다. 왜냐하면 이런 기능을 의존성으로 주입할 수 있는 서비스 클래스에 정의하면, 여러 컴포넌트가 이 기능을 공통으로 사용할 수 있기 때문입니다. 

 

컴포넌트는 movie-list.component.ts 와 같이 명명했다면, 서비스는 movie.service.ts 로 명명한다

//movie.service.ts

import { Injectable } from '@angular/core';
import { movie } from './movie.model';
import { catchError, Observable, tap, throwError } from 'rxjs';
import { HttpClient, HttpErrorResponse } from '@angular/common/http';

@Injectable({
  providedIn: 'root',
})
export class MovieService {
  private movieUrl = 'assets/json/movies.json';

  constructor(private http: HttpClient) {}

  getMovies(): Observable<movie[]> {
    return this.http.get<movie[]>(this.movieUrl).pipe(
      tap((data) => console.log(JSON.stringify(data))),
      catchError(this.handleError)
    );
  }

  private handleError(error: HttpErrorResponse) {
    let errorMessage = '';
    if (error.error instanceof ErrorEvent) {
      errorMessage = `error : ${error.error.message}`;
    } else {
      errorMessage = `return code : ${error.status}, message : ${error.message}`;
    }
    return throwError(() => new Error(errorMessage));
  }
}

 

@Injectable 데코레이터로 클래스를 데코레이트 해준다 

prividedIn 속성값을 'root' 로 하면 애플리케이션의 모든 컴포넌트 또는 다른 서비스에서 액세스가 가능해진다. (루트(최상위) 인젝터에 서비스 등록)

@Component({
  selector: 'app-movies',
  templateUrl: './movie-list.component.html',
  styleUrls: ['./movie-list.component.scss'],
  providers: [MovieService],  // 추가!
})

모든 클래스에는 생성자 함수 constructor 가 있다

-> 명시적으로 정의된 생성자가 없을때는 암시적 생성자가 사용된다

-> 서비스 인스턴스와 같은 의존성 주입을 위해서 명시적 생성자가 필요하다 

-> 컴포넌트가 생성될 때 실행되므로 주로 초기화에 사용되며, 부작용(사이드이펙트)가 있거나 실행시간이 오래 걸리는 코드는 쓰지 않는 것이 좋다 

 

@Component({
  selector: 'app-movies',
  templateUrl: './movie-list.component.html',
  styleUrls: ['./movie-list.component.scss'],
  providers: [MovieService],
})
export class MovieListComponent implements OnInit {
...
  // 서비스 의존성 주입
  constructor(private movieService: MovieService) {}

  public ngOnInit(): void {
    // this.movies = this.movieService.getMovies();
    // this.filteredMovies = this.movies;

    this.subscription = this.movieService.getMovies().subscribe({
      next: (data) => {
        this.movies = data;
        this.filteredMovies = this.movies;
      },
      error: (error) => console.log('에러', error),
      complete: () => console.log('complete'),
    });
  }

  public ngOnDestroy(): void {
    this.subscription.unsubscribe();
  }
}
private _movieService;

constructor(movieService : MovieService){
  this._movieService = movieService
}

// 위 코드를 아래와 같이 작성 - 프라이빗 멤버 변수를 선언과 동시에 초기화
// private와 같은 접근자 키워드를 생성자 매개변수에 추가하기만 하면 됩니다.
위와 같이 작성 함으로 변수를 선언하고, 매개변수를 정의하고, 변수를 매개변수로 할당까지 포함된 처리가 가능합니다.

constructor(private movieService: MovieService) {}

 


 

옵저버블 : 이벤트 처리, 비동기 프로그래밍, 여러 값을 연달아 처리할때 사용 / 하나 이상의 값을 여러 시간에 걸쳐 발행하는 객체 

next, error, complete 세 종류의 알람을 처리하는 핸들러로 이루어진 객체 

 

RxJS : 옵저버블을 활용해서 비동기 프로그래밍을 구현하는 라이브러리 


  getMovies(): Observable<movie[]> {
    return this.http.get<movie[]>(this.movieUrl).pipe(
      tap((data) => console.log(JSON.stringify(data))),
      catchError(this.handleError)
    );
  }

pipe : RxJS에서 연산자를 연결하는데 사용한다. 여러 연산자를 조합하여 데이터를 처리하는 파이프라인을 만들 수 있다 

 

- RxJS 연산자 - 

tap : 옵저버블 데이터 흐름을 가로채서 부수적인 작업 (사이드 이펙트) 수행할때 사용한다. 데이터를 변환하지않고 그대로 다음 연산자에게 전달 -> 디버깅을 위해 중간에 출력하거나, 데이터 흐름 기록(로깅) 등 을 위해 사용한다.

catchError : 옵저버블 에러 처리를 위해 사용하는 연산자 -> 새로운 옵저버블 반환하거나 에러를 다시 throw하여 상위로 전달 

 

밑틴! RxJS에서 사용하는 pipe와, 템플릿에서 데이터 변환이나 포맷팅을 위해 사용하는 pipe (lowercase, date, currency 등) 랑 다른거라고 한다 !!  💢
템플릿의 pipe는 실제 값에 영향을 미치지 않는 반면, RxJS의 pipe는 데이터를 처리하는 것이라서 실제 값에 영향을 미침.. 내가 미침..