본문으로 건너뛰기
Advertisement

16.2 환경 설정 — Angular CLI, 프로젝트 구조, Standalone 컴포넌트

사전 준비

Angular 개발을 시작하기 전에 Node.js와 npm이 필요합니다.

# Node.js 버전 확인 (18.19+ 또는 20+ 권장)
node --version
# v20.11.0

# npm 버전 확인
npm --version
# 10.2.4

Angular 19는 Node.js 18.19.1 이상 또는 20.x / 22.x를 지원합니다.


Angular CLI 설치

Angular CLI는 프로젝트 생성, 컴포넌트/서비스 생성, 빌드, 테스트를 자동화하는 필수 도구입니다.

# 글로벌 설치
npm install -g @angular/cli

# 버전 확인
ng version

설치 후 ng version 출력 예시:

Angular CLI: 19.x.x
Node: 20.x.x
Package Manager: npm 10.x.x
OS: ...

Angular: 19.x.x
... animations, common, compiler, compiler-cli, core, forms
... platform-browser, platform-browser-dynamic, router

프로젝트 생성

# Standalone 컴포넌트로 새 프로젝트 생성 (Angular 17+ 기본값)
ng new my-angular-app

# 대화형 프롬프트
# ? Which stylesheet format would you like to use? CSS
# ? Do you want to enable Server-Side Rendering (SSR) and Static Site Generation (SSG/Prerendering)? No

옵션을 미리 지정해서 생성할 수도 있습니다:

# 스타일: SCSS, SSR 없음, 라우팅 포함
ng new my-app --style=scss --no-ssr --routing

# 또는 인터랙티브 없이
ng new my-app \
--style=scss \
--routing=true \
--skip-tests=false \
--no-ssr

프로젝트 디렉토리 구조

my-angular-app/
├── src/
│ ├── app/
│ │ ├── app.component.ts ← 루트 컴포넌트 (클래스)
│ │ ├── app.component.html ← 루트 컴포넌트 (템플릿)
│ │ ├── app.component.scss ← 루트 컴포넌트 (스타일)
│ │ ├── app.component.spec.ts ← 루트 컴포넌트 (테스트)
│ │ └── app.config.ts ← 앱 설정 (providers, router)
│ │ └── app.routes.ts ← 라우트 정의
│ ├── assets/ ← 정적 파일 (이미지, 폰트)
│ ├── index.html ← 앱 진입점 HTML
│ ├── main.ts ← TypeScript 진입점
│ └── styles.scss ← 전역 스타일
├── public/ ← 빌드 시 그대로 복사되는 파일
├── angular.json ← Angular CLI 설정
├── tsconfig.json ← TypeScript 기본 설정
├── tsconfig.app.json ← 앱 빌드용 TypeScript 설정
├── tsconfig.spec.json ← 테스트용 TypeScript 설정
└── package.json ← 의존성 및 스크립트

핵심 파일 살펴보기

// src/main.ts — 앱 진입점
import { bootstrapApplication } from '@angular/platform-browser';
import { appConfig } from './app/app.config';
import { AppComponent } from './app/app.component';

bootstrapApplication(AppComponent, appConfig)
.catch((err) => console.error(err));
// src/app/app.config.ts — 앱 전역 설정
import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
import { provideRouter } from '@angular/router';
import { provideHttpClient } from '@angular/common/http';
import { routes } from './app.routes';

export const appConfig: ApplicationConfig = {
providers: [
provideZoneChangeDetection({ eventCoalescing: true }),
provideRouter(routes),
provideHttpClient(), // HttpClient 전역 등록
]
};
// src/app/app.routes.ts — 라우트 정의
import { Routes } from '@angular/router';

export const routes: Routes = [
{
path: '',
redirectTo: '/home',
pathMatch: 'full'
},
{
path: 'home',
loadComponent: () =>
import('./home/home.component').then(m => m.HomeComponent)
}
];
// src/app/app.component.ts — 루트 컴포넌트
import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';

@Component({
selector: 'app-root',
standalone: true,
imports: [RouterOutlet],
templateUrl: './app.component.html',
styleUrl: './app.component.scss'
})
export class AppComponent {
title = 'my-angular-app';
}

angular.json 주요 설정

