๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
Angular

๐Ÿ˜“ngrx๋ฅผ ์ด์šฉํ•˜์—ฌ ์—๋Ÿฌ๋‚œ ํŒŒ์ผ์ฒ˜๋ฆฌ

by frontChoi 2022. 7. 30.
๋ฐ˜์‘ํ˜•

๐Ÿชœ ๋ฌธ์ œ์ƒํ™ฉ

์ง€๋‚œ ํ”„๋กœ์ ํŠธ ์œ ์ง€๋ณด์ˆ˜ ์ง„ํ–‰์ค‘  ์ „์†ก๋˜์ง€ ์•Š๋Š” ํŒŒ์ผ์ด ์กด์žฌํ•˜์—ฌ, ์‘์‹œ์™„๋ฃŒ๊ฐ€ ๋˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋ฐœ์ƒํ•˜์˜€๋‹ค. ๊ทธ๋ฆฌํ•˜์—ฌ ngrx๋ฅผ ํ™œ์šฉํ•˜์—ฌ ์—๋Ÿฌ๋‚œ ํŒŒ์ผ์„ store๋‹ด์•„์„œ ์ฒ˜๋ฆฌ ํ•ด๋ณด๊ธฐ๋กœ ํ•˜์˜€๋‹ค. ๋ฌธ์ œ์ƒํ™ฉ ๋ฐ ํ•ด๊ฒฐ ๋ฐฉ์‹์€ ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

๋ฌธ์ œ ์ƒํ™ฉ

์œ„์ฒ˜๋Ÿผ "3๋ฒˆ"์—์„œ ์—๋Ÿฌ๊ฐ€ ๋‚  ๊ฒฝ์šฐ ๋ฉˆ์ถ”๋Š” ๊ฒƒ์ด ์•„๋‹Œ Store์— ๋‹ด๋Š”๋‹ค.

 

๐Ÿ‘จ‍๐Ÿ’ผ 500 ์—๋Ÿฌ ๋‚  ๊ฒฝ์šฐ Store์— ๋‹ด๊ธฐ

๐Ÿฆง 1๋ฒˆ ์ƒํ™ฉ์—์„œ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ• ๋ ค๋ฉด actions์— addFileAction๋ฅผ ๋“ฑ๋กํ•œ๋‹ค.

//file.actions.ts
import { createAction, props } from '@ngrx/store';
import { File } from './file'; // File ๋ชจ๋ธ์„ ๋งŒ๋“ค์–ด์•ผ ํ•œ๋‹ค.

export const addFileAction = createAction(
    ActionTypes.ADD_FILE,
    props<{ file: File}>()
)

๐ŸŽ1๋ฒˆ ์ƒํ™ฉ์—์„œ ํ•ด๋‹น ์ปดํผ๋„ŒํŠธ์—์„œ ํŒŒ์ผ์„ addFileAction๋ฅผ ํ˜ธ์ถœํ•œ๋‹ค.

 // ์‚ฌ์šฉ๋˜๋Š” ์ปดํผ๋„ŒํŠธ
import { Store } from '@ngrx/store'; //1.Store Import
import { addFileAction } from '../store/reducers/file/file.actions' //2.๋“ฑ๋ก๋œ actions import
import { File } from '../store/reducers/file/file'; //3.File ๋ชจ๋ธ ์„ ์–ธ

export class RxjsComponent implements OnInit {
    
    constructor(private store: Store) {
    }
    errorPush(){
		const fileItem:File ={
          id:0, //๊ณ ์œ ID
          file:this.formData, //ํŒŒ์ผ
          progressBar:0, // % ํ‘œ์‹œ๋ฅผ ์œ„ํ•ด ์‚ฌ์šฉ
          loaded:0, // loaded ์™„๋ฃŒ
          total:0 // total
        }
        this.store.dispatch(addFileAction({file:fileItem}))
    }
}

 

๐Ÿ˜ฌ2๋ฒˆ ์ƒํ™ฉ์—์„œ Reducer๋ฅผ ํ†ตํ•ด 3๋ฒˆ Store์— ๋‹ด๋Š”๋‹ค.

