5 ๋ถ„ ์†Œ์š”

โœ… 1๏ธโƒฃ ๋ชฉํ‘œ

์˜ค๋Š˜์€ Angular์˜ Observable์— ๋Œ€ํ•ด์„œ ๊ณต๋ถ€ํ•œ ๊ฒƒ์„ ์ •๋ฆฌํ•ด๋ณด๋ ค๊ณ  ํ•œ๋‹ค. ๋‘ ๊ฐ€์ง€ ์ผ€์ด์Šค๋ฅผ ํ†ตํ•ด์„œ Observable๋กœ ๋“ค์–ด์˜จ ๋ณ€์ˆ˜๋ฅผ ์–ด๋–ป๊ฒŒ ํ•„ํ„ฐ๋ง ํ•˜๋Š”์ง€, ๊ทธ๋ฆฌ๊ณ  ์ƒํƒœ ๊ด€๋ฆฌ๋Š” ์–ด๋–ป๊ฒŒ ํ•˜๋Š”์ง€ ์•Œ์•„๋ณด๊ฒ ๋‹ค.

  • ์„œ๋ฒ„์—์„œ ์˜ต์ €๋ฒ„๋ธ”๋กœ ๋„˜์–ด์˜จ movies$๋ฅผ ์ธํ’‹ ํ•„๋“œ์— ์ž…๋ ฅ๋œ ํ…์ŠคํŠธ๋กœ ํ•„ํ„ฐ๋งํ•˜๋Š” ๊ณผ์ •
  • ์ž๋™์ฐจ ๋ชจ๋ธ์„ ์„ ํƒํ•˜๋ฉด ํ•ด๋‹น ๋ชจ๋ธ์˜ ์ƒ‰์ƒ ๋ฆฌ์ŠคํŠธ๋ฅผ ์„ ํƒํ•  ์ˆ˜ ์žˆ๋Š” ๋“œ๋กญ ๋‹ค์šด ๋ฉ”๋‰ด๊ฐ€ ์ƒ๊ธฐ๊ณ , ์ƒ‰์ƒ ์„ ํƒ ์‹œ ์ž๋™์œผ๋กœ ํ•ด๋‹น ๋ชจ๋ธ์˜ ์ด๋ฏธ์ง€๊ฐ€ ์„ธํŒ…๋˜๋Š” ๊ณผ์ •

โœ… 2๏ธโƒฃ movies$ & filteredMovies$ ์˜ˆ์‹œ

๐Ÿ“Œ ํ๋ฆ„

readonly movies$ = this.moviesService.getMovies();
readonly titleControl = new FormControl('');
readonly yearControl = new FormControl('');

readonly filteredMovies$ = combineLatest([
  this.movies$,
  this.titleControl.valueChanges.pipe(startWith('')),
  this.yearControl.valueChanges.pipe(startWith('')),
]).pipe(
  map(([movies,title,year]) =>
    movies.filter(m => 
      (!title || m.title.toLowerCase().includes(title.toLowerCase())) &&
      (!year || m.release_date.includes(year))
    )
  )
);

