리액트 네이티브 개초보로서(일주일차) 처음에는 직접 setState 를 하다가

노마드코더 님의 리액트 훅스 강좌를 보고, Node.js 그래프큐엘 강좌를 보고, 이를 연동하는 React Apollo 강좌를 보고

3연속 충격...

 

이건 꼭 써야한다.

 

리액트에서 그래프큐엘을 사용하는 방식은 2가지가 있다.

알기로는 최근에 공식적으로 리액트 훅스를 지원하게 되면서 기존에 더해서 한가지 방법이 추가된 것.

일단 샘플 프로젝트의 구조는 다음과 같다.

단순히 apolloClient.js 모듈을 App.js 에서 가져와서 적용해주고, 실제 그래프큐엘을 패치할 컴포넌트에서 쿼리를 가져와 사용하는 부분만 보면 된다(아래설명)

 

 

 

1. 필요모듈

*서버단은 이미 그래프큐엘 API로 제작되어있다고 가정

npm install @apollo/react-hooks apollo-boost graphql

기본적인 모듈 외에 위의 모듈을 추가로 설치해야한다.

 

 

 

2. 클라이언트 생성

appolloClient.js

import ApolloClient from "apollo-boost";
import utils from "./commons/utils";

const client = new ApolloClient({
    uri: utils.getApiServer
});

export default client;

위와 같이 apollo-boost 에서 클라이언트를 가져와 uri를 설정하며 생성한다. utils.getApiServer 는 단지 호스트:포트 를 반환하는 커스텀 모듈이다.(직접 주소를 써주면 됨)

 

 

 

3. 클라이언트 주입

App.js

import React from 'react';
import { ApolloProvider } from '@apollo/react-hooks';

// 사용자정의모듈
import client from "./src/apolloClient";
import Router from "./src/route";

export default function App() {
  return (
    <ApolloProvider client={client}>
      <Router />
    </ApolloProvider>
  );
}

App.js 파일에 ApolloProvider 를 가져온다. redux를 설정할 때와 비슷하게 ApolloProvider 태그에 위에서 만들어둔 객체를 client 속성에 주입해준다. Router 는 나의 경우 materialTopTabNavigator 위에 createStackNavigator 를 올려서 구성했다(모르겠으면 구글링 ㄱㄱ)

 

 

 

4. 쿼리예시

queries.js

import { gql } from "apollo-boost";
// import gql from 'graphql-tag';

export const HOME_PAGE = gql`
    query {
        people {
            id
            name
            age
            gender
        }
    }   
`;

예시는 Person 객체의 배열을 가져오는 쿼리다. gql 을 apollo-boost에서 가져오느냐 graphql-tag 에서 가져오느냐는 선택이지만, 최신 공식문서에서는 apollo-boost 에서 가져온다.(예전에는 graphql-tag만 사용했음)

여기서 만들어둔 쿼리를 필요한 컴포넌트에서 import해서 사용하게 된다.

 

 

 

5. 실제 컴포넌트에서의 사용방법 2가지(택1)

 

1) Query 태그 사용방법

Home.js

import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import { Query } from "react-apollo";

// 사용자정의모듈
import utils from "../commons/utils";
import { HOME_PAGE } from "../queries";

/**
 * 피드목록컴포넌트 - Query 태그를 사용하는 방식
 * @method
 */
const Home = () => 
    <Query query={HOME_PAGE}>
        {
            ({loading, data, error}) => {

                console.log("=============");
                console.log(loading);
                console.log(data);
                console.log(error);
                console.log("=============");

                let template = ``;
                if(loading) {template = <Text>`로딩중... ${loading}`</Text>;}
                if(error) {template = <Text>`에러발생 : ${error}`</Text>;}
                if(data && data.people){
                    template = data.people.map((item, index) => 
                        <Text key={index}>{item.id} / {item.name}</Text>
                    )
                }
                return (<View>{template}</View>);
            }
        }
    </Query>
export default Home;

 

2) useQuery 메서드 사용방법

Home2.js

import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import { useQuery } from '@apollo/react-hooks';

// 사용자정의모듈
import utils from "../commons/utils";
import { HOME_PAGE } from "../queries";

/**
 * 피드목록컴포넌트 - useQuery 메서드를 사용하는 방식
 * @method
 */
