앵귤러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 메서드를 사용하여 데이터가 전달되면 수행할 작업을 진행하면 된다.




블로그 이미지

망원동똑똑이

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

,

문제상황


앵귤러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 키에 실제 값들이 들어있는 것을 확인가능!


















블로그 이미지

망원동똑똑이

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

,

문제상황


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

스택은 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 가 아닌 / 일까...)





블로그 이미지

망원동똑똑이

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

,