본문 바로가기
블로그 이미지

방문해 주셔서 감사합니다! 항상 행복하세요!

  
   - 문의사항은 메일 또는 댓글로 언제든 연락주세요.
   - "해줘","답 내놔" 같은 질문은 답변드리지 않습니다.
   - 메일주소 : lts06069@naver.com


앵귤러, 리엑트, 뷰/Angular Tutorial(new)

앵귤러 튜토리얼(Angular tutorial) - 13 : 데이터 바인딩 심화

야근없는 행복한 삶을 위해 ~
by 마샤와 곰 2020. 7. 8.

* 어려운 13장, 복잡해 보이는 ReactiveFormsModule

 

벌써 앵귤러에 대해서 살펴보는 13번째 시간입니다.
이번시간에도 마찬가지로 컴포넌트에 대해서 조금 더 살펴보겠습니다.


dashboard컴포넌트에 대해서 작업을 해 보겠습니다.
그런데.. 작업하려다보니까 조금 문제가 있네요.

새로고침이 되니까 계속해서 login컴포넌트가 나옵니다..

 

dashboard컴포넌트가 적용되었는지 확인하려면 무조건 로그인을 해야 됩니다.

다시말해 login컴포넌트를 통한 뒤에야 비로소 dashboard컴포넌트에 접근할 수 있습니다.
그런데 로그인을 한뒤 dashboard컴포넌트로 이동 하였더라도 우리가 작업을 하고나서 저장을 하게 되면 새로고침이 일어나면 안타깝게도 처음 로그인 모습으로 돌아가게 됩니다..
불편한 점이 너무 많으므로 첫 화면을 바꾸도록 합니다.
이를 위해서 app컴포넌트 html파일에서의 ngIf디렉티브의 조건을 바꾸어주도록 합니다.

* 대상 : app.component.html

<app-login *ngIf="false" [visible1]='loginBool' (sendMyEvent)="getEventThanks($event)"></app-login>
<app-dashboard *ngIf="true"></app-dashboard>

 

이제 dashboard화면이 무조건 앞에 나오도록 하게 되었습니다. 새로고침에도 끄떡 없군요!
하지만 나중에 이러한 새로고침이라던지, 로그인화면이 계속해서 나오는 모습은 반드시 고쳐져야 할 것 입니다.

또한..경로같은 기능도 있으면 참 좋겠네요.
* 나중에 살펴볼 예정입니다!

 

지금부터 우리는 일반적으로 간단하게 사용할 수 있는 데이터 바인딩 방법과 저번시간에 사용하였던 ReactiveFormsModule을 활용한 데이터 바인딩 방법에 대해서 비교하여 살펴보려 합니다.

데이터 바인딩이라는 의미는 예전에 언급하였습니다만 다시한번 살펴보면,

우리가 input type에서 데이터를 받으려면 예전에 Javascript나 Jquery등을 활용하여 아래처럼 가져오곤 했습니다.

var val1 = document.querySelector('#아이디').value;
var val2 = $('#아이디').val();

 

그런데 앵귤러에서는 Dom(엘리먼트, Html테그)에 직접 접근하지 않고 NgModel과 같은 디렉티브를 통해서 데이터의 변화에 바로바로 적용하도록 하였습니다. 

<input type="text" placeholder="id" [(ngModel)]="id"/>