const Home = () => {
    const { loading, error, data } = useQuery(HOME_PAGE);

    console.log("=============");
    console.log(loading);
    console.log(data);
    console.log(error);
    console.log("=============");
    
    let template = ``;
    if (loading) {template = <Text>`로딩중... ${loading}`</Text>;}
    if (error) {template = <Text>`에러발생 : ${error}`</Text>;}
    if (data && data.people) {
      template = data.people.map((item, index) => 
        <Text key={index}>{item.id} / {item.name}</Text>
      )
    }
    return (<View>{template}</View>);
}
export default Home;

자... 여기가 중요하다. 이부분 설명하려고 글쓰는것임.

두 컴포넌트는 완벽히 동일하게 작동하지만 문법이 다르다.

위의 방식은 react-apollo 모듈에서 Query 를 가져와 태그형식으로 가져온 데이터를 뷰에 그려준다. query={작성한쿼리} 속성을 주입하고, <Query> 태그 안에 스크립트문을 작성하는데, 이때 함수를 작성해주어야 하며, 이 함수가 반환한 jsx가 결과적으로 랜더린된다! Query 태그 안에서 함수를 작성해야 한다는 방식이 다소 생소하긴 하다.

 

두번째(아래) 방법은 좀더 언어적?인 것 같다. @apollo/react-hooks 에서 useQuery 를 가져와

const { loading, error, data } = useQuery(HOME_PAGE); 형식으로 쿼리를 인자로 넘겨 메서드를 호출한다.

그리고 data 를 가져와 jsx를 만들어 반환해주면 랜더링된다. 좀 더 우리에게 익숙한 방식인듯. 아마 그래서 최신에 react-hooks를 공식으로 지원하면서 이 방식을 권장할 것 같다...

 

결과

'Javascript > React Native' 카테고리의 다른 글

[React Native] android build profile 설정 차이 정리  (0) 2023.12.30
블로그 이미지

망원동똑똑이

프로그래밍 지식을 자유롭게 모아두는 곳입니다.

,

앵귤러2 에서 언젠가는 꼭 쓰게 되는 라이프사이클 훅에 대한 간단정리





ngOnChanges() : ngOnInt 보다 먼저, 하나 이상의 데이터 바인딩 된 프로퍼티가 변경될 때마다 호출. SimpleChanges 라는 인자와 함께 전달되며 SimpleChanges 안에는 previousValue 와 currentValue 라는 속성과 isFirstChange 라는 메서드가 존재. 최초 호출 시에는 isFirstChange 메서드는 true 를 반환, previousValue 는 UNINITIALIZED, currentValue 는 최초 바인딩된 값임.

* 참고 : https://angular.io/api/core/SimpleChange


ngOnInit() : 컴포넌트를 만든 직후 호출. 프로퍼티에 데이터가 바인딩 된 후 호출. 처음 한번만 호출됨.


ngDoCheck() : 컴포넌트의 상태 변경을 감지할 때마다 호출. 기본적으로 ngOnInit 이후 바로 한 번 호출. 구현된 컴포넌트에 관계없이 애플리케이션에 일어나는 모든 비동기 이벤트마다 실행되기 때문에 무리한 작업은 피하자.


ngAfterContentInit() : 컴포넌트의 뷰가 초기화되는 시점에 호출. Content Projection 으로 전달받은 템플릿의 초기화 완료 시 호출됨.


ngAfterContentChecked() : ngAfterContentInit 호출 이후 바로 호출됨. 뷰의 상태가 변경된 다음 처리할 것이 일을 때 사용됨.


ngAfterViewInit() : 컴포넌트 뷰와 자식 뷰를 초기화 한 후 실행. 즉, 부모로부터 프로퍼티 바인딩하여 받은 속성이 실제로 렌더링 완료되었을 시 호출됨.


ngAfterViewChecked() : ngAfterViewInit 호출 이후 바로 호출됨. 뷰의 상태가 변경된 다음 처리할 것이 일을 때 사용됨.


ngOnDestroy() : 컴포넌트가 소멸하기 직전에 실행. 보통 Observables 를 구독취소하여 메모리 누수 방지용으로 사용.





참고 블로그 : http://closer27.github.io/frontend/2017/07/06/Lifecycle-Hooks/

블로그 이미지

망원동똑똑이

프로그래밍 지식을 자유롭게 모아두는 곳입니다.

,

문제상황


앵귤러2 에서 일반적으로 자식 컴포넌트가 부모 컴포넌트에게 데이터를 전달하는 방법은 Output 과 EventEmitter 를 이용한 방법을, 부모 컴포넌트가 자식 컴포넌트에게 데이터를 전달하는 방법은 자식컴포넌트 선택자에 단지 속성으로 변수를 넘긴 후 Input 을 사용하여 받는다. 즉 뷰템플릿이 데이터 전송의 매체가 되는 셈.


