본문으로 바로가기
제품데스크톱 도구TASTECAD도면 확인TASTEFILES파일 관리TASTEFTP파일 전송TASTESSH원격 작업공간TASTEZIP압축과 해제개발 도구TASTECODERust 통합 개발 환경TASTESTUDIO통합 개발·테스트 환경TASTEETL데이터 통합(ETL)서버TASTEREMOTE원격 데스크톱 서버TASTEGEO공간정보 서버TASTEMAIL메일 작업공간시작하기다운로드문서구매모든 제품
마켓플레이스
소식
개발소식오늘의 개발 소식개발 문서프로그래밍 언어RustTypeScriptJavaScriptPythonGoC#JavaCC++KotlinDart프런트엔드 프레임워크Next.jsReactVue.js · NuxtSvelteKitAngular서버 프레임워크Spring BootDjangoFastAPIASP.NET CoreNestJS · Express앱 · 데스크톱 프레임워크TauriFlutterReact NativeElectron알고리즘알고리즘 기초알고리즘 핵심알고리즘 심화실무 알고리즘
커뮤니티프로젝트개발 로그프롬프트AI 도구 이슈쇼케이스커뮤니티 안내
협업기술 전수홈페이지 구축데스크톱 앱 구축릴리스·결제 파이프라인판매 파트너문의
서비스원격 데스크톱설치 패키지 커스텀
TASTEDEV
제품

데스크톱 도구

TASTECAD복잡한 도면을 가볍게 열다TASTEFILES필요한 파일만, 가장 빠르게TASTEFTP파일이 오가는 가장 단정한 길TASTESSH서버와 나 사이, 더 단단한 연결TASTEZIP더 빠르게 묶고, 더 안전하게 풀다

개발 도구

TASTECODERust 앱을 편집 · 빌드 · 디버깅 · 테스트까지 한 창에서TASTESTUDIO편집부터 원격 테스트와 승인 기반 AI 수정까지TASTEETL값을 보며 데이터를 옮깁니다

서버

TASTEREMOTE우리 회사 서버로 운영하는 원격 데스크톱TASTEGEOGeoServer 호환 공간정보 서버를 Rust로 다시 세웁니다TASTEMAIL메일의 주도권을 다시 내 손에

시작하기

다운로드모든 제품 최신 판문서제품 사용법구매구독 · 영구 라이선스모든 제품모든 제품 한눈에
모든 제품
마켓플레이스소식
개발

소식

오늘의 개발 소식언어 · 프레임워크 · GitHub · AI 공식 발표개발 문서언어 · 프레임워크 · 알고리즘 안내서

프로그래밍 언어

RustTypeScriptJavaScriptPythonGoC#JavaCC++KotlinDart

프레임워크

프런트엔드 프레임워크Next.jsReactVue.js · NuxtSvelteKitAngular
서버 프레임워크Spring BootDjangoFastAPIASP.NET CoreNestJS · Express
앱 · 데스크톱 프레임워크TauriFlutterReact NativeElectron

알고리즘

알고리즘 기초복잡도 분석 · 정렬 · 탐색 · 기본 자료구조 · 트리와 힙알고리즘 핵심그래프 탐색 · 최단 경로와 최소 신장 트리 · 동적 계획법 · 탐욕 알고리즘 · 분할 정복 · 백트래킹알고리즘 심화문자열 알고리즘 · 수학과 정수론 · 비트 연산 · 구간 트리 · 유니온 파인드실무 알고리즘코딩 면접 풀이 전략 · LRU 캐시 · 레이트 리미터 · 일관된 해시
개발 문서 전체 보기
커뮤니티
프로젝트빌더와 작업 발견개발 로그결정과 시행착오프롬프트재사용 가능한 지시AI 도구 이슈Claude · Codex · Gemini 문제와 우회법쇼케이스완성된 결과커뮤니티 안내공개하고 함께 만드는 방식
협업
기술 전수Rust·egui 네이티브 앱 만들기홈페이지 구축10개 언어, 계정, 문서·다운로드데스크톱 앱 구축Rust·egui, WebView 없이릴리스·결제 파이프라인서명 · MSI·deb·rpm·AppImage · 해시판매 파트너소개하고 결제액의 30% 받기문의구축 · 제휴 · 투자 문의
서비스
원격 데스크톱브라우저로 내 컴퓨터에 접속설치 패키지 커스텀필요한 기능만, 설정은 미리
구매
KO
한국어EnglishDeutschEspañolFrançaisItalianoPortuguês (Brasil)日本語简体中文繁體中文
로그인
로그인
TASTEDEV