์—ฌ๊ธฐ์„œ ์–ด๋ ค์› ๋˜ ์ ์„ ํ•„ํ„ฐ๋ฅผ ํ•ด์•ผํ•  ๋Œ€์ƒ๋„ ์˜ต์ €๋ฒ„๋ธ”์ด๊ณ , FormControl์„ ํ†ตํ•ด ๋“ค์–ด์˜จ title๊ณผ year ๋„ valueChanges()๋ฅผ ํ†ตํ•ด ์˜ต์ €๋ฒ„๋ธ”๋กœ ์ฒ˜๋ฆฌ๋ฅผ ํ•ด์•ผํ•œ๋‹ค๋Š” ์ ์ด์—ˆ๋‹ค. ์ฆ‰ ์„ธ ๊ฐœ์˜ ์˜ต์ €๋ฒ„๋ธ”์ด ์žˆ์—ˆ๊ณ , ์ด๊ฑธ ๋™์‹œ์— ์ฒ˜๋ฆฌ๋ฅผ ํ•ด์•ผํ–ˆ๋‹ค. ์ด๋Ÿด ๋•Œ ํ•„์š”ํ•œ ๊ฒƒ์ด combineLatest ์ด๋‹ค. ์ด combineLatest๋Š” ์˜ต์ €๋ฒ„๋ธ” ๋ณ€์ˆ˜์˜ ์ตœ์‹  ๊ฐ’๋“ค์„ ํ•˜๋‚˜๋กœ ๋ฌถ์–ด์„œ ์ฒ˜๋ฆฌ๋ฅผ ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค€๋‹ค. ๋”ฐ๋ผ์„œ ์ด combineLatest๋กœ ๋ฌถ์ธ ์„ธ ๊ฐ€์ง€ ์˜ต์ €๋ฒ„๋ธ”์— .pipe()์™€ .map() ์„ ๋™์‹œ์— ์ ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ์ด๋‹ค.

๋˜ ์—ฌ๊ธฐ์„œ ์•Œ์•„์•ผ ํ–ˆ๋˜ ์ ์ด .valueChanges()๋ฅผ ๊ทธ๋ƒฅ ์“ฐ์ง€ ์•Š๊ณ  .valueChanges.pipe(startWith('')) ๋กœ ์ผ๋‹ค๋Š” ์ ์ด๋‹ค. startWith(value) ๋Š” ํ•ด๋‹น Observable์ด ๊ตฌ๋…๋˜์ž๋งˆ์ž, ์›๋ž˜ ๊ฐ’์ด ๋ฐœ์ƒํ•˜๊ธฐ ์ „์— value๋ฅผ ๋จผ์ € ํ˜๋ ค๋ณด๋‚ด๋„๋ก ๋งŒ๋“œ๋Š” ์—ฐ์‚ฐ์ž์ด๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด:

this.titleControl.valueChanges.pipe(startWith(''));
  • valueChanges๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ํผ ์ปจํŠธ๋กค์ด ๋ฐ”๋€” ๋•Œ๋งŒ ๊ฐ’ ๋ฐœํ–‰ -> ์ฆ‰, ์•„๋ฌด ์ž…๋ ฅ๋„ ์•ˆํ•˜๋ฉด valueChanges๋Š” ๊ฐ’์„ ๋ฐœํ–‰ํ•˜์ง€ ์•Š๋Š”๋‹ค.
  • startWith('')๋ฅผ ๋ถ™์ด๋ฉด -> valueChanges๊ฐ€ ๋ฐœํ–‰๋˜๊ธฐ ์ „์— ๋นˆ ๋ฌธ์ž์—ด์„ ์ตœ์ดˆ ํ•œ ๋ฒˆ ํ˜๋ ค๋ณด๋‚ธ๋‹ค. ์ฆ‰์‹œ โ€˜โ€˜๋กœ ์‹œ์ž‘. ์˜ต์ €๋ฒ„๋ธ”์ด ํ•ญ์ƒ ์ดˆ๊ธฐ๊ฐ’์„ ๊ฐ€์ง„๋‹ค.

๊ทธ๋ ‡๋‹ค๋ฉด ์™œ ์“ฐ๋Š” ๊ฒƒ์ผ๊นŒ? combineLatest ๋˜๋Š” switchMap ํ•  ๋•Œ ๊ฐ Observable์ด ์ตœ์†Œ 1๊ฐœ ๊ฐ’์€ ์žˆ์–ด์•ผ ๋™์ž‘ํ•˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