{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"projects": {
"my-angular-app": {
"projectType": "application",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:application",
"options": {
"outputPath": "dist/my-angular-app",
"index": "src/index.html",
"browser": "src/main.ts",
"polyfills": ["zone.js"],
"tsConfig": "tsconfig.app.json",
"assets": [
{ "glob": "**/*", "input": "public" }
],
"styles": ["src/styles.scss"],
"scripts": []
},
"configurations": {
"production": {
"optimization": true, // 코드 최소화
"outputHashing": "all", // 캐시 버스팅
"sourceMap": false, // 소스맵 비활성화
"namedChunks": false,
"budgets": [
{
"type": "initial",
"maximumWarning": "500kB", // 번들 크기 경고
"maximumError": "1MB" // 번들 크기 오류
}
]
},
"development": {
"optimization": false,
"extractLicenses": false,
"sourceMap": true // 개발 시 소스맵 활성화
}
}
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"configurations": {
"production": { "buildTarget": "my-angular-app:build:production" },
"development": { "buildTarget": "my-angular-app:build:development" }
},
"defaultConfiguration": "development"
}
}
}
}
}

개발 서버 실행

# 기본 개발 서버 (http://localhost:4200)
ng serve

# 다른 포트 사용
ng serve --port 4201

# 외부 접속 허용
ng serve --host 0.0.0.0

# 프로덕션 설정으로 서빙 (최적화 적용)
ng serve --configuration production

코드를 수정하면 브라우저가 자동으로 새로고침됩니다 (Hot Module Replacement).


Angular CLI 주요 명령어

코드 생성 (generate)

# 컴포넌트 생성
ng generate component features/user-list
# 단축: ng g c features/user-list

# 서비스 생성
ng g service services/user

# 디렉티브 생성
ng g directive directives/highlight

# 파이프 생성
ng g pipe pipes/format-date

# 가드 생성
ng g guard guards/auth

# 인터페이스 생성
ng g interface models/user

# 열거형 생성
ng g enum models/user-role

빌드

# 개발 빌드
ng build

# 프로덕션 빌드 (최적화, 트리 쉐이킹)
ng build --configuration production

# 번들 분석 (별도 도구 필요)
ng build --stats-json
npx webpack-bundle-analyzer dist/my-app/browser/stats.json

테스트

# 단위 테스트 (Karma + Jasmine)
ng test

# 단위 테스트 한 번만 실행 (CI용)
ng test --no-watch --browsers ChromeHeadless

# e2e 테스트 (Playwright, Cypress 등 별도 설치)
ng e2e

린트 & 포맷

# ESLint 실행
ng lint

# 자동 수정
ng lint --fix

VS Code 추천 확장

확장역할
Angular Language Service템플릿 자동완성, 오류 표시
ESLint코드 품질 검사
Prettier코드 포맷팅
Angular Snippets코드 스니펫
Material Icon Theme파일 아이콘
# VS Code 확장 명령행 설치
code --install-extension angular.ng-template
code --install-extension esbenp.prettier-vscode
code --install-extension dbaeumer.vscode-eslint

Standalone 컴포넌트 vs NgModule

Angular 17 이전에는 모든 컴포넌트가 NgModule에 속해야 했습니다.

기존 NgModule 방식 (레거시)

// app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { UserListComponent } from './user-list/user-list.component';

@NgModule({
declarations: [
AppComponent,
UserListComponent, // 모든 컴포넌트를 여기서 선언해야 함
],
imports: [
BrowserModule,
AppRoutingModule,
],
bootstrap: [AppComponent]
})
export class AppModule { }

새 Standalone 방식 (Angular 17+ 권장)

// user-list.component.ts
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';

@Component({
selector: 'app-user-list',
standalone: true, // ← 이것만 추가하면 NgModule 불필요
imports: [CommonModule], // ← 필요한 것만 직접 import
template: `...`
})
export class UserListComponent { }

비교

항목NgModuleStandalone
설정 복잡도높음낮음
코드 양많음적음
트리 쉐이킹모듈 단위컴포넌트 단위 (더 효율적)
Lazy Loading모듈 단위컴포넌트 단위도 가능
권장 여부레거시현재 표준

실전 예제 — 기본 앱 컴포넌트 구조

// src/app/app.component.ts
import { Component, signal, computed } from '@angular/core';
import { RouterLink, RouterLinkActive, RouterOutlet } from '@angular/router';

interface NavItem {
path: string;
label: string;
icon: string;
}