하지만 문제가 복잡해져 서로 거리가 먼 컴포넌트 간의 통신을 해야하는 경우가 생김.

마치 나와 큰아빠와 큰아버지와 사촌동생 간에 동시에 데이터를 공유해야 하는 상황이라면?





1. 매개자 역할의 서비스 생성

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { Injectable } from '@angular/core';
import { Subject, Observable } from 'rxjs';
 
@Injectable()
export class DataService {
  private subject = new Subject<any>();
 
  constructor() { }
 
  sendData(data){
    this.subject.next(data);
    console.log("sendData() data : " , data);
  }
 
  getData(){
    return this.subject.asObservable();
  }
}
 
cs


rxjs 의 Subject 객체는 Observable 인 동시에 Observer. 한 컴포넌트에서 서비스의 sendData() 를 호출하여 데이터를 Subject 의 next 메서드를 통해서 데이터 스트림에 밀어넣는다.


다른 getData() 메서드는 데이터를 받을 컴포넌트에서 호출하여 데이터스트림에서 Observable 객체를 받은 후, 데이터 전송이 완료되었을 때 구독(subscribe) 할 것이다.




2. 데이터를 보낼 컴포넌트

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import { Component, OnInit, OnDestroy } from '@angular/core';
import { PostService } from '../../services/post/post.service';
import { DataService } from '../../services/data.service';
 
import { Post } from '../../models/post';
import { ActivatedRoute, Params } from '@angular/router';
import { Subscription } from 'rxjs/Subscription';
 
@Component({
  selector: 'app-post',
  templateUrl: './post.component.html',
  styleUrls: ['./post.component.css']
})
export class PostComponent implements OnInit, OnDestroy {
  private subscription: Subscription;
  post: Post;
  postNo: number;
 
  constructor(private activatedRouter: ActivatedRoute, private postService: PostService, private dataService: DataService) {
    this.subscription = activatedRouter.params.subscribe((params: Params) => {
      this.postNo = params['postNo'];
      this.postService.getPost(this.postNo)
      .subscribe(
        (post) => {
          this.post = post;
          dataService.sendData(this.post);
        },
        (error) => {
          console.log(error);
        }
      );
    });
  }
 
  ngOnInit() {
    
  }
 
  ngOnDestroy() {
    this.subscription.unsubscribe();
  }
}
 
cs


좀 복잡해 보이지만 핵심은 단지 위에서 생성한 service 의 sendData() 를 호출하는 것. 참고로 subscription 객체는 구독했던 객체를 파괴하기 위해 사용. ngOnDestroy() 라이프사이클에서 저장했던 subscription 을 unsubscribe 해주면 된다.


DataService 를 주입받고, 보내고자 하는 데이터를 dataService.sendData() 로 호출하여 넘겨준다.




3. 데이터를 받을 컴포넌트

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import { Component, OnInit, OnDestroy } from '@angular/core';
import { DataService } from '../../services/data.service';
 
import { Subscription } from 'rxjs/Subscription';
 
@Component({
  selector: 'app-banner',
  templateUrl: './banner.component.html',
  styleUrls: ['./banner.component.css']
})
export class BannerComponent implements OnInit, OnDestroy {
  title = 'Blog';
  regDate = '';
  categoryName = '';
 
  subscription: Subscription;
 
  constructor(private dataService: DataService) {
    console.log("banner 컴포넌트 생성!");
    
    this.subscription = dataService.getData().subscribe(data => {
      console.log("banner subscription : " , data);
      this.title = data.title;
      this.regDate = data.regTime;
      this.categoryName = data.categoryId;
    })
 
  }
 
  ngOnInit() {
  }
 
  ngOnDestroy(){
    this.subscription.unsubscribe();
  }
 
}
 
cs


데이터를 수신할 컴포넌트에서도 마찬가지로 Subscription 객체를 이용해 구독한 객체를 파괴시켜주어야 하며, DataService 를 주입받고, 이 DataService 의 getData() 메서드를 호출하여 Observable 객체를 받는다. 이를 subscribe 메서드를 사용하여 데이터가 전달되면 수행할 작업을 진행하면 된다.




블로그 이미지

망원동똑똑이

프로그래밍 지식을 자유롭게 모아두는 곳입니다.

,

Node.js 의 실행된 프로세스에 관한 정보 얻기


node.js 의 process 객체에 있는 속성과 메서드에 대해서 알아본다.