import { createReducer, on } from '@ngrx/store';
import { FileState } from './file.state';
import * as FileActionsTypes from './file.actions'; // 1.actions์— ์„ ์–ธ๋œ ๊ฒƒ์„ import

//2.์ดˆ๊ธฐ ์ƒํƒœ ์„ธํŒ…
export const initialState:FileState = {
    fileList: []
}

export const fileReducer = createReducer(
	initialState, //
     on(FileActionsTypes.addFileAction,(state,{ file }) =>{
     	
		//๊ธฐ์ค€ ๋ฌธํ•ญ์— , ์ƒˆ๋กœ์šด file ์ถ”๊ฐ€
        return [...state.fileList,file];
     })
)

 

๐Ÿฏ ์—๋Ÿฌ๋‚œ ํŒŒ์ผ ๋ฆฌ์ŠคํŠธ ํ•˜์—ฌ ํŒŒ์ผ์ „์†ก API๋ฅผ ํ†ตํ•ด ์žฌ์ „์†ก ํ•˜๊ธฐ

์ธ์ œ๋Š” Store์— ๋‹ด์€ ํŒŒ์ผ์„ ๋ฆฌ์ŠคํŠธํ™” ํ•˜์—ฌ ๋น„๋™๊ธฐ๋กœ ํŒŒ์ผ์„ ์žฌ์ „์†ก ์‹œ์ผœ๋ณด์ž.

ํŒŒ์ผ ์žฌ์ „์†ก

๐Ÿ™‰ Store์˜ ํŒŒ์ผ์„ ๋ฆฌ์ŠคํŠธํ™” ํ•˜๊ธฐ

ํ˜„์žฌ store(this.store.select(getFileList))์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์™€์„œ fileList์— ๋‹ด์•„์ค€๋‹ค.

/* ํ•ด๋‹น ์ปดํผ๋„ŒํŠธ typescript */
//ํŒŒ์ผ ngrx
import { Store } from '@ngrx/store';
import { getFileList } from '../store/reducers/file/file.selectors';


export class RxjsComponent implements OnInit {
   public fileList$ = this.store.select(getFileList); // Store ๋ฐ์ดํ„ฐ selectors
   public fileList: File[];
   ngOnInit() {
   	this.fileList$.subscribe(
      (result)=>{
        this.fileList = _.cloneDeep(result); // this.fileList$์˜ property์ง์ ‘์ ์ธ ์ ‘๊ทผ์€ ๋ถˆ๊ฐ€๋Šฅ ํ•˜๋ฏ€๋กœ ์ƒˆ๋กœ์šด ๊ฐ์ฒด๋กœ ๋ณต์‚ฌ
      },
      (err)=>{
        console.error('err : ',err);
      },
      ()=>{
        console.log('complete');
      }
    )
   }
}

๋‹ค์Œ์œผ๋กœ html์— ํ™”๋ฉด์— ๋ณด์—ฌ์ค€๋‹ค.

<div *ngFor="let item of fileList">
        <div class="progress">
            <div class="progress-bar progress-bar-striped" [style.width]="item.progressBar+'%'" role="progressbar"
                aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"></div>
        </div>
        <h1 *ngIf="item.progressBar !== 100">{{ item.progressBar }}%</h1>
        <h1 *ngIf="item.progressBar === 100 && (!item.isFinsh && (!item.isError))">์„œ๋ฒ„์— ์ „์†ก์ค‘</h1>
        <h1 *ngIf="item.progressBar === 100 && (item.isFinsh)">์ „์†ก์™„๋ฃŒ</h1>
        <h1 *ngIf="(item.progressBar === 100 && (item.isError)) || (item.progressBar !== 100 && (item.isError))">์ „์†ก์‹คํŒจ</h1>
  </div>