combineLatest([
  this.movies$,
  this.titleControl.valueChanges.pipe(startWith(''))
])
  • ๋งŒ์•ฝ startWith ์—†์ด titleControl.valueChanges ๊ฐ€ ์•„๋ฌด ์ž…๋ ฅ ์—†์œผ๋ฉด? ๐Ÿ‘‰ combineLatest๋Š” ๊ฐ’์„ ๊ธฐ๋‹ค๋ฆฌ๋А๋ผ ์•„๋ฌด๊ฒƒ๋„ ์•ˆ ๋ฐœํ–‰ํ•จ!
  • startWith('') ์žˆ์œผ๋ฉด? ๐Ÿ‘‰ movies$ ๊ฐ€ emit ๋˜์ž๋งˆ์ž โ€˜ โ€˜ ๋„ ๊ฐ™์ด emit โ†’ ๋ฐ”๋กœ ํ•„ํ„ฐ ๋กœ์ง ์ˆ˜ํ–‰ ๊ฐ€๋Šฅ!

์œ„์˜ ์˜ˆ์‹œ๋Š” combineLatest๋งŒ ์ผ์ง€๋งŒ, ๋‹ค๋ฅธ ๋ฒ„์ „๋„ ์žˆ๋‹ค. ์•„๋ž˜์˜ ๋ฒ„์ „์€ combineLatest ์™€ switchMap์„ ๊ฐ™์ด ์“ด ๊ฒฝ์šฐ์ด๋‹ค.

filteredMovies$ = combineLatest([
    this.titleControl.valueChanges.pipe(
      startWith(''),
      debounceTime(300),
      distinctUntilChanged()
    ),
    this.yearControl.valueChanges.pipe(
      startWith(''),
      debounceTime(300),
      distinctUntilChanged()
    ),
  ]).pipe(
    map(
      ([title, year]) =>
        [title?.trim(), year?.toString().trim()] as [string, string]
    ),
    switchMap(([title, year]) =>
      this.movies$.pipe(
        map((movies) => {
          const hasTitle = title ? title.length > 0 : false;
          const hasYear = year ? year.length > 0 : false;
          if (!hasTitle && !hasYear) {
            return null;
          }
          return movies.filter(
            (m) =>
              m.title.toLowerCase().includes(title?.toLowerCase() || '') &&
              m.release_date.includes(year || '')
          );
        })
      )
    )
  );

์ด ์ฝ”๋“œ์—์„œ๋Š” ์ผ๋‹จ ๋‘๊ฐœ์˜ formControl ์„ combineLatest๋กœ ๋ฌถ์–ด์„œ ๊ฐ€์žฅ ์ตœ์‹  ๊ฐ’์„ .pipe()์™€ .map()์œผ๋กœ ๋ฝ‘์•„๋ƒˆ๋‹ค. ์ถ”์ถœ๋œ ๊ฐ’์€ ๋‹ค์‹œ ํ•œ ๋ฒˆ trim()์„ ํ†ตํ•ด์„œ ๋นˆ ๊ณต๊ฐ„์„ ์—†์• ์คฌ๋‹ค. ์ด ๊ฐ’๋“ค์ด ์ด์ œ movies$ ๋ผ๋Š” ์˜ต์ €๋ฒ„๋ธ” ํ•„ํ„ฐ์— ์“ฐ์—ฌ์•ผ ํ•œ๋‹ค. ์ฆ‰, .pipe()์™€ .map()์„ ์“ฐ๋Š” ์ฃผ๋œ ์˜ต์ €๋ฒ„๋ธ”์ด ๋ฐ”๋€Œ์–ด์•ผ ํ•œ๋‹ค๋Š” ์…ˆ์ด๋‹ค. ์ด ๋•Œ ์šฐ๋ฆฌ๋Š” switchMap์„ ์“ธ ์ˆ˜ ์žˆ๋‹ค. formControl()์˜ pipe() ์—์„œ title๊ณผ year๋ฅผ ๊ฐ–๊ณ  movies$ ์˜ต์ €๋ฒ„๋ธ”๋กœ ์Šค์œ„์น˜๋ฅผ ํ•œ๋‹ค. ์ด๋•Œ map()๊ณผ switchMap์€ โ€˜,โ€™ ๋กœ ๊ฐ™์ด ์“ฐ๋ฉด ๋œ๋‹ค. switchMap์—์„œ ์šฐ๋ฆฌ๋Š” title๊ณผ year๋ฅผ ๊ฐ–๊ณ  ์ฃผ๋œ ์˜ต์ €๋ฒ„๋ธ”์„ movies$๋กœ ๋ฐ”๊ฟ”์ค€๋‹ค. ๊ทธ ๋‹ค์Œ์—๋Š” ์œ„์˜ ์˜ˆ์™€ ๋˜‘๊ฐ™์ด .pipe() ์™€ .map()์œผ๋กœ movies๋ฅผ ํ•„ํ„ฐํ•ด์ฃผ๋ฉด ๋œ๋‹ค.