import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
@Component({
  selector: 'app-login',
  templateUrl: './login.component.html',
  styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit {
  id :string;
  constructor() { 
  }

  ngOnInit(): void {
  }

  tryToLogin() : void{
    console.log(this.id);
  }
}

 

자..이제 어느정도 기억을 되찾았다고 생각하겠습니다!

내용이 저번시간보다 상당히 어렵습니다!

 

 

#방법1 : 간단한 바인딩

기존에 사용했던 모습은 몇개 되지 않는 데이터였습니다. id와 pwd 값 2개가 전부였습니다.

2개의 값만 받는 화면은 사실 너무 쉽습니다.

이번에는 dashboard컴포넌트에 배열로 데이터를 나열해 보도록 하겠습니다.

이를 위해서 데이터를 담는 변수를 만들어줍니다.

* 대상 : dashboard.component.ts

import { Component, OnInit } from '@angular/core';

declare type MyType = {
  text : any;
  number : any;
};

@Component({
  selector: 'app-dashboard',
  templateUrl: './dashboard.component.html',
  styleUrls: ['./dashboard.component.css']
})
export class DashboardComponent implements OnInit {

  DataArray : Array<MyType> = [ ];  //데이터를 담는 변수

  constructor() { 

  }

  ngOnInit(): void {
    for(var i = 0;i < 10;i++){
      this.DataArray.push({text:'abcd'+i, number:i+1});
    }
  }

  showData(arg?){  //화면에서 데이터를 볼 함수
    if(arg){
      console.log(arg);
    } else {
      console.log(this.DataArray);
    }
  }

}

 

여기서 눈 여겨 보아야 할 점은 ngOnInit이라는 메소드 입니다.
해당 메소드는 Dashboard컴포넌트의 implements라는 부분에서 OnInit이라는 인터페이스를 통해 받은 기능 입니다.
우리가 통상적으로 컴포넌트를 만드는 명령어인 ng g component 라는 명령어는 기본적으로 OnInit 이라는 인터페이스를 상속받은 상태로 컴포넌트를 만들어 줍니다.
굳이 상속받지 않아도 컴포넌트는 정상적으로 동작합니다.
OnInit 인터페이스를 상속 받으면 ngOnInit이라는 함수를 사용할 수 있습니다.
ngOnInit 함수는 앵귤러에서 컴포넌트가 본인의 행동준비가 끝난 이후에(초기화가 된 이후) 한번만 호출되는 아주 괜찮은 함수 입니다.

해당 내용을 다시 설명하여보면,
ngOnInit 함수는 컴포넌트가 내부적으로 선언되어진 모든 행동준비가 다 되고나서 1번만 호출되는 함수 입니다.
이와 비슷한 constructor(생성자)는 컴포넌트가 최초 생명을 부여받기 전 1번만 동작하는 함수입니다.
constructor가 호출되고 난 다음에 컴포넌트는 각종 선언된 변수, 외부에서 참조된 라이브러리 같은 기능을 준비시키고 나서 ngOnInit함수를 호출합니다.

내용이 조금 어렵다면 이렇게 일단 이해하면 될 것 같습니다.
* constructor는 최초 1번만 가장 제일 먼저, 누구보다 컴포넌트에서 먼저 호출된다.
* OnInit 인터페이스를 상속받은 뒤 사용 가능한 ngOnInit 함수는 컴포넌트 준비가 다 되면 1번만 호출 된다.

* 호출 순서 : constructor > ngOnInit


ngOnInit 함수에 사용된 DataArray 변수에 반복문을 통해 값을 넣어주고 있습니다.
DataArray 변수는 MyType이라는 타입을 통해서 데이터 작성시 오류를 방지하여 주었습니다.

이를 dashboard컴포넌트의 html파일에 나타내어 보겠습니다.
기억을 조금 더듬어 보변 ngFor 디렉티브가 사용되었었습니다.

* 대상 : dashboard.component.html

<div *ngFor="let item of DataArray">
    <input type='text'  [value] = 'item.text' />
    <input type='number'  [value] = 'item.number' />
    <input type='button' value='보기' (click)='showData(item)' />
</div>
<input type='button' value='전부 보기' (click)='showData()' />


한번 실행하여봅니다. 데이터가 올바르게 나오는 것을 볼 수 있습니다.

오...많은데이터가 표현되고 있습니다.

 

"보기" 버튼을 누르면 각각의 데이터가 나오는 것이 보입니다.

그런데..input의 값을 바꾸어도 맨 처음 생성된 배열의 값만 보일 뿐 변경된 데이터는 적용되지가 않습니다.

만약 변경된 데이터를 적용하는 기능을 붙여주려면 change속성을 이용해야 합니다.

* 대상 : dashboard.component.html

<div *ngFor="let item of DataArray">
    <input type='text'  [value] = 'item.text'  (change)="item.text  = $event.target.value" />
    <input type='number'  [value] = 'item.number'  (change)="item.number = $event.target.value" />
    <input type='button' value='보기' (click)='showData(item)'  />
</div>
<input type='button' value='전부 보기' (click)='showData()' />

 

훌륭하게 적용되었습니다.
아직은 만들지 못했지만 언젠가 showData함수를 데이터베이스 서버에 전달하는 코드로 변경하여 준다면 데이터베이스 서버와 연동되는 앱이 될 것 입니다.
* 한번 열심히 해 봅시다!!

여기까지가 데이터를 일반적으로 바인딩하는 가장 손쉬운 방법입니다.

 

 

#방법2 : ReactiveFormsModule을 활용한 바인딩

그러면 위 내용을 바탕으로 저번 시간에 살펴본 FormControl 클래스를 활용하여 변경하여 보겠습니다.

데이터는 n개입니다. 1개~2개가 아닌 많은 데이터입니다.

 

해당 사진을 보면 우리가 바인딩 해 줄 데이터는 N개 입니다.
저번장에서 우리는 FormControl 를 통해서 데이터를 바인딩 하여 주었습니다.

FormControl는 소규모의 데이터에 대해서 바인딩 할 때 아주 좋은 방법 입니다.
그런데 이번장에서는 저번 시간처럼 FormControl을 일일이 부여하는 것은 매우 바람직하지 못합니다.

왜냐하면 우리는 반복문을 통해서 20개가 넘는 데이터를 만들 예정이기 때문입니다.

저번시간과 동일하게 작업을 한 다면, 최소한 FormControl 객체 20개의 변수를 선언해야 되기 때문에 매우 비 효율적입니다.

관리하기 쉽게 위 내용처럼 그룹을 만들어 보겠습니다.


사진처럼 우리는 N개를 그릴 데이터를 group으로 묶은뒤에 배열로 처리를 할 것 입니다.
그렇게 묶은 배열을 그룹에 담아서 관리를 할 것 입니다.
이해를 위해서 single이라는 배열이 아닌 단순한 대상을 1개 추가하였습니다.
이제 기존소스코드를 변경해 보겠습니다!

* 대상 : dashboard.component.ts

import { Component, OnInit } from '@angular/core';

import { Validators } from '@angular/forms';
import { FormGroup } from '@angular/forms';
import { FormControl } from '@angular/forms';
import { FormBuilder } from '@angular/forms';
import { FormArray } from '@angular/forms';


@Component({
  selector: 'app-dashboard',
  templateUrl: './dashboard.component.html',
  styleUrls: ['./dashboard.component.css']
})
export class DashboardComponent implements OnInit {

  builder : FormBuilder;
  rows: FormArray;
  formGrp : FormGroup;
  single : FormControl;

  constructor(fb : FormBuilder) {  //FormBuilder를 주입받습니다.
    this.builder = fb;
    this.rows = this.builder.array([]);  //주입받은 FormBuilder를 통해서 배열 형태로 값을 담을 변수를 만들어줍니다.
    this.single = new FormControl('Title', Validators.required);  //1개의 데이터에 바인딩할 대상 입니다.
    this.formGrp  = this.builder.group({'row_data':this.rows, 'single_data' : this.single});   //rows와 single을 담을 그룹입니다.
  }
  //생략..
}

 

생성자(constructor) 부분까지의 내용입니다.
fb는 FormBuilder 타입이며 생성자를 통해서 받은 내용입니다. 
app모듈에서 ReactiveFormsModule을 imports를 해 주었기 때문에 생성자에 fb라는 FormBuilder 값을 받을 수 있는 모습입니다.
아직 우리는 생성자를 통해 데이터를 받는 방법에 대해서 배우지는 않았습니다.
여기서는 일단 FormBuilder를 받으려면 생성자를 통해 가능하다라고 이해하면 될 것 같습니다.

 

FormBuilder는 이름 그대로 Builder(만들다)의 역할을 합니다.

FormBuilder를 통해서 우리는 rows변수에 FormArray 타입의 객체를 만들었습니다.
변수 rows는 FormArray타입인 객체이며, 기존에 만들었던 DataArray를 대신할 객체입니다.
변수 single은 FormControl타입이며 input type 테그 1개를 만들어 준 다음 적용할 예정 입니다.
마지막으로 formGrp변수는 FormGroup타입으로 rows와 single변수를 담을 최 상위의 노드입니다.
FormGroup은 json 형식으로 key, value 값을 갖습니다.

 

여기까지가 위의 사진에서본 트리모양의 형태로 데이터를 묶는 기초작업입니다.

해당 부분을 반복해서 꼭 이해하셔야 합니다!! ㅠㅠ
이제 그러면 ngOnInit함수의 내용도 바꾸어 보겠습니다.

* 대상 : dashboard.component.ts

//..생략

  ngOnInit(): void {
    for(var i = 0;i < 10;i++){
      var group = this.builder.group({  //배열에 담을 group을 생성합니다.
        text : this.builder.control('abcd'+i, Validators.minLength(5)),   //control 함수는 FormControl을 만드는 역할을 합니다. 
        number: this.builder.control(i, Validators.required)
      });
      this.rows.push(group);
    }
  }

  showData(arg?){
    if(arg){
      console.log(arg);
    } else {
      console.log(this.formGrp);
    }
  }

 

rows변수는 FormArray의 형태입니다.

데이터를 넣어주기 위해서 group이라는 변수를 선언한 뒤에 builder.group 함수를 호출하여 내용을 채운뒤 대입 해 주었습니다.

builder.group 부분은 new FormGroup과 동일한 기능이며 key와 value 형태로 값을 채워주었습니다.

key는 일반 문자, value는 FormControl객체가 담겨져 있습니다. (text와 number가 보이시나요?)

builder.control 부분은 new FormControl과 동일한 내용입니다. 

그리고 group을 배열에 push 해 주었습니다.

 

FormArray 타입은 이처럼 FormGroup타입의 데이터를 받습니다.
다시말해, rows 변수는 FormArray의 형태이며, FormArray는 FormControl값이 채워진 FormGroup을 받는다는 것이 핵심 입니다.

 

정말 난이도가 떡상(?) 한것 같습니다.
그러면 마지막으로 html파일 내용을 수정하여 보겠습니다.

* 대상 : dashboard.component.html

<form [formGroup] = 'formGrp'>
    <input type='text' [value]='' formControlName='single_data' />
    <div formArrayName="row_data"  *ngFor="let item of rows.controls; let i = index;">
        <div [formGroupName]="i">  <!-- formArray의 그룹 index값을 추가하여 item의 값을 찾을수 있도록 하여 줍니다. -->
            <span>{{i+1}}</span>            
            <input type='text'  formControlName="text"  [value] = 'item.value.text' />
            <input type='number' formControlName="number"    [value] = 'item.value.number'/>
            <input type='button' value='보기' (click)='showData(item)' />
        </div>
    </div>
</form>
<input type='button' value='전부 보기' (click)='showData()' />

 

내용이 이전내용과 크게 차이나지는 않습니다.
먼저 formGroup 디렉티브값에 formGrp 값을 주었습니다. formGrp값은 우리가 ts파일에서 선언한 변수값과 일치 합니다.
formControlName은 선언한 변수값이 아니라 변수 formGrp의 key 값인 single_data가 쓰여졌습니다.
여기까지가 row_data이전까지의 설명입니다!

우리가 선언안 rows 변수인 FormArray도 formGrp에서 선언한 key값인 row_data가 사용되었습니다.
ngFor디렉티브에서 let i = index라는 부분이 추가 되었습니다. 해당 부분은 반복문이 증가하는 index(순서)값을 가져올 수 있는 부분입니다.
밑에 보이는 div테그에 formGroupName 은 row_data의 인덱스 값이 필요합니다.
formGroupName에 대괄호와 인덱스를 사용한 이유는 따로 key값을 부여하지 않았기 때문에 인덱스로 값을 찾은 후 매핑하기 위해서 입니다.

흥미로운점은 change로 데이터 변화에 따른 값을 대입해 주는 부분이 사라진 점 입니다.

데이터 자체가 FormControl 클래스로 묶여있기 때문에 가능한 일 입니다!


이하 나머지 부분은 직관적이므로 넘어가겠습니다.

한번 동작하여 보겠습니다.

익숙한 status 속성의 VALID 값이 보입니다.

 

유효성 검사도 잘 동작하면서 데이터도 이상없이 변경됨을 알 수 있습니다.
ReactiveFormsModule를 사용하는 것은 정말 많은 연습이 필요합니다.
여러번 확인하고 직접 코딩을 해 보아야 합니다! ㅠㅠ
* 사실 대량의 Form형태로 구현되어 있으면 바로 이해 못하고 이해 하는데 시간이 어느정도 필요로 합니다.

 

위 내용처럼 대량의 데이터를 관리하는 방법은 2가지가 존재 합니다.
#방법1 : 일반적인 배열+json형식의 데이터를 만든 뒤 디렉티브를 통해서 관리하는법
#방법2 : ReactiveFormsModule을 활용하여 다양한 Form과 관련된 클래스를 활용하는 방법

어떤 형식을 사용하던지 간에 그것은 사용자의 선택에 달려있습니다.
#방법1은 매우 직관적이고 간단합니다. 그리고 모든 행위와 기능, 이벤트 전달 등을 사용자가 직접 구현해야 됩니다.
#방법2는 다소 복잡합니다. 그렇지만 행위, 이벤트 및 기능등에 대해서 이미 구현되어진 기능을 단순히 사용만 하면 될 뿐 입니다.

 

dashboard컴포넌트를 통하여 컴포넌트를 다루는 방법에 대해서 좀더 살펴보았습니다!

#방법2의 내용이 어렵다면 #방법1을 사용하면서 익숙해 질 필요가 있겠습니다.
컴포넌트에 대해서 살펴보았으므로 이제는 앵귤러의 다른 강력한 기능을 살펴보겠습니다.
다음시간에는 "공유(Share)"에 대해서 살펴보겠습니다.

src.zip
0.01MB

 

반응형
* 위 에니메이션은 Html의 캔버스(canvas)기반으로 동작하는 기능 입니다. Html 캔버스 튜토리얼 도 한번 살펴보세요~ :)
* 직접 만든 Html 캔버스 애니메이션 도 한번 살펴보세요~ :)

댓글