์ด 4๊ฐ€์ง€์˜ ๊ฒฝ์šฐ์— ๋”ฐ๋ผ ์ „์†ก ์ƒํƒœ๋ฅผ ๋ณด์—ฌ์ค€๋‹ค

  1. *ngIf="item.progressBar !== 100" ๊ฐ™์€ ๊ฒฝ์šฐ๋Š” ํ˜„์žฌ 100%๊ฐ€ ์•ˆ๋˜๋Š” ๊ฒฝ์šฐ ์ฆ‰ ์ „์†ก์ค‘์ธ ๊ฒฝ์šฐ
  2. *ngIf="item.progressBar === 100 && (!item.isFinsh && (!item.isError))" ๋Š” 100%์ด๋ฉด์„œ ์„œ๋ฒ„์—์„œ ๋‹ต์žฅ์„ ๋ฐ›์ง€ ๋ชปํ•œ๊ฒฝ์šฐ
  3. *ngIf="item.progressBar === 100 && (item.isFinsh)" ๋Š” 100%์ด๋ฉด์„œ ์„œ๋ฒ„์—์„œ ์ „์†ก์ด ์™„๋ฃŒ๋œ๊ฒฝ์šฐ
  4. *(item.progressBar === 100 && (item.isError)) || (item.progressBar !== 100 && (item.isError))" ๋Š” ์„œ๋ฒ„์— ์ „์†ก์„ ์‹คํŒจํ•˜์˜€๊ฑฐ๋‚˜, ์—…๋กœ๋“œ์— ์‹คํŒจํ•œ ๊ฒฝ์šฐ

 

๐Ÿ˜ŽํŒŒ์ผ ์žฌ์ „์†ก ํ•˜๊ธฐ

angular์˜ http์˜ต์…˜์—๋Š” ํŒŒ์ผ์—…๋กœ๋“œ ์ƒํƒœ๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋„๋ก ์˜ต์…˜์„ ๋„ฃ์„ ์ˆ˜ ์žˆ๋‹ค.reportProgress:true,observe:'events'

๋ฅผ ํ†ตํ•ด ํŒŒ์ผ ์—…๋กœ๋“œ ๊ด€์ฐฐ ํ•  ์ˆ˜ ์žˆ๋‹ค.

// fileuploadService.ts

import { Injectable } from '@angular/core';
import { environment } from 'src/environments/environment';
import { HttpClient } from '@angular/common/http';


export class FileuploadService {
	constructor(private http: HttpClient) { 
    
    }
	fileUplaod(fb:any){
        return this.http.post<any>(`${environment.apiUrl}/board/fileupload`,fb,{
          reportProgress:true,
          observe:'events'
        });
    }
}

๊ทธ๋ฆฌ๊ณ  ํ•ด๋‹น ์ปดํผ๋„ŒํŠธ์—์„œ "fileUplaod"๋ฅผ ํ˜ธ์ถœํ•œ๋‹ค. ๋ฏธ๋ฆฌ ๋‹ด์•„๋†“์€ fileList์˜ ๋ฐ˜๋ณต์„ ๋Œ๋ ค์„œ ๊ฐ file์„ api๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ๋น„๋™๊ธฐ๋กœ ์ฒ˜๋ฆฌํ•œ๋‹ค.

 submit(){
 	this.fileList.forEach((fileItem)=>{
      const formData= new FormData();
      formData.append('id',   fileItem.id as any) ;
      formData.append('file' , fileItem.file.get('file'));
      this.fileuploadService.fileUplaod(formData)
      .pipe(
        timeout(2000),
      )
      .subscribe(
        (event: (HttpEvent<any>))=>{
          switch (event.type) {
            case HttpEventType.Sent:
              console.log('Request has been made!');
              break;
            case HttpEventType.ResponseHeader:
              console.log('Response header has been received!');
              break;
            case HttpEventType.UploadProgress:
              //์—…๋กœ๋“œ ์ƒํƒœ๋ฅผ ๊ด€์ฐฐํ•˜์—ฌ ํ˜„์žฌ ์—…๋กœ๋“œ๋ฅผ ๊ณ„์‚ฐํ•œ๋‹ค.
              fileItem.progressBar = Math.round((event.loaded / event.total) * 100);
              break;
            case HttpEventType.Response:
              console.log('User successfully created!', event.body);
              setTimeout(() => {
                // ๋น„๋™๊ธฐ๋กœ ์ฒ˜๋ฆฌ๋œ ํŒŒ์ผ๋“ค์˜ ๊ฐœ์ˆ˜๋ฅผ ํŒŒ์•…ํ•˜๊ธฐ ์œ„ํ•ด finshCount++
                this.finshCount++;
                // ๋‹จ์ผ ํŒŒ์ผ์˜ ์ „์†ก์ƒํƒœ ์„ฑ๊ณต์„ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด isFinsh = true์ฒ˜๋ฆฌ
                fileItem.isFinsh = true;
              }, 2000);
              break;
            default:
              break;
          }
        },
        (err)=>{
          console.error(`${fileItem.id} : file upload error `, err);
          setTimeout(() => {
            // ๋น„๋™๊ธฐ๋กœ ์ฒ˜๋ฆฌ๋œ ํŒŒ์ผ๋“ค์˜ ๊ฐœ์ˆ˜๋ฅผ ํŒŒ์•…ํ•˜๊ธฐ ์œ„ํ•ด errorCount++
            this.errorCount++;
            // ๋‹จ์ผ ํŒŒ์ผ์˜ ์ „์†ก์ƒํƒœ ์—๋Ÿฌ๋ฅผ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด isError = true์ฒ˜๋ฆฌ
            fileItem.isError = true;
          }, 2000);
          
        },
        ()=>{
          console.log(`${fileItem.id} : finally`);
          // ์ตœ์ข…์ ์œผ๋กœ ์—๋Ÿฌcount + ์„ฑ๊ณตcount๊ฐ€ ์ „์ฒด ํŒŒ์ผ ๊ฐœ์ˆ˜์™€ ๊ฐ™์œผ๋ฉด ์ „์ฒด ์ „์†ก ์™„๋ฃŒ
          if(this.fileList.length === (this.finshCount + this.errorCount)){
            this.isFinsh = true;
          }
        }
      )
    })
 }