물론 process 객체도 global 전역객체에 속해있다.




process 객체의 속성

속성 

설명 

 예시

 argv 

명령줄에서 실행할 때 입력한 매개변수를 나타내는 배열 

 [ 'C:\\Program Files\\nodejs\\node.exe',

  'D:\\csj\\js\\cluster\\cluster.js' ]

 env

 실행된 컴퓨터 시스템의 환경에 대한 정보 

 생략

 version

노드의 버전 

 v8.11.1

 versions 

이 노드 프로세스에서 사용하는 모듈들의 버전 

 { http_parser: '2.8.0',

  node: '8.11.1',

  v8: '6.2.414.50',

  uv: '1.19.1',

  zlib: '1.2.11',

  ares: '1.10.1-DEV',

  modules: '57',

  nghttp2: '1.25.0',

  openssl: '1.0.2o',

  icu: '60.1',

  unicode: '10.0',

  cldr: '32.0',

  tz: '2017c' }

 arch 

프로세서의 아키텍처 정보 

 x64

 platform 

플랫폼 정보 

 win32




process 객체의 메서드

 메서드

설명 

예시 

 exit([exitCode=0])

 프로그램 종료. 쉘 반환값이 0이면 정상종료, 1이면 비정상종료. 인자로 0 또는 1을 넘겨서 표시 가능

 생략

 memoryUsage()

 메모리 사용 정보 반환

 { rss: 22212608,

  heapTotal: 7684096,

  heapUsed: 5136152,

  external: 8608 }

 uptime()

 프로세스의 실행 시점부터 이 메서드를 호출한 시점까지의 시간 반환

 0.105


위 속성과 메서드들로 애플리케이션 동작 환경을 확인하여 상황에 맞게 코드를 구성할 수 있다.

memoryUsage() 와 uptime() 메서드는 웹애플리케이션의 로드밸런싱이나 재부팅에 사용될 수 있다고 함.




- 출처 : https://m.blog.naver.com/musasin84/60189885531

블로그 이미지

망원동똑똑이

프로그래밍 지식을 자유롭게 모아두는 곳입니다.

,

문제상황


현재 AWS 프리티어를 가지고 포트폴리오용 프로젝트들을 서비스하는 중 DB 를 프로젝트별로 생성하여 사용하기 위해 2개의 자바프로젝트에는 Oracle DB를 생성하였고, 추가로 Node.js 프로젝트를 위하여 Mysql DB 인스턴스를 생성하려 하는 상황.

(참고로 AWS RDS에서는 한개의 Oracle DB 인스턴스에는 1개의 데이터베이스밖에 못올린다. Mysql 은 갯수제한이 없고 MS는 아마 40인 걸로 기억함...)


하지만 왜때문인지(멍청해서때문이지 뭐) 이틀 넘게 인스턴스 생성 삭제를 반복하며 지X를 했지만 Mysql 인스턴스에 외부접속이 안되는 상황...




- 동일한 VPC에 EC2와 RDS 모두 올리는 시나리오 사용




구글 자습서 :

https://docs.aws.amazon.com/ko_kr/AmazonRDS/latest/UserGuide/USER_VPC.Scenarios.html#USER_VPC.Scenario4


위 링크의 첫번째 시나리오를 가장 흔히 쓰는 방식


1. VPC 생성

EC2 와 RDS 들이 공통으로 포함될 VPC 한 개 생성(해당 vpc의 기본 서브넷 그룹 자동생성)


2. RDS용 보안그룹 생성

위에서 만든 VPC에 RDS 들이 포함될 보안그룹 생성(RDS는 아무나 접속할 수 없도록)


3. RDS용 보안그룹 인바운드 설정

위에서 만든 보안그룹에 인바운드 규칙 설정(아래같이 허용하고자 하는 ip/32 형식으로 적용. EC2에서는 접근 가능해야 하므로 아래 4번 이후 EC2용 보안그룹의 아이디를 추가해야 함)


4. EC2용 보안그룹 생성

동일 VPC에 EC2 가 포함될 보안그룹 생성


5. EC2용 보안그룹 인바운드 설정

위에서 만든 보안그룹에 인바운드 규칙 설정(일반적으로 서비스되는 부분이므로 80포트 개방 및 SSH 용 22포트 ip 설정)


6. 서브넷 생성

EC2 인스턴스와 각 RDS 인스턴스가 할당될 서브넷을 인스턴스 갯수만큼 생성.