โœ”๏ธ ํ•ต์‹ฌ

  • ์—ฌ๋Ÿฌ๊ฐœ์˜ ์˜ต์ €๋ฒ„๋ธ”์„ ๋™์‹œ์— ์ฒ˜๋ฆฌํ•ด์•ผ ํ•  ๋•Œ๋Š”combineLatest + map
  • ์ฃผ ์˜ต์ €๋ฒ„๋ธ”์„ ๋ฐ”๊พธ๊ณ  ์‹ถ์„ ๋•Œ๋Š” ํ˜„์žฌ ์˜ต์ €๋ฒ„๋ธ” ํŒŒ์ดํ”„ ์•ˆ์—์„œ switchmap
  • combineLatest๋ฅผ ์“ธ ๋•Œ valueChanges๊ฐ€ ์žˆ์œผ๋ฉด ๊ผญ .pipe(startWith(''))๋กœ formControl ์— ์ดˆ๊ธฐ๊ฐ’

โœ… 3๏ธโƒฃ currentCar$ & currentCarColor$

์ด๋ฒˆ ์ผ€์ด์Šค๋Š” ์•ž์˜ ์ผ€์ด์Šค์™€๋Š” ์•ฝ๊ฐ„ ๋‹ค๋ฅด๋‹ค. ๋จผ์ € ์ฐจ ๋ชจ๋ธ์„ ๊ณ ๋ฅด๋Š” ๋“œ๋กญ๋‹ค์šด ๋ฉ”๋‰ด๊ฐ€ ์žˆ๋‹ค. ์—ฌ๊ธฐ์„œ ์ฐจ ๋ชจ๋ธ์„ ์„ ํƒํ•˜๊ฒŒ ๋˜๋ฉด ํ•ด๋‹น ๋ชจ๋ธ์˜ ์ƒ‰์ƒ ๋ฉ”๋‰ด๊ฐ€ ๋“œ๋กญ๋‹ค์šด์œผ๋กœ ๋‚˜์˜จ๋‹ค. ๋ชจ๋ธ์˜ ์ƒ‰์ƒ๊นŒ์ง€ ์„ ํƒํ•˜๊ฒŒ ๋˜๋ฉด ์„ ํƒ๋œ ์ƒ‰์ƒ๊ณผ ๋ชจ๋ธ์˜ ์ด๋ฏธ์ง€๊ฐ€ ๋‚˜์˜ค๊ฒŒ ๋œ๋‹ค.

์ด๊ฑธ ๋งŒ์•ฝ์— signal๋กœ ์ฒ˜๋ฆฌ๋ฅผ ํ•œ๋‹ค๊ณ  ํ–ˆ์„ ๋•Œ, valueChanges()์—์„œ ๋‚˜์˜จ ๊ฐ’์„ ๊ตฌ๋…ํ•ด์„œ ์‹œ๊ทธ๋„๋กœ ์ƒํƒœ๋ฅผ ์ €์žฅํ•  ๊ฒƒ์ด๋‹ค. ํ•˜์ง€๋งŒ ์šฐ๋ฆฌ๋Š” ์ด๊ฒƒ์„ ์˜ต์ €๋ฒ„๋ธ”๋กœ ์ฒ˜๋ฆฌ๋ฅผ ํ•ด์•ผํ•œ๋‹ค. ์˜ต์ €๋ฒ„๋ธ”๋กœ ์ฒ˜๋ฆฌ๋ฅผ ํ•  ๋•Œ ์‹œ๊ทธ๋„๊ณผ ๋น„์Šทํ•˜๊ฒŒ ์ƒํƒœ๋ฅผ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋Š” ๊ฒƒ์ด BehaviorSubject()์ด๋‹ค.