์—ฌ๊ธฐ์„œ "case HttpEventType.UploadProgress:"์˜ ๋ถ€๋ถ„์„ ํ†ตํ•ด์„œ ํ˜„์žฌ ์ง„ํ–‰์ƒํƒœ๋ฅผ ์ฒดํฌํ•œ๋‹ค๋Š” ๊ฒƒ์ด๋‹ค.
ํ˜„์žฌ ์ „์†ก์ƒํƒœ = Math.round((event.loaded / event.total) * 100); ์ด๋‹ค. 
 
 

๐Ÿฆง ์ตœ์ข… ํ™”๋ฉด ํ™•์ธ

๐Ÿš“์ „์†ก์ „

์ „์†ก์ „

๐ŸŽ์ „์†ก์ค‘

์ „์†ก์ค‘

๐Ÿ‘Œ์ „์†ก์™„๋ฃŒ

์ „์†ก์™„๋ฃŒ

 

๐Ÿฃํ›„๊ธฐ

๊ฐœ์ธ์ ์œผ๋กœ rxjs์˜ effects๋ถ€๋ถ„์„ ํ™œ์šฉํ•˜์ง€ ๋ชปํ•˜์˜€๋‹ค. ์ด์œ ๋Š” effects์—์„œ store์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐฐ์—ด๋กœ ๊ฐ€์ ธ์™€์„œ effects์—์„œ rxjs๋ฅผ ํ™œ์šฉํ•˜์—ฌ api ํ˜ธ์ถœํ•˜๋Š” ๋ถ€๋ถ„์„ ํ•ด๊ฒฐํ•˜์ง€ ๋ชปํ•˜์˜€๋‹ค.

๊ทธ๋ฆฌํ•˜์—ฌ ๋ณด์™„ํ•  ์ ์€ ngrx์˜ effects๋ถ€๋ถ„์„ ํ™œ์šฉํ•˜์—ฌ ํŒŒ์ผ์ „์†ก์„ ํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

๋ฐ˜์‘ํ˜•

'Angular' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€

๐Ÿ Angular ng-content  (0) 2022.10.26
Ngrx ๋„์ž…  (0) 2022.06.16
Rxjs?  (0) 2022.02.19
๐ŸฅทAngular์˜ ๋‹ค๊ตญ์–ด ๊ด€๋ฆฌ  (0) 2021.11.07

๋Œ“๊ธ€