작은 도구부터, 더 좋은 개발까지.

support@tastedev.netTASTEDEV 후원하기
제품제품군구매문서다운로드마켓플레이스고객 지원
커뮤니티프로젝트개발 로그프롬프트AI 도구 이슈쇼케이스
정책개인정보처리방침이용약관구독 약관환불 정책커뮤니티 운영 정책
계정로그인가입하기
방문자
오늘
012345678901234567890123456789012345678901234567890123456789
어제
012345678901234567890123456789012345678901234567890123456789
누적 조회
0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
상호
지엑스소프트
대표자
최영구
사업자등록번호
853-44-00368
주소
인천광역시 미추홀구 경인로 351 9층 V24
전화
+82-10-4404-1523
이메일
whitecalvin@tastedev.net
개인정보 보호책임자
최영구

© 2026 TASTEDEV제품 정보는 현재 검증된 개발 소스를 기준으로 합니다.출시·고도화 중

Angular 문서
Angular
Angular 시작하기프로젝트 구조핵심 개념데이터와 상태테스트배포
토론

Angular 안내서 · 4/6

데이터와 상태

대부분의 Angular 앱은 HTTP로 데이터를 불러오고, 일부 상태를 클라이언트에 두며, 폼으로 입력을 받습니다. 이 장에서는 HttpClient, 시그널 기반 상태, Angular 코드에서 자주 만나는 RxJS 기초, 반응형 폼을 다룹니다.

HttpClient 설정

app.config.ts에서 HttpClient를 한 번 등록합니다. withFetch()를 쓰면 브라우저의 Fetch API로 요청하며, 서버 사이드 렌더링을 쓸 때 권장되는 설정입니다.

import { ApplicationConfig } from '@angular/core';
import { provideHttpClient, withFetch } from '@angular/common/http';

export const appConfig: ApplicationConfig = {
  providers: [provideHttpClient(withFetch())],
};

데이터 서비스

HTTP 호출은 서비스에 모아 둡니다. HttpClient 메서드는 Observable을 돌려줍니다. Observable은 누군가 구독할 때 요청을 보내고, 응답 JSON을 파싱한 값을 내보냅니다.

import { HttpClient, HttpParams } from '@angular/common/http';
import { Injectable, inject } from '@angular/core';
import { Observable } from 'rxjs';

export interface Todo {
  id: number;
  title: string;
  completed: boolean;
}

@Injectable({ providedIn: 'root' })
export class TodoApi {
  private readonly http = inject(HttpClient);
  private readonly baseUrl = 'https://jsonplaceholder.typicode.com/todos';

  list(limit = 10): Observable<Todo[]> {
    const params = new HttpParams().set('_limit', limit);
    return this.http.get<Todo[]>(this.baseUrl, { params });
  }

  create(title: string): Observable<Todo> {
    return this.http.post<Todo>(this.baseUrl, { title, completed: false });
  }
}

get<Todo[]>의 타입 인자는 TypeScript에 기대하는 모양을 알려 줄 뿐, 실행 중에 응답을 검증하지는 않습니다.

Observable을 시그널로

toSignal()은 Observable을 구독해 가장 최근 값을 시그널로 내놓습니다. 컴포넌트가 사라지면 구독도 자동으로 해제됩니다.

import { Component, inject } from '@angular/core';
import { toSignal } from '@angular/core/rxjs-interop';
import { TodoApi } from './todo-api';

@Component({
  selector: 'app-todo-list',
  template: `
    @for (todo of todos(); track todo.id) {
      <p>{{ todo.title }}</p>
    } @empty {
      <p>Loading...</p>
    }
  `,
})
export class TodoList {
  private readonly api = inject(TodoApi);
  protected readonly todos = toSignal(this.api.list(), { initialValue: [] });
}

시그널로 공유 상태 만들기

