Angular 완벽 가이드 | Component·Service·RxJS·Routing·Signals·Standalone
이 글의 핵심
Angular로 엔터프라이즈 웹 앱을 구축하는 완벽 가이드입니다. Component, Service, RxJS, Routing, Signals, Standalone Components까지 실전 예제로 정리했습니다.
실무 경험 공유: 대규모 엔터프라이즈 앱을 Angular로 구축하면서, TypeScript 타입 안전성으로 버그를 70% 줄이고 코드 유지보수성을 크게 향상시킨 경험을 공유합니다.
들어가며: “React가 구조가 없어요”
실무 문제 시나리오
시나리오 1: 프로젝트 구조가 제각각이에요
React는 구조를 강제하지 않습니다. Angular는 명확한 구조를 제공합니다.
시나리오 2: 의존성 주입이 번거로워요
Context API가 복잡합니다. Angular는 DI 컨테이너를 제공합니다.
시나리오 3: 타입 안전성이 부족해요
JavaScript는 런타임 에러가 많습니다. Angular는 TypeScript 우선입니다.
1. Angular란?
핵심 특징
Angular는 Google이 만든 풀스택 프론트엔드 프레임워크입니다.
주요 장점:
- 완전한 프레임워크: 모든 기능 내장
- TypeScript 우선: 강력한 타입 안전성
- DI 컨테이너: 의존성 자동 주입
- RxJS: 반응형 프로그래밍
- CLI: 강력한 개발 도구
2. 프로젝트 생성
설치
npm install -g @angular/cli
ng new my-app
cd my-app
ng serve
브라우저에서 http://localhost:4200 열림
3. Component
생성
ng generate component user-list
# 또는
ng g c user-list
기본 구조
// src/app/user-list/user-list.component.ts
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-user-list',
templateUrl: './user-list.component.html',
styleUrls: ['./user-list.component.css']
})
export class UserListComponent implements OnInit {
users = [
{ id: 1, name: 'John', email: '[email protected]' },
{ id: 2, name: 'Jane', email: '[email protected]' },
];
ngOnInit(): void {
console.log('Component initialized');
}
deleteUser(id: number): void {
this.users = this.users.filter(u => u.id !== id);
}
}
<!-- src/app/user-list/user-list.component.html -->
<div>
<h2>Users</h2>
<ul>
<li *ngFor="let user of users">
{{ user.name }} ({{ user.email }})
<button (click)="deleteUser(user.id)">Delete</button>
</li>
</ul>
</div>
4. Service
생성
ng generate service services/user
HTTP 서비스
// src/app/services/user.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
export interface User {
id: number;
name: string;
email: string;
}
@Injectable({
providedIn: 'root'
})
export class UserService {
private apiUrl = 'https://api.example.com/users';
constructor(private http: HttpClient) {}
getUsers(): Observable<User[]> {
return this.http.get<User[]>(this.apiUrl);
}
getUser(id: number): Observable<User> {
return this.http.get<User>(`${this.apiUrl}/${id}`);
}
createUser(user: User): Observable<User> {
return this.http.post<User>(this.apiUrl, user);
}
updateUser(id: number, user: User): Observable<User> {
return this.http.put<User>(`${this.apiUrl}/${id}`, user);
}
deleteUser(id: number): Observable<void> {
return this.http.delete<void>(`${this.apiUrl}/${id}`);
}
}
사용
import { Component, OnInit } from '@angular/core';
import { UserService, User } from '../services/user.service';
@Component({
selector: 'app-user-list',
templateUrl: './user-list.component.html',
})
export class UserListComponent implements OnInit {
users: User[] = [];
loading = false;
constructor(private userService: UserService) {}
ngOnInit(): void {
this.loadUsers();
}
loadUsers(): void {
this.loading = true;
this.userService.getUsers().subscribe({
next: (users) => {
this.users = users;
this.loading = false;
},
error: (err) => {
console.error(err);
this.loading = false;
},
});
}
}
5. Routing
설정
// src/app/app-routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { HomeComponent } from './home/home.component';
import { UsersComponent } from './users/users.component';
import { UserDetailComponent } from './user-detail/user-detail.component';
const routes: Routes = [
{ path: '', component: HomeComponent },
{ path: 'users', component: UsersComponent },
{ path: 'users/:id', component: UserDetailComponent },
{ path: '**', redirectTo: '' },
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule {}
Navigation
<!-- app.component.html -->
<nav>
<a routerLink="/" routerLinkActive="active" [routerLinkActiveOptions]="{exact: true}">
Home
</a>
<a routerLink="/users" routerLinkActive="active">
Users
</a>
</nav>
<router-outlet></router-outlet>
프로그래매틱 Navigation
import { Router } from '@angular/router';
constructor(private router: Router) {}
goToUser(id: number): void {
this.router.navigate(['/users', id]);
}
6. Forms
Template-driven Forms
import { Component } from '@angular/core';
@Component({
selector: 'app-login',
template: `
<form #loginForm="ngForm" (ngSubmit)="onSubmit(loginForm)">
<input
name="email"
type="email"
[(ngModel)]="email"
required
email
/>
<input
name="password"
type="password"
[(ngModel)]="password"
required
minlength="6"
/>
<button type="submit" [disabled]="!loginForm.valid">
Login
</button>
</form>
`,
})
export class LoginComponent {
email = '';
password = '';
onSubmit(form: any): void {
console.log(form.value);
}
}
Reactive Forms
import { Component } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
@Component({
selector: 'app-login',
template: `
<form [formGroup]="loginForm" (ngSubmit)="onSubmit()">
<input formControlName="email" type="email" />
<div *ngIf="loginForm.get('email')?.invalid && loginForm.get('email')?.touched">
Email is required
</div>
<input formControlName="password" type="password" />
<div *ngIf="loginForm.get('password')?.invalid && loginForm.get('password')?.touched">
Password must be at least 6 characters
</div>
<button type="submit" [disabled]="!loginForm.valid">
Login
</button>
</form>
`,
})
export class LoginComponent {
loginForm: FormGroup;
constructor(private fb: FormBuilder) {
this.loginForm = this.fb.group({
email: ['', [Validators.required, Validators.email]],
password: ['', [Validators.required, Validators.minLength(6)]],
});
}
onSubmit(): void {
if (this.loginForm.valid) {
console.log(this.loginForm.value);
}
}
}
7. Signals (Angular 16+)
기본 사용
import { Component, signal, computed } from '@angular/core';
@Component({
selector: 'app-counter',
template: `
<div>
<p>Count: {{ count() }}</p>
<p>Doubled: {{ doubled() }}</p>
<button (click)="increment()">Increment</button>
</div>
`,
})
export class CounterComponent {
count = signal(0);
doubled = computed(() => this.count() * 2);
increment(): void {
this.count.update(value => value + 1);
}
}
8. Standalone Components (Angular 14+)
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router';
@Component({
selector: 'app-user-list',
standalone: true,
imports: [CommonModule, RouterModule],
template: `
<div>
<h2>Users</h2>
<ul>
<li *ngFor="let user of users">
<a [routerLink]="['/users', user.id]">{{ user.name }}</a>
</li>
</ul>
</div>
`,
})
export class UserListComponent {
users = [
{ id: 1, name: 'John' },
{ id: 2, name: 'Jane' },
];
}
9. 배포
빌드
ng build --configuration production
Docker
FROM node:20-alpine as builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM nginx:alpine
COPY --from=builder /app/dist/my-app /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
정리 및 체크리스트
핵심 요약
- Angular: 완전한 프론트엔드 프레임워크
- TypeScript 우선: 강력한 타입 안전성
- DI 컨테이너: 의존성 자동 주입
- RxJS: 반응형 프로그래밍
- Signals: 새로운 반응성 시스템
- Standalone: 간소화된 구조
구현 체크리스트
- Angular CLI 설치
- 프로젝트 생성
- Component 작성
- Service 구현
- Routing 설정
- Forms 구현
- 배포
같이 보면 좋은 글
- RxJS 완벽 가이드
- TypeScript 5 완벽 가이드
- React 18 심화 가이드
이 글에서 다루는 키워드
Angular, TypeScript, Frontend, RxJS, Signals, Enterprise, Web Framework
자주 묻는 질문 (FAQ)
Q. Angular vs React, 어떤 게 나은가요?
A. Angular는 완전한 프레임워크입니다. React는 라이브러리입니다. 대규모 엔터프라이즈는 Angular, 유연성이 필요하면 React를 권장합니다.
Q. 학습 곡선이 가파른가요?
A. 네, Angular는 학습할 것이 많습니다. 하지만 구조화되어 있어 대규모 프로젝트에 유리합니다.
Q. AngularJS와 다른가요?
A. 네, 완전히 다릅니다. AngularJS는 레거시이고, Angular (2+)는 완전히 새로 작성되었습니다.
Q. 프로덕션에서 사용해도 되나요?
A. 네, Google, Microsoft, Forbes 등 많은 기업에서 사용합니다.