์„œ๋น„์Šค ๋‹จ์— BehavorSubject()๋กœ ์ฐจ ๋ชจ๋ธ์˜ ์ƒํƒœ๋ฅผ ์ €์žฅํ•  ์ˆ˜ ์žˆ๋Š” โ€œ์ €์žฅ์†Œโ€๋ฅผ ๋งŒ๋“ค์–ด์ค€๋‹ค.

currentCarSubject = new BehaviorSubject<CarModel | null>(null);

์œ„์˜ ์ฝ”๋“œ๋ฅผ ๋ณด๋ฉด ์•Œ ์ˆ˜ ์žˆ๋“ฏ์ด BehaviorSubject๋ฅผ ๋งŒ๋“ค ๋•Œ๋Š” ๋ฐ˜๋“œ์‹œ ์ดˆ๊ธฐ๊ฐ’์„ ์ค˜์•ผํ•œ๋‹ค. ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ๋ผ๋ฉด ํ•ด๋‹น ๋ณ€์ˆ˜์˜ ํƒ€์ž…๊นŒ์ง€ ๊ฐ™์ด ์ง€์ •ํ•ด์•ผ ํ•œ๋‹ค. BehaviorSubject๋Š” ๊ฐ€์žฅ ์ตœ๊ทผ์˜ ๊ฐ’์„ ํ•ญ์ƒ ๊ธฐ์–ตํ•œ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๊ฐ’์„ ์ง€์ •ํ•  ๋•Œ, ์ฆ‰ setState๋ฅผ ํ•˜๊ณ  ์‹ถ์„ ๋• setCurrentCar() ํ•จ์ˆ˜๋ฅผ ๋งŒ๋“ค์–ด์ฃผ๊ณ  BehaviorSubject์˜ next() ๋ฉ”์„œ๋“œ๋กœ ๊ฐ’์„ ๊ฐฑ์‹ ํ•œ๋‹ค. ๊ฐฑ์‹ ํ•˜๋ฉด ์ž๋™์œผ๋กœ ๊ตฌ๋…์ž์—๊ฒŒ ์•Œ๋ ค์ค€๋‹ค.

๋”ฐ๋ผ์„œ ์šฐ๋ฆฌ๋Š” ์„œ๋น„์Šค๋‹จ์— ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ƒํƒœ๊ด€๋ฆฌ ๋ณ€์ˆ˜์™€ ์ƒํƒœ ์„ธํŒ… ํ•จ์ˆ˜๋“ค์„ ๋งŒ๋“ค์–ด ๋†“์„ ์ˆ˜ ์žˆ๋‹ค.

@Injectable({ providedIn: 'root' })
export class ConfiguratorService {
  private currentCarSubject = new BehaviorSubject<CarModel | null>(null);
  readonly currentCar$ = this.currentCarSubject.asObservable();

  private currentCarColorSubject = new BehaviorSubject<Color | null>(null);
  readonly currentCarColor$ = this.currentCarColorSubject.asObservable();

  private currentCarImageSubject = new BehaviorSubject<string | null>(null);
  readonly currentCarImage$ = this.currentCarImageSubject.asObservable();

  setCurrentCar(car: CarModel) {
    this.currentCarSubject.next(car);
  }

  setCurrentCarColor(color: Color) {
    this.currentCarColorSubject.next(color);
  }

  setCurrentCarImage(url: string) {
    this.currentCarImageSubject.next(url);
  }
}

์ด๊ฑธ ์ด์ œ ์ปดํฌ๋„ŒํŠธ์™€ ์—ฐ๋™์„ ํ•ด์•ผํ•œ๋‹ค.