역시 동일 VPC 내 생성하며, EC2용으로는 퍼블릭 서브넷으로 / RDS 는 VPC 외부에서 직접 접속할 일 없으므로 프라이빗 서브넷으로 설정


*퍼블릭 서브넷으로 설정시 자동으로 서브넷에 물린 라우팅 테이블에 0.0.0.0/0 주소가 인터넷 게이트웨이로 매핑되고, 프라이빗 서브넷으로 설정시 자동으로 블랙홀 처리됨(디도스 공격등에서 트래픽을 흡수하기 위함?)


*IPv4 CIDR은 0.0.0.0/24, 10.0.1.0/24 ... 같이 따로 설정해야 함


*가용영역은 상관 없음


7. 인스턴스 생성

이제 각각의 EC2 와 필요한 RDS 인스턴스를 생성하며 위에서 미리 생성해둔 VPC(모두 동일), 보안그룹(EC2와 RDS 분리), 서브넷 그룹(모두 동일), 서브넷(각각) 설정.

이때 RDS끼리는 가용영역을 통일해야 함(보안그룹이 같기 때문에 가용영역이 같아야 하는 것으로 추정)


8. 라우팅테이블 서브넷 연결

마지막으로 6번에서 퍼블릭으로 생성했던 EC2용 서브넷만 기본 라우팅테이블이 아닌 퍼블릭 서브넷이 명시적 연결되어있는 라우팅테이블로 (자동설정되어있지 않다면)설정하고, 나머지 RDS용 서브넷들은 VPC에서 설정한 라우팅테이블(기본 라우팅테이블)에서 "서브넷 연결" 탭에 각 프라이빗 서브넷들이 모두 등록된 것 확인. 등록되지 않았다면 등록해주어야 함.




*즉, 각 서브넷은 라우팅테이블 규칙을 따로 설정한 경우 우선순위를 받아 적용되고, 따로 설정하지 않는 경우 VPC 기본 라우팅테이블 규칙을 암시적으로 따르게 되어있는데, 이때 따르는 라우팅테이블에 서브넷 규칙이 설정되어 있어야 함. 이 부분이 누락되어 이틀동안 헛짓했던 것!


VPC 라우팅 테이블

블로그 이미지

망원동똑똑이

프로그래밍 지식을 자유롭게 모아두는 곳입니다.

,

문제상황


앵귤러2에 텍스트 에디터 플러그인을 붙여 써야하는 상황이 발생.

처음 고른 에디터는 nhn에서 만든 Toast ui editor(일명 tui editor) 이었지만 이의 앵귤러 버전인 ngx-tui-editor 가 빌드시에 가용 메모리를 모두 점유하는? 문제가 발생하여 좀 더 안정성 있는 에디터인 Froala editor를 사용하기로 결정.


https://www.froala.com/wysiwyg-editor/docs/framework-plugins/angularjs-2-4


하지만 Docs 를 읽는 도중에 Reactive Form 이라는 용어가 등장하여 앵귤러2 Form 에 대해 간단히 찾아보고 내용을 정리하고자 함


(기본 사용법으로 해도 되지만 이미 궁금증이 생긴 상황)




1. Reactive Form VS Template From


앵귤러 2에서 폼의 작동원리는 2가지 방법으로 나뉨.

하나는 템플릿 기반의 폼으로, 각 input 태그에는 ngModel 속성을 주고, form 태그에서 #참조변수명="ngForm" 으로 (폼값에)로컬레퍼런스를 걸어 ngSubmit에 인자로 넘기는 방식인데, 나중에 따로 포스팅하기로 하고 Reactive Form만 살펴봄기로 함.

즉, Template Form 은 폼 설정이 html 템플릿에 있다고 할 수 있음




2. 모듈추가


Reactive Form을 사용하기 위해서는 app.module.ts 파일에 "ReactiveFormsModule" 을 가져와야 한다.


app.module.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
 
import { PostWriteComponent } from './components/post-write/post-write.component';
import { FormsModule, ReactiveFormsModule} from '@angular/forms';
 
import { HttpModule, RequestOptions } from '@angular/http';
 
@NgModule({
  declarations: [
  ],
  imports: [
    BrowserModule,
    HttpModule,
    FormsModule,
    ReactiveFormsModule
  ],
  providers: [
    ],
    bootstrap: [AppComponent]
})
export class AppModule { }
 
cs


필요 없는 부분은 뺏음. 

import { FormsModule, ReactiveFormsModule} from '@angular/forms';

부분을 추가하고 하단에 imports 시킨다.