장바구니처럼 여러 컴포넌트가 함께 쓰는 상태는 시그널을 가진 서비스 하나로 충분한 경우가 많습니다. 컴포넌트는 공개된 시그널을 읽기만 하고, 값은 서비스만 바꿉니다.

import { Injectable, computed, signal } from '@angular/core';

export interface CartItem { id: number; price: number; qty: number; }

@Injectable({ providedIn: 'root' })
export class CartStore {
  private readonly items = signal<CartItem[]>([]);

  readonly all = this.items.asReadonly();
  readonly total = computed(() => this.items().reduce((sum, item) => sum + item.price * item.qty, 0));

  add(item: CartItem) {
    this.items.update((list) => [...list, item]);
  }
}

규모가 큰 앱에서는 NgRx 같은 상태 관리 라이브러리를 쓰기도 하지만, 그 전까지는 시그널 서비스로 상당 부분을 해결할 수 있습니다.

RxJS 기초

RxJS는 지금도 HttpClient, 라우터, 폼의 중심에 있습니다. debounceTime은 입력이 잠시 멈출 때까지 기다리고, distinctUntilChanged는 같은 값의 반복을 건너뛰며, switchMap은 새 값이 오면 이전 요청을 취소하고, catchError는 실패를 대체 값으로 바꿉니다. 직접 구독할 때는 takeUntilDestroyed()로 컴포넌트가 사라질 때 구독을 끝냅니다.

import { Component, inject, signal } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { HttpClient } from '@angular/common/http';
import { FormControl, ReactiveFormsModule } from '@angular/forms';
import { catchError, debounceTime, distinctUntilChanged, of, switchMap } from 'rxjs';

@Component({
  selector: 'app-search',
  imports: [ReactiveFormsModule],
  template: `
    <input [formControl]="query" placeholder="Search" />
    @for (name of results(); track name) {
      <p>{{ name }}</p>
    }
  `,
})
export class Search {
  private readonly http = inject(HttpClient);
  protected readonly query = new FormControl('', { nonNullable: true });
  protected readonly results = signal<string[]>([]);

  constructor() {
    this.query.valueChanges.pipe(
      debounceTime(300),
      distinctUntilChanged(),
      switchMap((q) => this.http.get<string[]>('/api/search', { params: { q } }).pipe(catchError(() => of([])))),
      takeUntilDestroyed(),
    ).subscribe((list) => this.results.set(list));
  }
}

반응형 폼

반응형 폼은 폼 모델을 TypeScript로 정의하며, 컨트롤에 타입과 검증기가 붙습니다. 템플릿은 formGroup과 formControlName으로 모델에 연결합니다.

import { Component, inject } from '@angular/core';
import { NonNullableFormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';

@Component({
  selector: 'app-signup',
  imports: [ReactiveFormsModule],
  template: `
    <form [formGroup]="form" (ngSubmit)="submit()">
      <input type="email" formControlName="email" />
      @if (form.controls.email.invalid && form.controls.email.touched) {
        <small>Enter a valid email address.</small>
      }
      <input type="password" formControlName="password" />
      <button type="submit" [disabled]="form.invalid">Sign up</button>
    </form>
  `,
})
export class Signup {
  private readonly fb = inject(NonNullableFormBuilder);
  protected readonly form = this.fb.group({
    email: ['', [Validators.required, Validators.email]],
    password: ['', [Validators.required, Validators.minLength(8)]],
  });

  submit() {
    if (this.form.valid) console.log(this.form.getRawValue());
  }
}

정리

  • provideHttpClient()를 한 번 등록하고, Observable을 돌려주는 서비스에서 API를 호출합니다.
  • toSignal()로 Observable을 템플릿에서 쓰기 좋은 시그널로 바꿉니다.
  • 비공개 시그널과 공개 computed() 값을 가진 서비스는 간단한 공유 저장소가 됩니다.
  • switchMap, debounceTime, catchError, takeUntilDestroyed()로 흔한 비동기 상황을 처리합니다.
  • 반응형 폼은 검증기를 갖춘 타입 있는 폼 모델을 제공합니다.
이 장에 대해 질문이 있나요?
이전 장핵심 개념다음 장테스트

댓글 0개

인기 댓글순최신순

로그인 · 로그인하면 댓글을 남길 수 있습니다.