๐Ÿ“Œ ์ปดํฌ๋„ŒํŠธ ์—ฐ๋™

export class Step1Component implements OnInit {
  readonly configuratorService = inject(ConfiguratorService);
  readonly allModels$ = this.configuratorService.allModels$;

  readonly carModelControl = new FormControl('');
  readonly carColorControl = new FormControl('');

  ngOnInit() {
    // ๋ชจ๋ธ ์„ ํƒ
    this.carModelControl.valueChanges.subscribe(modelDescription => {
      this.allModels$.subscribe(models => {
        const selected = models.find(m => m.description === modelDescription);
        if (selected) {
          this.configuratorService.setCurrentCar(selected);
          const firstColor = selected.colors[0];
          if (firstColor) {
            this.carColorControl.setValue(firstColor.code);
            this.configuratorService.setCurrentCarColor(firstColor);
          }
        }
      });
    });

    // ์ƒ‰์ƒ ์„ ํƒ
    this.carColorControl.valueChanges.subscribe(code => {
      const car = this.configuratorService.currentCarSubject.getValue();
      const color = car?.colors.find(c => c.code === code);
      if (color && car) {
        this.configuratorService.setCurrentCarColor(color);
        const url = `https://site.com/${car.code}/${color.code}.jpg`;
        this.configuratorService.setCurrentCarImage(url);
      }
    });
  }
}

์•ž์˜ movies ์˜ˆ์‹œ์™€ ๋‹ค๋ฅธ ์ ์€ ๋“œ๋กญ๋‹ค์šด ๋ฉ”๋‰ด๋กœ ์„ ํƒ๋œ ๊ฐ’์„ ์šฐ๋ฆฌ๋Š” ์ €์žฅํ•˜์—ฌ ๋‹ค๋ฅธ ๊ฐ’์„ ๋งŒ๋“ค์–ด ๋‚ด๊ธฐ ์œ„ํ•ด ์จ์•ผ ํ•œ๋‹ค๋Š” ๊ฒƒ์ด๋‹ค. ๊ทธ๋ž˜์„œ ๋”ฐ๋กœ combineLatest์™€ ๊ฐ™์€ ๋ฉ”์„œ๋“œ๋Š” ํ•„์š”๊ฐ€ ์—†๊ณ , valueChantes๋ฅผ ๊ณง๋ฐ”๋กœ ๊ตฌ๋…์„ ํ•˜๋ฉด ๋œ๋‹ค. ๊ทธ๋ž˜์„œ ngOnInit() ๋ฉ”์„œ๋“œ์— ๋„ฃ์–ด๋†“๊ณ  ์“ฐ๋ฉด ๋œ๋‹ค. ์ฒซ ๋ฒˆ์งธ ๋ชจ๋ธ ์„ ํƒ์—์„œ๋Š” ๋ชจ๋ธ์ด ์„ ํƒ๋˜๊ณ  ๋‚˜๋ฉด, ๊ทธ ์„ ํƒ๋œ ๋ชจ๋ธ์„ setCurrentCar ๋ฉ”์„œ๋“œ์— ๋„ฃ์–ด์„œ ์ƒํƒœ๋ฅผ ์ €์žฅํ•ด์ค€๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๋ชจ๋ธ์ด ์„ ํƒ๋˜์ž๋งˆ์ž ํ•ด๋‹น ์ƒ‰์ƒ ๋ฆฌ์ŠคํŠธ์˜ ๊ฐ€์žฅ ์ฒซ๋ฒˆ์งธ ์ƒ‰์ด ์ž๋™ ์„ ํƒ์ด ๋˜์–ด์•ผ ํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— firstColor ๋ฅผ ์ถ”์ถœํ•˜์—ฌ carColorControl์˜ ๊ฐ’์„ setValue๋กœ ์„ธํŒ…ํ•ด์ฃผ๊ณ  setCurrentCarColor ๋ฉ”์„œ๋“œ์— ๋„ฃ์–ด์„œ ์ƒ‰์ƒ ์ƒํƒœ๋„ ์ •ํ•ด์ค€๋‹ค.