@Component({
selector: 'app-root',
standalone: true,
imports: [RouterOutlet, RouterLink, RouterLinkActive],
template: `
<div class="app-shell">
<!-- 헤더 네비게이션 -->
<header class="header">
<nav class="nav">
<a class="brand" routerLink="/">{{ appName() }}</a>
<ul class="nav-links">
@for (item of navItems; track item.path) {
<li>
<a
[routerLink]="item.path"
routerLinkActive="active"
[routerLinkActiveOptions]="{ exact: item.path === '/' }"
>
{{ item.icon }} {{ item.label }}
</a>
</li>
}
</ul>
<div class="user-info">
@if (isLoggedIn()) {
<span>{{ userName() }}</span>
<button (click)="logout()">로그아웃</button>
} @else {
<a routerLink="/login">로그인</a>
}
</div>
</nav>
</header>

<!-- 메인 콘텐츠 -->
<main class="main-content">
<router-outlet />
</main>

<!-- 푸터 -->
<footer class="footer">
<p>© {{ currentYear() }} {{ appName() }}. All rights reserved.</p>
</footer>
</div>
`,
styleUrl: './app.component.scss'
})
export class AppComponent {
// Signals로 반응형 상태 관리
appName = signal('My Angular App');
isLoggedIn = signal(false);
userName = signal('');

// computed — 파생 상태
currentYear = computed(() => new Date().getFullYear());

navItems: NavItem[] = [
{ path: '/', label: '홈', icon: '🏠' },
{ path: '/products', label: '상품', icon: '📦' },
{ path: '/about', label: '소개', icon: 'ℹ️' },
];

logout() {
this.isLoggedIn.set(false);
this.userName.set('');
}
}

tsconfig.json 주요 설정

{
"compileOnSave": false,
"compilerOptions": {
"outDir": "./dist/out-tsc",
"strict": true, // 엄격한 타입 검사 (권장)
"noImplicitOverride": true, // override 키워드 강제
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true, // 모든 코드 경로에서 return 강제
"noFallthroughCasesInSwitch": true,
"esModuleInterop": true,
"sourceMap": true,
"declaration": false,
"moduleResolution": "bundler",
"experimentalDecorators": true, // @Component 등 데코레이터
"importHelpers": true,
"target": "ES2022",
"module": "ES2022",
"lib": ["ES2022", "dom"],
// 경로 별칭 (선택 사항)
"paths": {
"@app/*": ["src/app/*"],
"@env/*": ["src/environments/*"]
}
},
"angularCompilerOptions": {
"enableI18nLegacyMessageIdFormat": false,
"strictInjectionParameters": true,
"strictInputAccessModifiers": true,
"strictTemplates": true // 템플릿 타입 검사 활성화
}
}

고수 팁

팁 1: 경로 별칭 설정으로 import 간소화

// tsconfig.json에 paths 추가 후
// 변경 전
import { UserService } from '../../../services/user.service';

// 변경 후
import { UserService } from '@app/services/user.service';

팁 2: 환경별 설정 파일 활용

// src/environments/environment.ts (개발)
export const environment = {
production: false,
apiUrl: 'http://localhost:3000/api'
};

// src/environments/environment.production.ts (프로덕션)
export const environment = {
production: true,
apiUrl: 'https://api.myapp.com'
};

팁 3: Nx로 모노레포 관리

대규모 프로젝트에서는 Nx를 사용해 여러 Angular 앱과 라이브러리를 모노레포로 관리하면 빌드 캐싱과 의존성 분석이 강력해집니다.

npx create-nx-workspace@latest my-workspace

팁 4: Schematic 활용

ng g 명령어는 내부적으로 Schematic을 사용합니다. Angular Material 등 외부 라이브러리도 스키마틱을 제공합니다:

# Angular Material 설치 (자동 설정)
ng add @angular/material

# NgRx Store 설치
ng add @ngrx/store@latest

정리

명령어역할
ng new프로젝트 생성
ng serve개발 서버 실행
ng build프로덕션 빌드
ng g c컴포넌트 생성
ng g s서비스 생성
ng test단위 테스트 실행
ng lint코드 품질 검사
ng update의존성 업데이트

다음 챕터에서는 Angular 컴포넌트와 템플릿 문법을 깊이 있게 학습합니다.

Advertisement