3. 컴포넌트 파일에 Form 구조 작성


Reactive Form 에서는 html이 아닌 컴포넌트에서 직접 FormGroup 과 FormControl 을 설정해주어야 함.


post-write.component.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import { Component, OnInit } from '@angular/core';
 
import { FormGroup, FormControl } from '@angular/forms';
 
@Component({
  selector: 'app-post-write',
  templateUrl: './post-write.component.html',
  styleUrls: ['./post-write.component.css']
})
export class PostWriteComponent implements OnInit {
    postForm: FormGroup;
 
    constructor() {
        this.postForm = new FormGroup({
            'category'new FormControl(),
            'isNotice'new FormControl(),
            'title'new FormControl(),
            'content'new FormControl()
        });
    }
 
    ngOnInit() {}
    
    onSubmit(){
        console.log(this.postForm);
    }
 
}
 
cs


위와 같이 FormGroup 타입으로 폼값이 들어갈 객체를 생성해주는 동시에 FormControl로 프로퍼티를 지정해줌.

*onSubmit 함수는 아래에서 사용




4. html 폼 작성


html로 폼 테그를 짤 때에는 form 태그의 [formGroup]="컴포넌트에서생성한폼그룹객체" 와 input 태그의 formControlName 지시자만 잘 설정해주면 됨.

템플릿 기반 폼과는 달리 form 의 ngSubmit 이벤트 발생시 폼값을 html 에서 인자로 넘겨주는 것이 아니라, 이미 formGroup 과 formControlName 으로 바인딩되어있는 개체를 사용하기만 하면 된다.


post-write.component.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<h1>글 작성</h1>
<form [formGroup]="postForm" (ngSubmit)="onSubmit()">
    <div>
        <label for="category">카테고리</label>
        <input type="text" name="category" formControlName="category">
    </div>
    <div>
        <label for="isNotice">공지사항으로 설정</label>
        <input type="checkbox" name="isNotice" formControlName="isNotice">
    </div>
    <div>
        <label for="title">제목</label>   
        <input type="text" name="title" formControlName="title">
    </div>
    <div>
        <textarea [froalaEditor] formControlName="content"></textarea>    
    </div>
    <button type="submit">Submit</button>
</form>
cs



[formGroup]="postForm"

부분과

formControlName="category"

같은 부분이 핵심이다.




5. 빌드 후 데이터 확인


이제 ng build 후 브라우저 개발자도구 창에서 데이터를 확인한다.



이렇게 적당히 값을 입력한 후



서브밋을 하면 콘솔로 폼 값이 찍혀 나온다.

value 키에 실제 값들이 들어있는 것을 확인가능!


















블로그 이미지

망원동똑똑이

프로그래밍 지식을 자유롭게 모아두는 곳입니다.

,


git init

 현재 디렉토리를 git 저장소로 설정

 git status

 현재 add/commit 또는 새로 생성된 파일들의 목록 보기

 

 

 git config --global user.name [user name]

 사용자 이름 설정

 git config --global user.email [user email]

 사용자 이메일 설정

 git config --global --list

 설정값 확인

 

 

 git remote add [remote name] [remote url]

 별명(remote name)으로 원격지 주소 저장

 git remote rm [remote name]

 별명으로 원격지 주소 삭제

 git remote rename [remote name] [new name]

 별명 변경

 

 

 git fetch [remote name]

 원격저장소의 내용 가져오기(branch)

 git merge [branch name]

 브런치의 내용을 가져와 로컬에 병합하기

 git pull [remote name] [localbranch name]

 원격저장소의 내용 가져와 병합하기

 

 

 git add *

 stage에 모든 파일/폴더 추가(.으로 시작 제외)

 git add .

 stage에 모든 파일/폴더 추가(.으로 시작 포함)

 git add [file or folder]

 stage에 특정 파일/폴더 추가

 git reset HEAD [file name]

 stage에 올라간 특정 파일 내리기

 git commit -m [message]

 message와 함께 Head에 올리기

 git push [remote name] [localbranch name]

 로컬브런치 내용을 원격저장소로 업로드

 

 

 git push [server] tag [TAG]

 서버에 tag 전송

 git push [server] --tags

 변경된 모든 tag 전송

 git push [server] [L.B]:[R.B]

 서버에 로컬브런치를 리모트브런치 이름으로 저장

 git tag [TAG name]

 원격저장소에 태그 붙이기

 git tag

 태그 목록 보기

 

 

 git branch

 내가 현재 위치한 브런치 목록 보기

 git branch [branch name]

 원격저장소에 브런치이름으로 브런치 생성

 git branch -a

 모든 로컬브런치와 리모트브런치 확인

 

 

 git checkout [branch name]

 다른 브런치로 전환

 git checkout -b [branch name]

 브런치 생성 후 전환

 git checkout -- [file or folder]

 지정한 파일/폴더를 수정하기 이전 상태로 복원

 git checkout [id]

 원격저장소 기준 id에 해당하는 커밋으로 복원

 git checkout -f

 변경된 파일들을 Head의 상태로 복원

 

 

 git clean -f

 workspace에 있는 untracked file 모두 삭제

 

 

 git rm [file or folder]

 해당 파일을 workspace에서 삭제하고 stage에 등록

 git diff

 로컬저장소와 원격저장소의 차이 보기

 git remote

 원격저장소 확인

 git log

 현재까지 commit된 목록 확인


