๐Ÿงฑ ๊ฐ•์˜ 9B : ์ œํ’ˆ ๊ด€๋ฆฌ (Angular)

๋ฐœํ–‰: (2025๋…„ 12์›” 5์ผ ์˜คํ›„ 02:54 GMT+9)
3 min read
์›๋ฌธ: Dev.to

Source: Dev.to

์œ„์— ์ œ๊ณต๋œ ์†Œ์Šค ๋งํฌ๋งŒ์œผ๋กœ๋Š” ๋ฒˆ์—ญํ•  ๋ณธ๋ฌธ์ด ์—†์Šต๋‹ˆ๋‹ค. ๋ฒˆ์—ญ์ด ํ•„์š”ํ•œ ์ „์ฒด ํ…์ŠคํŠธ๋ฅผ ์ œ๊ณตํ•ด ์ฃผ์‹œ๋ฉด ํ•œ๊ตญ์–ด๋กœ ๋ฒˆ์—ญํ•ด ๋“œ๋ฆฌ๊ฒ ์Šต๋‹ˆ๋‹ค.

์†Œ๊ฐœ

์ด ๋ชจ๋“ˆ์€ ์ „์ฒด CRUD ์ž‘์—…๊ณผ ํ”„๋ŸฐํŠธ์—”๋“œ ์ธํ„ฐํŽ˜์ด์Šค์™€ ๋ฐฑ์—”๋“œ API ๊ฐ„์˜ ์›ํ™œํ•œ ํ†ตํ•ฉ์„ ํฌํ•จํ•œ ์™„์ „ํ•œ ์ œํ’ˆ ๊ด€๋ฆฌ ๊ธฐ๋Šฅ ๊ตฌ์ถ•์— ์ค‘์ ์„ ๋‘ก๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ์‚ฌ์šฉ์ž๋Š” ์‹œ์Šคํ…œ ์ „๋ฐ˜์— ๊ฑธ์ณ ์›ํ™œํ•˜๊ณ  ๋ฐ˜์‘์„ฑ์ด ๋›ฐ์–ด๋‚œ ๊ฒฝํ—˜์„ ์œ ์ง€ํ•˜๋ฉด์„œ ์ œํ’ˆ์„ ํšจ์œจ์ ์œผ๋กœ ์ƒ์„ฑ, ์—…๋ฐ์ดํŠธ, ์กฐํšŒ ๋ฐ ์‚ญ์ œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ด€๋ฆฌ์ž๋Š” ๋‹ค์Œ์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

  • ์ œํ’ˆ ๊ด€๋ฆฌ
  • ์ด๋ฏธ ๊ตฌ์ถ•ํ•œ ๋ฐฑ์—”๋“œ API์™€ ํ†ต์‹ 

Topics Covered

  • Auth Guard
  • ์ œํ’ˆ์— ๋Œ€ํ•œ ์ „์ฒด CRUD ํŽ˜์ด์ง€
  • Product Service
  • Product Route
  • App Routes ์—…๋ฐ์ดํŠธ
  • ๋ฉ”๋‰ด ์—…๋ฐ์ดํŠธ

์ธ์ฆ ๊ฐ€๋“œ

๊ฒฝ๋กœ: src/app/services/authguard/auth-guard.service.ts

import { Injectable } from '@angular/core';
import { Router } from '@angular/router';
import { AuthApiService } from '../api/auth.service';

@Injectable({
  providedIn: 'root'
})
export class AuthGuardService {
  constructor(
    private authService: AuthApiService,
    private router: Router
  ) {}

  canActivate(): boolean {
    const token = this.authService.getToken();
    const refreshToken = this.authService.getRefreshToken();

    // If no token and no refresh token, redirect to login
    if (!token && !refreshToken) {
      this.router.navigate(['/auth/login']);
      return false;
    }

    // If no token but refresh token exists, try to refresh
    if (!token && refreshToken) {
      this.attemptTokenRefresh(refreshToken);
      return false; // Wait for refresh attempt
    }

    // If token exists, user is authenticated
    if (token) {
      return true;
    }

    return false;
  }

  private attemptTokenRefresh(refreshToken: string): void {
    this.authService.refresh({ token: '', refreshToken }).subscribe({
      next: () => {
        // Token refreshed successfully, reload current route
        this.router.navigate([this.router.url]);
      },
      error: () => {
        // Refresh failed, redirect to login
        this.authService.logout();
        this.router.navigate(['/auth/login']);
      }
    });
  }
}