๊ทธ ๋‹ค์Œ ์ƒ‰์ƒ์„ ๊ณ ๋ฅผ ๋•Œ formControl์—์„œ ์ƒ‰์ƒ ์ฝ”๋“œ๋งŒ ๊ตฌ๋…์ด ๋œ๋‹ค๋Š” ์‚ฌ์‹ค์„ ์ฃผ์˜ํ•ด์•ผ ํ–ˆ๋‹ค. ์ƒ‰์ƒ ์ฝ”๋“œ๋งŒ ์˜ค๊ธฐ ๋•Œ๋ฌธ์—, ์šฐ๋ฆฌ๋Š” ํ˜„์žฌ ์„ ํƒ๋œ ์ฐจ ๋ชจ๋ธ์„ ๊ฐ–๊ณ ์™€์„œ ์ฐจ ๋ชจ๋ธ์˜ ์ƒ‰์ƒ ๋ฆฌ์ŠคํŠธ ์ค‘์—์„œ ์„ ํƒํ•œ ์ฝ”๋“œ์™€ ๋งž๋Š” ์ƒ‰์„ ์ฐพ์•„๋‚ด์•ผ ํ–ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๊ทธ ์ƒ‰์„ ๋‹ค์‹œ setCurrentCarColor ๋ฉ”์„œ๋“œ๋กœ ์„ธํŒ…์„ ๋‹ค์‹œ ํ•ด์ค˜์•ผ ํ–ˆ๋‹ค. ์ด๋•Œ ํ•„์š”ํ–ˆ๋˜ ๊ฒƒ์ด .getValue() ๋ผ๋Š” ๋ฉ”์„œ๋“œ์˜€๋‹ค. BehaviorSubject์—์„œ ํ˜„์žฌ ์ƒ‰์ƒ์„ ์˜ต์ €๋ฒ„๋ธ” ์—†์ด ๊ฐ’์„ ๊ฐ€์ ธ์˜ฌ๋•Œ ์‚ฌ์šฉํ•˜๋Š” ๋ฉ”์„œ๋“œ๊ฐ€ .getValue() ์ด๋‹ค. ์ด๊ฒƒ์„ ํ†ตํ•ด์„œ ํ˜„์žฌ ์„ ํƒ๋œ ์ฐจ ๋ชจ๋ธ์„ ๊ฐ–๊ณ ์˜ค๊ณ , ๊ทธ๋ฆฌ๊ณ  ์ƒ‰์ƒ์„ ์ถ”์ถœํ•ด์„œ imageUrl๋ฅผ ๋งŒ๋“ค์–ด currentCarImage์— ์ €์žฅ์„ ํ•  ์ˆ˜ ์žˆ์—ˆ๋˜ ๊ฒƒ์ด๋‹ค.


โœ… ์ „์ฒด ํ•ต์‹ฌ ์š”์•ฝ

โœ… ํ•ต์‹ฌ ์š”์•ฝ

1๏ธโƒฃ ์—ฌ๋Ÿฌ Observable ๋™๊ธฐ ์ฒ˜๋ฆฌ๋Š” combineLatest

  • movies$์™€ FormControl์˜ valueChanges๋ฅผ ํ•˜๋‚˜์˜ ์ŠคํŠธ๋ฆผ์œผ๋กœ ๋ฌถ์–ด ์ตœ์‹ ๊ฐ’์„ ๋™์‹œ์— ์‚ฌ์šฉ.
  • startWith('')๋กœ ์ดˆ๊ธฐ๊ฐ’ ๋ณด์žฅํ•ด์„œ combineLatest๊ฐ€ ํ•ญ์ƒ ๋™์ž‘ํ•˜๋„๋ก ์„ค๊ณ„.