블로그 이미지

망원동똑똑이

프로그래밍 지식을 자유롭게 모아두는 곳입니다.

,

문제상황


개인용 포폴로 새로운 웹사이트를 구축하기 시작하였습니다.

스택은 Angular2 + Node.js + Express + Mysql 일명 짝퉁 MEAN 스택이죠.

일단 AWS EC2 프리티어를 사용중에 있기 때문에 EC2 인스턴스 1개를 가지고 여러개의 서비스를 포트폴리오로 보여주고자 하였음.


그래서 EC2 서버에 nginx 리버스 프록시 서버를 올려서 특정 path 으로 들어오면 각각의 서비스를 보여주는 식으로.


예를 들면 

abc.com/aaa => 8080포트로 포워딩하여 aaa 라는 이름의 JSP 서비스로 연결!

abc.com/bbb => 3000포트로 포워딩하여 bbb 라는 이름의 node 서비스로 연결!

abc.com/ccc => 등등...


하지만 abc.com/bbb 에서 node 웹서버가 리스닝하는 3000포트로 날려주고, 노드서버는 앵귤러 페이지를 첫페이지로 뿌려줘야 하는데 문제가 생겼습니다.

앵귤러 프로젝트의 메인인 index.html은 나오지만, (기본 자식컴포넌트인) <app-root></app-root> 내용이 비어있는 것.


문제는 번들링된 파일들의 경로 문제였음.

아마도 리눅스 파일시스템 자원에 접근하는 경로가 기본적으로 /blog를 포함하지 않기 때문인 것.




1. base-href 와 deploy-url 에 대한 고민



위에 보면 <base href="/"> 태그가 존재함.

이부분은 아마 앵귤러 가상 url 라우팅에서 기본경로로 잡는 부분을 지정해주는 것 같음.


앵귤러 프로젝트를 빌드할 때


ng build -prod --base-href /test


로 빌드하면 위의 <base href="/test"> 로 빌드가 됨.


반면 deploy-url 은 정적 자원경로 앞에 prefix 형식으로 경로를 추가해 주는 것.





2. ng build -prod --base-href /blog --deploy-url /blog




ng build -prod --base-href /blog --deploy-url /blog 로 빌드한 경우 base href 속성과 정적 자원에 대한 경로 모두 /blog 가 붙으며, 정상적으로 모든 파일이 로드됨.





3. ng build -prod --base-href /blog



ng build -prod --base-href /blog 로 빌드한 경우 base href 속성이 /blog로 채워지지만, 정적 자원들은 그렇지 않아 번들링된 파일들이 로드되지 않음.





4. ng build -prod --deploy-url /blog



ng build -prod --deploy-url /blog 로 빌드한 경우 base href 의 속성은 채워지지 않았지만 정적 자원에 대한 경로는 제대로 잡힘. 현재 상황에서는 --deploy-url 옵션만 주는 것이 맞는 방법으로 보임.


하지만!


자세히 보니 url 에서 /blog 경로가 자동으로 제거된 것이 보임.

브라우저 특성이 아니라 자동으로 /blog 경로가 제거되며, 따라서 새로고침시에 기본경로로 접속하게 되어버림.


즉, base-href 옵션은 앵귤러 컴포넌트 라우팅에 사용할 기본 경로를 잡아주는 것임을 알 수 있다.


제대로 사용하기 위해서는 결국 ng build -prod --base-href /blog --deploy-url /blog 로 빌드해야 함





5. 또 다른 방법 APP_BASE_HREF 모듈 사용


위에서 나타난 url 상의 path 자동제거 현상을 잡아주는 모듈이 있음