PrimeNG์„ ์‚ฌ์šฉํ•œ UI ํŽ˜์ด์ง€

์ œํ’ˆ ๊ธฐ๋Šฅ์„ ์œ„ํ•ด ๋‹ค์Œ ์„ธ ํŒŒ์ผ์„ ์ƒ์„ฑํ•˜์„ธ์š”:

  • src/app/features/products/product/product.html
  • src/app/features/products/product/product.scss (๋นˆ ํŒŒ์ผ)
  • src/app/features/products/product/product.ts

product.html

        

        
        
    

    
        
    

    
        
        Manage Products
            
                
                
        
    
    
        
            
                
            
            ID
            
                Name
                
            
            
                Description
                
            
            
                Price
                
            
            
                Stock
                
            
            
        
    
    
        
            
                
            
            {{ product.id }}
            {{ product.name }}
            {{ product.description }}
            {{ product.price | currency: 'USD' }}
            {{ product.stock }}
            
                
                
            
        
    

    
        
            
                Name
                
                Name is required.
            
            
                Description
                
            
            
                
                    Price
                    
                
                
                    Stock
                    
                
            
        
    

    
        
        
    

product.scss

product.ts

import { Component, OnInit, signal, ViewChild } from '@angular/core';
import { ConfirmationService, MessageService } from 'primeng/api';
import { Table, TableModule } from 'primeng/table';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { ButtonModule } from 'primeng/button';
import { RippleModule } from 'primeng/ripple';
import { ToastModule } from 'primeng/toast';
import { ToolbarModule } from 'primeng/toolbar';
import { RatingModule } from 'primeng/rating';
import { InputTextModule } from 'primeng/inputtext';
import { TextareaModule } from 'primeng/textarea';
import { SelectModule } from 'primeng/select';
import { Radi
Back to Blog

๊ด€๋ จ ๊ธ€

๋” ๋ณด๊ธฐ ยป

๋ฐ˜์‘์„ฑ์˜ ์ง„ํ™”: UI ์—…๋ฐ์ดํŠธ๊ฐ€ ์Šค์Šค๋กœ๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•

๊ฐ„๋žตํ•œ ์—ญ์‚ฌ 2010๋…„์œผ๋กœ ๊ฑฐ์Šฌ๋Ÿฌ ์˜ฌ๋ผ๊ฐ€๋ฉด, Knockout.js๋Š” Observable๊ณผ Computed๋ผ๋Š” ๊ฐœ๋…์„ ํ”„๋ก ํŠธ์—”๋“œ ์„ธ๊ณ„์— ๋„์ž…ํ–ˆ์Šต๋‹ˆ๋‹ค. ์ฒ˜์Œ์œผ๋กœ ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์‹ค์šฉ์ ์ธโ€ฆ

Show HN: Angular์—์„œ 47๊ฐœ ์ด์ƒ์˜ ์ปดํฌ๋„ŒํŠธ๋ฅผ ํฌํ•จํ•œ ์‹œ๊ฐ์  ์•„ํ‚คํ…์ฒ˜ ๋‹ค์ด์–ด๊ทธ๋žจ ๋นŒ๋”

์˜คํ”ˆ์†Œ์Šค Angular ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋งŒ๋“ค์–ด ์•„ํ‚คํ…์ฒ˜ ๋‹ค์ด์–ด๊ทธ๋žจ์„ ๊ตฌ์ถ•ํ–ˆ์Šต๋‹ˆ๋‹ค. ํŠน์ง• - 47๊ฐœ ์ด์ƒ์˜ ์‚ฌ์ „ ๊ตฌ์ถ•๋œ ์ปดํฌ๋„ŒํŠธ(AWS, Azure, AI/ML ๋„๊ตฌ ๋“ฑ) - ๋“œ๋ž˜๊ทธ ์•ค ๋“œ๋กญ i...

Signal Forms ์ž๋™ ์ƒํƒœ ํด๋ž˜์Šค๊ฐ€ ์ถ”๊ฐ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค (๊ทธ๋ฆฌ๊ณ  ๋” ๋งŽ์€ ๊ธฐ๋Šฅ)

Reactive Forms vs Signal Forms: ngโ€‘Classes๋Š” ์–ด๋””๋กœ ๊ฐ”๋‚˜์š”? ์—ฌ๊ธฐ Reactive Forms๋กœ ๋งŒ๋“  ํผ์ด ์žˆ์Šต๋‹ˆ๋‹ค: !Reactive Forms๋กœ ๋งŒ๋“  ๊ฐ„๋‹จํ•œ ํผ https://media2...