2๏ธโƒฃ ์‹ค์‹œ๊ฐ„ ํ•„ํ„ฐ๋ง์€ map ๋˜๋Š” switchMap์œผ๋กœ

  • combineLatest + map โ†’ ํด๋ผ์ด์–ธํŠธ ํ•„ํ„ฐ๋ง.
  • combineLatest + switchMap โ†’ ๊ฐ’ ๋ณ€ํ™” ์‹œ๋งˆ๋‹ค ์ฃผ ์ŠคํŠธ๋ฆผ(movies$)๋กœ ์ „ํ™˜ํ•ด์„œ ์ฒ˜๋ฆฌ.
  • debounceTime, distinctUntilChanged๋กœ ๋ถˆํ•„์š”ํ•œ ์—ฐ์‚ฐ ๋ฐฉ์ง€.

3๏ธโƒฃ ์ƒํƒœ ๊ด€๋ฆฌ๋Š” BehaviorSubject๋กœ

  • BehaviorSubject๋Š” ์ดˆ๊ธฐ๊ฐ’ ํ•„์ˆ˜.
  • ๊ฐ€์žฅ ์ตœ๊ทผ ๊ฐ’์„ ๋ฉ”๋ชจ๋ฆฌ์— ์œ ์ง€, next()๋กœ ๊ฐฑ์‹ , getValue()๋กœ ํ˜„์žฌ ๊ฐ’ ๋™๊ธฐ ์กฐํšŒ ๊ฐ€๋Šฅ.
  • ์‹œ๊ทธ๋„ ๋Œ€์‹  Observable + BehaviorSubject ํŒจํ„ด์œผ๋กœ ์ „ํ™˜ ๊ฐ€๋Šฅ.

4๏ธโƒฃ FormControl๊ณผ ์ƒํƒœ๋ฅผ ์ˆ˜๋™ ๋™๊ธฐํ™”

  • ๋“œ๋กญ๋‹ค์šด ์„ ํƒ ๋ณ€ํ™” โ†’ FormControl.setValue()๋กœ UI ๊ฐ’ ๋™๊ธฐํ™”.
  • ์„ ํƒํ•œ ๋ชจ๋ธ๊ณผ ์ƒ‰์ƒ์€ BehaviorSubject๋กœ ์ƒํƒœ ์ €์žฅ.
  • ์ €์žฅ๋œ ์ƒํƒœ๋ฅผ ์ด๋ฏธ์ง€ URL ์ƒ์„ฑ ๋“ฑ ํŒŒ์ƒ ๋ฐ์ดํ„ฐ์— ํ™œ์šฉ.

5๏ธโƒฃ ๊ตฌ๋… ์œ„์น˜์™€ ๊ตฌ์กฐ

  • ์—ฌ๋Ÿฌ ๊ฐ’์ด ํ•„์š”ํ•˜๋ฉด combineLatest๋กœ ๋ฌถ์–ด ํ•œ ๋ฒˆ์— ์ฒ˜๋ฆฌ.
  • ๋“œ๋กญ๋‹ค์šด ๊ฐ™์€ ๋‹จ์ผ ์„ ํƒ์€ valueChanges๋ฅผ ngOnInit์—์„œ ์ง์ ‘ ๊ตฌ๋….
  • ํ•„์š”์— ๋”ฐ๋ผ switchMap์œผ๋กœ ์ŠคํŠธ๋ฆผ ์ „ํ™˜ ์„ค๊ณ„.

๐Ÿ‘ ์ด ๋‚ด์šฉ์€ ์‹ค์ „์—์„œ ์ž์ฃผ ๊ฒช๋Š” ๋ฌธ์ œ์ด๋‹ˆ, ์•ž์œผ๋กœ ๋น„์Šทํ•œ ์ƒํ™ฉ์—์„œ ๋ฐ”๋กœ ๋– ์˜ฌ๋ฆด ์ˆ˜ ์žˆ๋„๋ก ๊ธฐ์–ตํ•ด๋‘์ž.

ํƒœ๊ทธ:

์นดํ…Œ๊ณ ๋ฆฌ:

์—…๋ฐ์ดํŠธ:

๋Œ“๊ธ€๋‚จ๊ธฐ๊ธฐ