import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { APP_BASE_HREF } from '@angular/common';

import { RouterModule } from '@angular/router';
import { AppComponent } from './app.component';
import { HomeComponent } from './components/home/home.component';


@NgModule({
declarations: [
AppComponent,
HomeComponent
],
imports: [
BrowserModule,
RouterModule.forRoot([
{
path: 'home',
component: HomeComponent
}
])
],
providers: [{provide: APP_BASE_HREF, useValue: '/blog'}],
bootstrap: [AppComponent]
})


export class AppModule { }



3번 라인에서 APP_BASE_HREF 모듈을 가져오고, providers 에 userValue 값으로 원하는 경로를 설정.


이제 ng build -prod --deploy-url 옵션만 주어도 url 에서 경로가 지워지지 않는다.

(하지만 index.html 의 base href 속성은 왜 /blog 가 아닌 / 일까...)





블로그 이미지

망원동똑똑이

프로그래밍 지식을 자유롭게 모아두는 곳입니다.

,

문제상황



위와 같이 commit 진행시 위와 같은 메시지가 뜨면서 실제로 push를 하면 일부 디렉토리가 제대로 repo에 저장되지 않는 문제가 발생.

메시지에서 보이는 client 디렉토리가 제대로 연동되지 않는 현상임.



git status


깃 상태를 조회해보면 마찬가지 메시지를 볼 수 있음.

메시지 중에 

commit or discard the untracked or modified content in submodules

즉, 서브모듈로 뭔가 인식하고 있는 것!!!





1. .git 폴더 확인



find . -name '.git'

find ./ -name '.git'

 find -name '.git'


위 세가지 명령어는 동일한 결과를 보여줌

즉, 현재 위치하는 폴더부터 하위 노드를 모두 찾아가면서 '.git' 이라는 파일이 존재하면 경로를 출력해줌.



많이들 특정 모듈이 어디 설치되어 있는지 알아볼 때

find / -name '알고싶은모듈명'

으로 루트에서부터 검색하는 것 많이 써봤을거임 ㅇㅇ(위사진처럼)



우리는 현재 위치하는 디렉토리의 '.git' 폴더만 사용하고 하위 디렉토리에 있는 '.git' 은 서브모듈로 인식하지 않도록 삭제할 필요가 있는것임..

위와같이 -mindepth 2 를 중간에 옵션으로 쳐주면 자식노드부터 검색함.





2. 하위 .git 폴더 모두 삭제


뭐 일일이 직접 rm -rf 지울파일경로

이렇게 지워도 금방 할테지만 좀더 멋있게 하려면


find ./ -mindepth 2 -name '.git' -prune -exec rm -rf {} +


확인하니 자식노드들의 '.git' 파일들이 모두 지워졌음!





3. git push 실행


이제 안심하고 다시 add > commit > push 를 진행하면 깔-끔



'Git' 카테고리의 다른 글

[Git] 자주쓰는 깃 명령어 모음  (0) 2018.04.13
블로그 이미지

망원동똑똑이

프로그래밍 지식을 자유롭게 모아두는 곳입니다.

,

블로그의 첫 글입니다.

사실 그동안 진행했던 프로젝트들도 많이 있었지만, 이렇게 블로그로 기록을 남기지 않았었죠. 현재 이직을 준비하면서 직접 블로그형 웹사이트를 만들어서 개인 포트폴리오 겸 공부기록 사이트로 남겨두면 참 좋겠다고 많이 생각했습니다. 하지만 개인 블로그 사이트 구축하는 것도 공부의 일종이기에... 우선하여 티스토리 블로그에 오늘부터의 기록을 조금씩 남겨보고자 합니다.


사람은 망각의 동물이라 저도 매일 개발하면서 구글링 하다보면 예전에 봤던 똑~같은 문서들을 자꾸 다시보게 되는 경우가 많아요. 이런 자주 찾는 내용들을 다루는 팁북같은 글부터... 진짜 별거 아닌데 며칠씩 막혀서 고생했던 문제(물론 해결책까지), 개발 패러다임까지 자유롭게 다루어 보려고 합니다.


일단은 체계가 없는 블로그라서 글부터 던져놓고, 나중에 카테고리는 글에 맞추어 정리해야겠네요.


마치며 제가 개발자로 마음가짐을 다잡게 한 글 공유합니다.


http://hothobbang.tistory.com/m/59

블로그 이미지

망원동똑똑이

프로그래밍 지식을 자유롭게 모아두는 곳입니다.

,