<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>summer.log</title>
        <link>https://velog.io/</link>
        <description>조금씩, 꾸준히</description>
        <lastBuildDate>Sun, 08 Aug 2021 10:20:20 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>summer.log</title>
            <url>https://velog.velcdn.com/images/sang-gyeong/profile/b2f0d33b-9566-44dd-8cef-4055fd393a48/image.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. summer.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/sang-gyeong" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[앵귤러 생명주기와 훅 메소드]]></title>
            <link>https://velog.io/@sang-gyeong/%EC%95%B5%EA%B7%A4%EB%9F%AC-%EC%83%9D%EB%AA%85%EC%A3%BC%EA%B8%B0%EC%99%80-%ED%9B%85-%EB%A9%94%EC%86%8C%EB%93%9C</link>
            <guid>https://velog.io/@sang-gyeong/%EC%95%B5%EA%B7%A4%EB%9F%AC-%EC%83%9D%EB%AA%85%EC%A3%BC%EA%B8%B0%EC%99%80-%ED%9B%85-%EB%A9%94%EC%86%8C%EB%93%9C</guid>
            <pubDate>Sun, 08 Aug 2021 10:20:20 GMT</pubDate>
            <description><![CDATA[<h1 id="생명주기life-cycle와-훅-메소드">생명주기(life cycle)와 훅 메소드</h1>
<p>컴포넌트와 디렉티브는 생명주기를 갖습니다.
이 생명주기는 컴포넌트와 디렉티브가 생성하고 소멸되기까지의 여러 과정을 말하며
Angular에 의해 관리됩니다.</p>
<p>다시 말해 Angular는 생명주기를 통해 컴포넌트와 디렉티브를 생성하고 렌더링하며,
프로퍼티의 변화를 체크하고 DOM에서 제거하는 일련의 과정을 관리합니다.
개발자는 Angular가 제공하는 생명주기 훅 메소드를 구현하여,
각각의 생명주기 단계에서 처리하여야 할 행위를 정의할 수 있습니다.</p>
<p><img src="https://images.velog.io/images/sang-gyeong/post/eafc7008-fe8d-4fdc-9f3f-4a7262182f10/angular_lifecycle.png" alt=""></p>
<p>Angular는 위 그림의 순서대로 생명주기를 관리하고,
생명주기 이름 앞에 ng가 붙은 생명주기 훅 메소드를 제공합니다.
<br></p>
<h2 id="1-생명주기-훅-메소드">1. 생명주기 훅 메소드</h2>
<p>생명주기 훅 메소드는 <strong>인터페이스</strong>의 형태로 제공됩니다.</p>
<p>예를 들어 OnInit 생명주기에 처리되어야 할 행위를 정의하기 위해서는 훅 메소드 ngOnInit를 구현합니다.
이 ngOnInit 메소드는 추상 메소드이며 OnInit 인터페이스에 포함되어 있습니다.</p>
<pre><code class="language-tsx">interface OnInit {
    ngOnInit(): void
}</code></pre>
<p>이와 같이 생명주기(OnInit)에는 동일한 이름의 인터페이스(OnInit)가 존재합니다.
그리고 이 인터페이스는 생명주기 이름 앞에 ng 접두어가 붙은 추상메소드 (ngOnInit)를 포함합니다.</p>
<p>생명주기 (OnInit)에 처리되어야 할 행위를 정의하려면,
생명주기 인터페이스 (OnInit)의 추상메소드(ngOnInit)를 구현해야 합니다.</p>
<pre><code class="language-tsx">import { Component, OnInit } from &#39;@angular/core&#39;;

...
export class AppComponent implements OnInit {
    name = &#39;Lee&#39;;

    constructor() { }

    // 생명주기 OnInit 단계에 실행할 처리를 구현한다.
    ngOnInit() {...}
}</code></pre>
<p>컴포넌트와 디렉티브는 클래스이므로 constructor의 호출에 의해 생성됩니다.
그 이후, Angular는 특별한 시점에 구현된 생명주기 훅 메소드를 호출합니다.</p>
<p>물론 모든 생명주기 훅 메소드를 구현할 필요는 없습니다.
특정 생명주기에 처리해야 할 행위가 있을 때, 필요한 생명주기 훅 메소드만 구현하면 됩니다.
각각의 생명주기 훅 메소드가 어떤 시점에 호출되는지 알아봅시다.
<br></p>
<h3 id="1-ngonchanges">1) ngOnChanges</h3>
<p><strong>부모 컴포넌트에서 자식 컴포넌트의 입력 프로퍼티(@Input)로 바인딩한 값이 초기화 또는 변경되었을 때 실행됩니다.</strong>
따라서 컴포넌트에 입력 프로퍼티가 없을 때는 호출할 필요가 없습니다.</p>
<pre><code class="language-tsx">class MyComponent implements OnChanges {
    @Input() props1: number;
    @Imput() props2: string;

    ngOnChanges(changes: SimpleChanges) {
        // changes는 모든 입력 프로퍼티의 이전 값과 현재 값을 포함한다.
        console.log(changes);

        /*
        { prop1: SimpleChange, prop2: SimpleChange}
            prop1: SimpleChange {previousValue: undefined, currentValue: 100, firstChange: true}
            prop2: SimpleChange {previousValue: undefined, currentValue: &quot;string&quot;, firstChange: true}
        */
        }
    }</code></pre>
<p>ngOnChanges는 ngOnInit 이전에 입력 프로퍼티가 존재하는 경우, 최소 1회 호출됩니다.
이후에는 입력 프로퍼티가 변경될 때마다 반복 호출됩니다.
(이때의 변경은 입력 프로퍼티의 참조 변경을 말합니다.)
<strong>다시 말해 기본 자료형의 값이 재할당되없을 때와 객체의 참조가 변경되었을 때만 반응합니다.</strong></p>
<p>ngOnChanges는 입력 프로퍼티의 <strong>참조 변경에만 반응</strong>하므로,
입력 프로퍼티가 아닌 일반 컴포넌트 프로퍼티의 내용이 변경되었을 때는 반응하지 않습니다.</p>
<p>ngOnChanges 메소드는 입력 프로퍼티의 변경 정보를 담고있는
simpleChanges 객체를 인자로 전달받을 수 있습니다.
<strong>이 객체는 모든 입력 프로퍼티의 이전 값과 현재 값을 포함합니다.</strong>
<br></p>
<h3 id="2-ngoninit">2) ngOnInit</h3>
<p><strong>ngOnChanges 이후, 입력 프로퍼티를 포함한 모든 프로퍼티의 초기화가 완료된 시점에 한번만 호출됩니다.</strong></p>
<p>constructor는 TypeScript 클래스의 메소드로서 인스턴스 생성을 위해 호출됩니다.
Angular에서 constructor는 의존성 주입을 위해 사용되기도 하지만,
Angular의 생명주기와 직접적인 관계는 없습니다.</p>
<p>⚠️ <strong>TypeScript에서는 constructor에서 프로퍼티를 초기화하는 것이 일반적이지만,</strong>
<strong>Angular에서 프로퍼티의 초기화 처리는 contructor가 아닌 ngOnInit에서 수행하는 것이 좋습니다.</strong></p>
<p>간단한 값으로 프로퍼티를 초기화하는 것은 문제가 되지 않지만,
서버에서 데이터를 가져와 할당하는 것과 같이 복잡한 처리는 constructor가 아닌 ngOnInit에서 수행해야 합니다.</p>
<ul>
<li>(참고)Constructor에서 복잡한 처리를 피해야 하는 이유
: <a href="http://misko.hevery.com/code-reviewers-guide/flaw-constructor-does-real-work/">http://misko.hevery.com/code-reviewers-guide/flaw-constructor-does-real-work/</a></li>
</ul>
<p>constructor가 실행되는 시점에 Angular에서 관리하는 입력 프로퍼티는 초기화되기 이전의 상태이며 undefined가 반환됩니다. 만일 이 시점에 입력 프로퍼티를 사용한다면 의도하지 않은 결과가 발생하기 때문에 주의해야 합니다.</p>
<p>ngOnInit에서는 입력 프로퍼티의 참조가 보장되야 합니다.
입력 프로퍼티의 초기화는 ngOnChanges에서 처음 수행되지만,
ngOnChanges는 입력 프로퍼티가 변경될 때마다 반복 호출됩니다.
<strong>따라서 프로퍼티 초기화를 수행하기에 가장 적당한 훅 메소드는 ngOnInit입니다.</strong></p>
<br>

<h3 id="3-ngdocheck">3) ngDoCheck</h3>
<p>ngOnInit 이후, 컴포넌트 또는 디렉티브의 모든 상태 변화가 발생할 때마다 호출됩니다.
다시 말해 변화 감지(Change Detection) 로직이 실행될 때 호출됩니다.</p>
<p>ngDoCheck는 Angular의 변화 감지에 의해 감지되지 않거나 감지할 수 없는 변경 사항을,
수동으로 <strong>더티 체크(dirty check)</strong>하기 위해 사용합니다.
커스텀 더티 체크를 통해 사용자 변화 감지 로직을 구현하기 위해서는
Angular가 제공하는 KeyValueDiffers와 IterableDiffers를 사용합니다.</p>
<p>⚠️ 하지만 ngOnCheck는 모든 상태 변화가 발생할 때마다 매번 호출되기 때문에 성능에 악영향을 줄 수 있습니다.
가장 바람직한 것은 Angular의 변화 감지가 상태 변화를 감지하도록 코드를 구현하는 것이지만,
ngDoCheck를 사용할 수밖에 없는 상황이라면 최대한 가벼운 처리로 성능에 무리를 주지 않도록 주의하여야 합니다.</p>
<p>ngOnChanges는 입력 프로퍼티에 바인딩된 값이 초기화 또는 변경(기본 자료형의 값이 재할당되었을 때와 객체의 참조가 변경)되었을 때만 반응하여 호출되지만, ngDoCheck는 모든 상태의 변경에 의해 호출됩니다.
ngOnChanges와 ngDoCheck의 차이점에 대해서는 뒤에서 보다 자세히 살펴보도록 합시다.
<br></p>
<h3 id="4-ngaftercontentinit">4) ngAfterContentInit</h3>
<p>ngContent 디렉티브를 사용하여 외부 콘텐츠를 컴포넌트의 뷰에 콘텐트 프로젝션한 이후 호출됩니다.
첫 번째 ngDoCheck 호출 이후에 한 번만 호출되며, 컴포넌트에서만 동작하는 컴포넌트 전용 훅 메소드입니다.</p>
<pre><code>👉 콘텐츠 프로젝션 (Content Projection)

부모 컴포넌트가 자식 컴포넌트에게 부모 컴포넌트의 템플릿 일부를 전달하는 기능.
(@ContentChild, @ContentChildren)</code></pre><br>
### 5) ngAfterContentChecked

<p>콘텐츠 프로젝션에 의해 부모 컴포넌트가 전달한 부모 컴포넌트의 템플릿 조각을 체크한 후 호출됩니다.
ngAfterContentInit 호출 이후, ngDoCheck가 호출된 이후에 호출되며
컴포넌트에서만 동작하는 컴포넌트 전용 훅 메소드 입니다.
<br></p>
<h3 id="6-ngafterviewinit">6) ngAfterViewInit</h3>
<p>컴포넌트의 뷰와 자식 컴포넌트의 뷰를 초기화한 이후 호출됩니다.
첫 번째 ngAfterContentChecked 호출 이후 한 번만 호출되며
컴포넌트에서만 동작하는 컴포넌트 전용 훅 메소드 입니다.
<br></p>
<h3 id="7-ngafterviewchecked">7) ngAfterViewChecked</h3>
<p>컴포넌트의 뷰와 자식 컴포넌트의 뷰를 체크한 이후 호출됩니다.
첫 번째 ngAfterViewInit 호출 이후, ngAfterContentChecked 호출 이후 호출되며
컴포넌트에서만 동작하는 컴포넌트 전용 훅 메소드입니다.
<br></p>
<h3 id="8-ngondestroy">8) ngOnDestroy</h3>
<p>컴포넌트와 디렉티브가 소멸하기 이전 호출됩니다.
RxJs의 unsubscribe 등 메모리 누수를 방지하기 위한 코드 등을 정의합니다.
<br></p>
<hr>
<h2 id="2-생명주기-훅-메소드-실습">2. 생명주기 훅 메소드 실습</h2>
<p>예제를 통해 훅 메소드가 실제로 동작하는 것을 살펴보며, 각각의 호출 시점과 용도에 대해 알아보도록 합시다.
(실습 사이트 : <a href="https://stackblitz.com/edit/lifecycle-hooks-exam">https://stackblitz.com/edit/lifecycle-hooks-exam</a>)</p>
<blockquote>
<p>부모 컴포넌트</p>
</blockquote>
<pre><code class="language-tsx">import { Component } from &#39;@angular/core&#39;;

@Component({
    selector: &#39;app-root&#39;,
    template: `
        &lt;button (click)=&quot;status = !status&quot;&gt;
            {{ status ? &#39;Destroy Child&#39; : &#39;Create Child&#39; }}
        &lt;/button&gt;
        &lt;app-root *ngIf=&quot;status&quot; [prop]=&quot;prop&quot;&gt;&lt;/app-child&gt;
        `
    })
    export class AppComponent {
        status = false;
        prop = &#39;Hello&#39;;
    }</code></pre>
<p>자식 컴포넌트를 아래와 같이 작성합니다.
자식 컴포넌트는 @Input 데코레이터를 사용하여 부모 컴포넌트로부터 상태를 전달받습니다.
생성자 함수와 모든 생명주기 훅 메소드를 구현하여 훅 메소드의 호출 여부를 확인하여 봅시다.</p>
<blockquote>
<p>자식 컴포넌트</p>
</blockquote>
<pre><code class="language-tsx">import { Component, Input, OnChanges, OnInit, DoCheck, AfterContentInit, AfterContentChecked, AfterViewInit, AfterViewChecked, OnDestroy, SimpleChanges } from &#39;@angular/core&#39;;

@Component({
  selector: &#39;app-child&#39;,
  template: `
    &lt;p&gt;child component&lt;/p&gt;
    &lt;p&gt;부모 컴포넌트가 전달한 값: {{ prop }}&lt;/p&gt;
  `
})
export class ChildComponent implements OnChanges, OnInit, DoCheck, AfterContentInit, AfterContentChecked, AfterViewInit, AfterViewChecked, OnDestroy {
  @Input() prop: string;

  constructor() {
    console.log(&#39;[construnctor]&#39;);
    console.log(`prop: ${this.prop}`);
    this.prop = &#39;TEST&#39;;
    console.log(`prop: ${this.prop}`);
  }

  ngOnChanges(changes: SimpleChanges) {
    console.log(&#39;[OnChanges]&#39;);
    console.log(`prop: ${this.prop}`);
    console.log(&#39;changes:&#39;, changes);
  }

  ngOnInit() {
    console.log(&#39;[OnInit]&#39;);
    console.log(`prop: ${this.prop}`);
  }

  ngDoCheck() {
    console.log(&#39;[DoCheck]&#39;);
  }

  ngAfterContentInit() {
    console.log(&#39;[ngAfterContentInit]&#39;);
  }

  ngAfterContentChecked() {
    console.log(&#39;[ngAfterContentChecked]&#39;);
  }

  ngAfterViewInit() {
    console.log(&#39;[ngAfterViewInit]&#39;);
  }

  ngAfterViewChecked() {
    console.log(&#39;[ngAfterViewChecked]&#39;);
  }

  ngOnDestroy() {
    console.log(&#39;[ngOnDestroy]&#39;);
  }
}</code></pre>
<p>[Create Child] 버튼을 클릭하면 자식 컴포넌트가 생성되면서 훅 메소드가 순차적으로 호출됩니다.
버튼 클릭 후 콘솔에 출력되는 결과는 다음과 같습니다.</p>
<pre><code class="language-tsx">[Constructor]
prop: undefined
prop: TEST

[OnChanges]
prop: Hello
changes: {prop: SimpleChange}
prop: SimpleChange {previousValue: undefined, currentValue: &quot;Hello&quot;, firstChange: true&quot;
__proto__: Object

[OnInit]
prop: Hello

[DoCheck]

[ngAfterContentInit]

[ngAfterContentChecked]

[ngAfterViewInit]

[ngAfterViewChecked]</code></pre>
<p>가장 먼저 constructor가 호출됩니다.</p>
<p>constructor는 인스턴스 생성을 위해 호출되며 Angular가 관리하는 메소드가 아닙니다.
constructor에서 입력 프로퍼티를 참조하면 undefined가 출력되는데, 
이는 입력 프로퍼티의 초기화가 OnChanges에서 완성되기 때문입니다.
<strong>따라서 위 예제처럼 constructor에서 입력 프로퍼티를 초기화하는 것은 무의미한 행위이며 해서도 안됩니다.</strong></p>
<p>ngOnChanges는 자식 컴포넌트에 입력 프로퍼티가 존재하기 때문에 실행되었습니다.
만약 자식 컴포넌트에 입력 프로퍼티가 존재하지 않으면 ngOnChanges는 호출되지 않습니다.
이후 OnInit, DoCheck, ngAfterContentInit, ngAfterContentChecked, ngAfterViewInit, ngAfterViewChecked가 순차적으로 호출되는 것을 확인할 수 있습니다.</p>
<p>이제 생성된 자식 컴포넌트를 소멸시켜 봅시다.
[Destroy Child] 버튼은 ngIf에 false를 할당하여 자식 컴포넌트를 소멸시킵니다.
[Destroy Child] 버튼을 클릭하면 ngOnDestroy가 호출되는 것을 확인할 수 있습니다.
<br></p>
<hr>
<h3 id="ngonchanges와-ngdocheck">ngOnChanges와 ngDoCheck</h3>
<p>ngOnChanges와 ngDoCheck는 모두 상태 변화와 관계가 있습니다.
하지만 ngOnChange는 입력 프로퍼티의 초기화, 입력 프로퍼티의 참조 변경 시에 호출되고,
ngOnCheck는 모든 상태 변화 시점, 즉 변화 감지 로직이 실행될 때 호출됩니다.</p>
<p>하지만 객체의 경우, 프로퍼티의 값을 변경하여도 객체의 참조는 변경되지 않기 때문에
ngOnChanges는 이 변화에 반응하지 않습니다.
즉, 기본 자료형이나 불변 객체와 같이 이뮤터블한 값에만 반응합니다.</p>
<p>아래에서 실습을 통해 확인해 봅시다.
(실습 사이트 : <a href="https://stackblitz.com/edit/lifecycle-hooks-ngonchanges-ngdocheck">https://stackblitz.com/edit/lifecycle-hooks-ngonchanges-ngdocheck</a>)</p>
<blockquote>
<p>부모 컴포넌트</p>
</blockquote>
<pre><code class="language-tsx">import { Component } from &#39;@angular/core&#39;;

@Component({
  selector: &#39;app-root&#39;,
  template: `
    &lt;button (click)=&quot;status = !status&quot;&gt;
      {{ status ? &#39;Destroy Child&#39; : &#39;Create Child&#39; }}
    &lt;/button&gt;
    &lt;div *ngIf=&quot;status&quot;&gt;
      &lt;button (click)=&quot;immutable=&#39;HELLO&#39;&quot;&gt;
        기본자료형 프로퍼티 변경
      &lt;/button&gt;
      &lt;button (click)=&quot;mutable.name=&#39;kim&#39;&quot;&gt;
        객체형 프로퍼티 변경
      &lt;/button&gt;

      &lt;app-child 
        [immutable]=&quot;immutable&quot; 
        [mutable]=&quot;mutable&quot;&gt;&lt;/app-child&gt;
    &lt;/div&gt;
  `
})
export class AppComponent {
  status = false;

  immutable = &#39;Hello&#39;;
  mutable = { name: &#39;Lee&#39; };
}</code></pre>
<p>부모 컴포넌트의 버튼에 의해 자식 컴포넌트가 생성 소멸하도록 ngIf 디렉티브를 사용하도록 합니다.
자식 컴포넌트가 생성된 이후 2개의 버튼이 나타나는데,
이 버튼이 변경하고 전달하는 값은 이뮤터블한 값과 뮤터블한 값입니다.</p>
<blockquote>
<p>자식 컴포넌트</p>
</blockquote>
<pre><code class="language-tsx">import { Component, Input, OnChanges, OnInit, DoCheck, AfterContentInit, AfterContentChecked, AfterViewInit, AfterViewChecked, OnDestroy, SimpleChanges } from &#39;@angular/core&#39;;

@Component({
  selector: &#39;app-child&#39;,
  template: `
    &lt;p&gt;child component&lt;/p&gt;
    &lt;p&gt;부모 컴포넌트가 전달한 값: {{ immutable }}&lt;/p&gt;
    &lt;p&gt;부모 컴포넌트가 전달한 값: {{ mutable | json }}&lt;/p&gt;
  `
})
export class ChildComponent implements OnChanges, OnInit, DoCheck, AfterContentInit, AfterContentChecked, AfterViewInit, AfterViewChecked, OnDestroy {
  @Input() immutable: string;
  @Input() mutable: object;
  prop = &#39;normal prop&#39;;

  constructor() {
    console.log(&#39;[construnctor]&#39;);
  }

  ngOnChanges(changes: SimpleChanges) {
    console.log(&#39;[OnChanges]&#39;);
    console.log(`changes:`, changes);
    console.log(`immutable: ${this.immutable}`);
    console.log(`mutable:`, this.mutable);
  }

  ngOnInit() {
    console.log(&#39;[OnInit]&#39;);
    console.log(`prop: ${this.prop}`);
    console.log(`immutable: ${this.immutable}`);
    console.log(`mutable:`, this.mutable);
  }

  ngDoCheck() {
    console.log(&#39;[DoCheck]&#39;);
    console.log(`immutable: ${this.immutable}`);
    console.log(`mutable:`, this.mutable);
  }

  ngAfterContentInit() {
    console.log(&#39;[ngAfterContentInit]&#39;);
  }

  ngAfterContentChecked() {
    console.log(&#39;[ngAfterContentChecked]&#39;);
  }

  ngAfterViewInit() {
    console.log(&#39;[ngAfterViewInit]&#39;);
  }

  ngAfterViewChecked() {
    console.log(&#39;[ngAfterViewChecked]&#39;);
  }

  ngOnDestroy() {
    console.log(&#39;[ngOnDestroy]&#39;);
  }
}</code></pre>
<p>자식 컴포넌트는 @Input 컴포넌트를 사용하여 부모 컴포넌트로부터 상태를 전달받습니다.
이때 전달된 값의 타입에 따라 다르게 동작하는 것을 확인할 수 있습니다.</p>
<ul>
<li>👉 <strong>기본 자료형 프로퍼티 변경</strong></li>
</ul>
<p>첫번째 &lt;기본 자료형 프로퍼티 변경&gt; 버튼을 클릭하면
기본 자료형인 string타입의 값을 자식 컴포넌트에 전송합니다.
이때 자식 컴포넌트의 입력 프로퍼티 immutable에 새로운 값이 재할당됩니다.</p>
<p>입력 프로퍼티 immutable은 기본 자료형이므로 입력 프로퍼티 immutable의 값은 절대로 변경할 수 없습니다. 재할당 이전의 값인 문자열 &quot;Hello&quot;를 변경하는 것이 아니라,
새로운 문자열 &quot;Hello&quot;를 메모리에 생성하고 입력 프로퍼티 immutable은 이것을 가르킵니다.</p>
<p><strong>즉, 재할당 시에 입력 프로퍼티 immutable이 가리키는 참조가 변경됩니다.
따라서 ngOnChanges가 호출됩니다.</strong></p>
<p>첫 번째 &lt;기본 자료형 프로퍼티 변경&gt; 버튼을 클릭한 후 콘솔에 출력되는 결과는 다음과 같습니다.</p>
<pre><code class="language-tsx">[OnChanges]
changes: {immutable: SimpleChange}
    immutable:SimpleChange {previousValue: &quot;Hello&quot;, currentValue: &quot;HELLO&quot;, firstChange: false
immutable: HELLO
mutable: {name: &quot;Lee&quot;}

[DoCheck]
immutable: STRING
mutable: {name: &quot;Lee&quot;}

[ngAfterContentChecked]

[ngAfterViewChecked]</code></pre>
<ul>
<li><strong>👉 객체형 프로퍼티 변경</strong></li>
</ul>
<p>두 번째 &lt;객체형 프로퍼티 변경&gt; 버튼을 클릭하면 객체의 프로퍼티값을 변경하여 자식 컴포넌트에 전송합니다.
<strong>이때 객체는 뮤터블 하므로, 객체의 프로퍼티값을 변경하도라도 참조가 변경되지 않습니다.
따라서 입력 프로퍼티가 변경되지 않은 것으로 간주되어 OnChanges가 호출되지 않습니다.</strong></p>
<p>하지만 Angular의 변화 감지 로직은 실행되며 변화 감지 로직이 실행될 때 호출되는 ngDoCheck도 호출됩니다.
두 번째 &lt;객체형 프로퍼티 변경&gt; 버튼을 클릭한 후 콘솔에 출력되는 결과는 다음과 같습니다.</p>
<pre><code class="language-tsx">[DoCheck]
immutable: HELLO
mutable: {name: &quot;kim&quot;}

[ngAfterContentChecked]

[ngAfterViewChecked]</code></pre>
<p><strong>ngDoCheck는 모든 상태 변경에 의해 호출됩니다.
따라서 입력 프로퍼티뿐만 아니라 컴포넌트 프로퍼티가 변경되어도 호출됩니다.</strong>
예를 들어, 자식 컴포넌트 템플릿에 아래의 버튼을 추가하고 클릭하면 ngDoCheck는 호출됩니다.</p>
<pre><code class="language-html">&lt;button (click)=&quot;prop=checked!&quot;&gt;컴포넌트 프로퍼티 변경&lt;/button&gt;</code></pre>
<br>

<hr>
<h2 id="3-디렉티브-생명주기-훅-메소드">3. 디렉티브 생명주기 훅 메소드</h2>
<p>디렉티브도 컴포넌트와 동일한 생명주기와 훅 메소드를 사용합니다.
다만, 뷰가 없는 디렉티브의 특성상 뷰에 관련한 생명주기는 디렉티브에 존재하지 않습니다.</p>
<p>뷰와 관련된 생명주기인
AfterViewInit, AfterViewChecked, AfterContentInit, AfterContentChecked는
디렉티브 생명주기에 존재하지 않는 컴포넌트 전용 생명주기 입니다.</p>
<p><strong>따라서 디렉티브 생명주기에는 OnChanges, DoCheck, OnInit, OnDestroy만이 존재합니다.</strong></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[2-1. Angular의 구성요소 - Component]]></title>
            <link>https://velog.io/@sang-gyeong/2-1.-Angular%EC%9D%98-%EA%B5%AC%EC%84%B1%EC%9A%94%EC%86%8C-Component</link>
            <guid>https://velog.io/@sang-gyeong/2-1.-Angular%EC%9D%98-%EA%B5%AC%EC%84%B1%EC%9A%94%EC%86%8C-Component</guid>
            <pubDate>Sat, 07 Aug 2021 09:44:11 GMT</pubDate>
            <description><![CDATA[<h1 id="컴포넌트">컴포넌트</h1>
<hr>
<p>컴포넌트는 Angular의 핵심 구성 요소로서 Angular 애플리케이션은 컴포넌트를 중심으로 구성됩니다.
컴포넌트의 역할은 애플리케이션의 화면을 구성하는 <strong>뷰(View)</strong>를 생성하고 관리하는 것이고
Angular는 컴포넌트를 조립하여 하나의 완성된 애플리케이션을 작성합니다.
컴포넌트는 컴포넌트 클래스와 템플릿으로 구성되는데,</p>
<ul>
<li>컴포넌트 클래스는 애플리케이션 데이터와 로직을 처리하고</li>
<li>템플릿은 화면에 표시할 HTML을 정의합니다.</li>
</ul>
<p>Angular 컴포넌트는 컴포넌트 클래스에 @<a href="https://angular.kr/api/core/Component">Component</a> 데코레이터를 사용해서
컴포넌트에 대한 메타데이터를 지정하면서 템플릿도 함께 지정하는 형태를 띄고 있습니다.</p>
<pre><code class="language-tsx">import { Component } from &#39;@angular/core&#39;;

@Component({
  selector: &#39;app-hello&#39;,
  templateUrl: &#39;./hello.component.html&#39;,
  styleUrls: [&#39;./hello.component.scss&#39;]
})

export class HelloComponent {
  // 여기서 로직과 데이터를 관리
}</code></pre>
<p><strong>👉 (참고) 컴포넌트 메타데이터</strong></p>
<ul>
<li><p><code>selector</code>: 컴포넌트 인스턴스가 DOM 트리의 어떤 자리에 위치할지 CSS 셀렉터로 지정합니다.
  Angular는 템플릿 HTML에 사용된 컴포넌트 셀렉터를 기준으로 컴포넌트의 인스턴스를 생성하기 때문에 컴포넌트에 CSS 셀렉터 를 지정해야 합니다.</p>
</li>
<li><p><code>templateUrl</code>: 컴포넌트의 HTML 템플릿을 외부 파일에 정의할 때, 이 템플릿 파일의 위치를 지정합니다.
  템플릿을 인라인으로 구성하려면 이 프로퍼티 대신 <code>template</code> 프로퍼티를 사용하면 됩니다. </p>
</li>
<li><p><code>providers</code>: 컴포넌트가 생성될 때 의존성으로 주입되는 서비스의 프로바이더를 지정합니다.</p>
</li>
</ul>
<pre><code>View는 컴포넌트 메타데이터에서 지정하는 외부 템플릿 파일이나
인라인 템플릿이 컴포넌트 코드와 연결되면서 정의됩니다.
템플릿을 외부 파일에서 불러올지 컴포넌트 안에 포함시킬지는 메타데이터 설정에 의해 결정됩니다.
그리고 의존성으로 주입받아야 하는 서비스가 있다면 이 내용도 메타데이터에 지정할 수 있습니다.</code></pre><br>

<h2 id="컴포넌트-트리">컴포넌트 트리</h2>
<p>Angular 애플리케이션은 분할된 컴포넌트로 구성되기 때문에
컴포넌트 간에는 컴포넌트 트리로 표현되는 &#39;부모-자식&#39; 관계가 형성됩니다.
컴포넌트 간의 부모-자식 관계는 데이터와 이벤트가 왕래하는 상태 정보 흐름의 통로가 되며,
이를 위해 상태 공유가 이루어지기 때문에 컴포넌트 간의 부모-자식 관계는
Angular 애플리케이션에서 중요한 의미를 갖습니다.
따라서 설계 시점부터 화면을 어떠한 컴포넌트 단위로 분할할 것인지에 대한 검토가 필요합니다.</p>
<br>

<h2 id="컴포넌트의-기본-동작-구조">컴포넌트의 기본 동작 구조</h2>
<p>컴포넌트의 기본 동작 구조를 알아보기 위해
@Component 데코레이터의 templateUrl 프로퍼티에 설정된 템플릿을 살펴봅시다.</p>
<pre><code>&lt;h1&gt;
  Welcome to {{ title }}!
&lt;/h1&gt;</code></pre><p>템플릿은 컴포넌트의 뷰를 정의하기 위해 HTML과 Angular 고유의 템플릿 문법으로 작성합니다.
<code>{{ title }}</code>은 템플릿 문법 중 하나인 인터폴레이션으로, 컴포넌트 클래스의 데이터를 템플릿에 바인딩합니다.</p>
<p>이러한 방식을 <strong>데이터 바인딩</strong>이라고 합니다다.
<img src="https://images.velog.io/images/sang-gyeong/post/8d293ad8-9045-45fd-9c85-16f245298f98/data-binding.png" alt=""></p>
<p>컴포넌트는 데이터 바인딩에 의해 템플릿과 컴포넌트 클래스의 데이터를 유기적으로 연계합니다.
기본적인 동작 구조는 아래와 같습니다.
<img src="https://images.velog.io/images/sang-gyeong/post/3e871fdf-fc7e-4739-8e4c-c68e627fd56b/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-08-07%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%206.48.42.png" alt=""></p>
<ul>
<li>출처 : <a href="https://poiemaweb.com/angular-component-basics">https://poiemaweb.com/angular-component-basics</a></li>
</ul>
<br>


<h2 id="컴포넌트-간-통신방법">컴포넌트 간 통신방법</h2>
<h3 id="1-부모-자식-관계일때">1. 부모-자식 관계일때</h3>
<h4 id="1-input-데코레이터-부모-→-자식">1) <strong>@Input 데코레이터 (부모 → 자식)</strong></h4>
<p>자식 컴포넌트/디렉티브에 있는 특정 프로퍼티가
부모 컴포넌트/디렉티브에서 값을 받는다는 것을 지정하는 데코레이터입니다.
프로퍼티 바인딩으로 전달한 데이터는 자식 컴포넌트에서 @Input 데코레이터로 받을 수 있습니다.</p>
<blockquote>
<p>child</p>
</blockquote>
<pre><code class="language-tsx">import { Component, Input } from &#39;@angular/core&#39;;

import { Hero } from &#39;./hero&#39;;

@Component({
  selector: &#39;app-hero-child&#39;,
  template: `
    &lt;h3&gt;{{hero.name}} says:&lt;/h3&gt;
    &lt;p&gt;I, {{hero.name}}, am at your service, {{masterName}}.&lt;/p&gt;
  `
})
export class HeroChildComponent {
  @Input() hero: Hero;
  @Input(&#39;master&#39;) masterName: string; 
    // masterName 프로퍼티를 외부에서 바인딩 할 때 &#39;master&#39;라는 이름으로 사용하기 위한 선언
}</code></pre>
<blockquote>
<p>parent</p>
</blockquote>
<pre><code class="language-tsx">import { Component } from &#39;@angular/core&#39;;

import { HEROES } from &#39;./hero&#39;;

@Component({
  selector: &#39;app-hero-parent&#39;,
  template: `
    &lt;h2&gt;{{master}} controls {{heroes.length}} heroes&lt;/h2&gt;
    &lt;app-hero-child *ngFor=&quot;let hero of heroes&quot;
      [hero]=&quot;hero&quot;
      [master]=&quot;master&quot;&gt;
    &lt;/app-hero-child&gt;
  `
})
export class HeroParentComponent {
  heroes = HEROES;
  master = &#39;Master&#39;;
}</code></pre>
<p>👉 
parent는 *ngFor를 사용해서 배열에 있는 항목마다 child를 만드는데,
각 컴포넌트를 만들때마다 master 문자열 프로퍼티를 자식 컴포넌트의 master로 연결하고,
반복되는 hero 인스턴스를 자식 컴포넌트의 hero 프로퍼티로 바인딩 합니다.</p>
<h4 id="2-output-데코레이터-자식-→-부모">2) <strong>@Output 데코레이터 (자식 → 부모)</strong></h4>
<p>@Output 데코레이터는 자식의 데이터를 부모에 전달하는데 사용합니다.
주로 <strong>이벤트를 바인딩</strong>하여 사용하며, 자식 컴포넌트에서 발생한 이벤트를 부모 컴포넌트가 이벤트 바인딩해서 데이터를 전달받게 됩니다.</p>
<p>자식 컴포넌트에서 어떤 이벤트가 발생하면 이 이벤트는 <a href="https://angular.kr/api/core/EventEmitter">EventEmitter</a> 타입으로 지정한 프로퍼티를 통해 부모 컴포넌트에게 보낼 수 있습니다. 부모 컴포넌트는 이 이벤트를 바인딩해서 원하는 로직을 실행하면 됩니다.</p>
<p>예제를 통해서 살펴봅시다.</p>
<blockquote>
<p>child</p>
</blockquote>
<pre><code class="language-tsx">import { Component, EventEmitter, Input, Output } from &#39;@angular/core&#39;;

@Component({
  selector: &#39;app-voter&#39;,
  template: `
    &lt;h4&gt;{{name}}&lt;/h4&gt;
    &lt;button (click)=&quot;vote(true)&quot;  [disabled]=&quot;didVote&quot;&gt;Agree&lt;/button&gt;
    &lt;button (click)=&quot;vote(false)&quot; [disabled]=&quot;didVote&quot;&gt;Disagree&lt;/button&gt;
  `
})
export class VoterComponent {
  @Input()  name: string;
  @Output() voted = new EventEmitter&lt;boolean&gt;();
  didVote = false;

  vote(agreed: boolean) {
    this.voted.emit(agreed);
    this.didVote = true;
  }
}</code></pre>
<blockquote>
<p>parent</p>
</blockquote>
<pre><code class="language-tsx">import { Component } from &#39;@angular/core&#39;;

@Component({
  selector: &#39;app-vote-taker&#39;,
  template: `
    &lt;h2&gt;Should mankind colonize the Universe?&lt;/h2&gt;
    &lt;h3&gt;Agree: {{agreed}}, Disagree: {{disagreed}}&lt;/h3&gt;
    &lt;app-voter *ngFor=&quot;let voter of voters&quot;
      [name]=&quot;voter&quot;
      (voted)=&quot;onVoted($event)&quot;&gt;
    &lt;/app-voter&gt;
  `
})
export class VoteTakerComponent {
  agreed = 0;
  disagreed = 0;
  voters = [&#39;Narco&#39;, &#39;Celeritas&#39;, &#39;Bombasto&#39;];

  onVoted(agreed: boolean) {
    agreed ? this.agreed++ : this.disagreed++;
  }
}</code></pre>
<p>이 예제에서 버튼을 클릭하면 자식 컴포넌트로 불리언 타입의 데이터를 전달합니다.
그리고 parent의 <code>onVoted()</code> 함수가 이벤트 객체를 인자로 받아서 <code>agree</code>와 <code>disagree</code> 카운터를 갱신합니다.</p>
<p>이 때 전달되는 이벤트 객체는 템플릿에서 $event라는 이름으로 접근할 수 있으며,
템플릿에서 이벤트 핸들러 함수에 인자로 전달하기 때문에 컴포넌트 클래스 코드에서 이 이벤트 객체를 활용할 수 있습니다.</p>
<ul>
<li>예제2(장바구니)</li>
</ul>
<blockquote>
<p>child</p>
</blockquote>
<pre><code class="language-tsx">    @Component({
        selector: &#39;app-child&#39;,
        template: `
        &lt;p&gt;
        {{item}}
        &lt;button (click)=&quot;addToCart(item)&quot;&gt;추가&lt;/button&gt;
        &lt;/p&gt;
    `
    })
    export class ChildComponent {
        @Input() item;
        @Output() pushItem = new EventEmitter&lt;string&gt;();
        addToCart(item:string) {
            this.pushItem.emit(item);
        }
    }</code></pre>
<blockquote>
<p>parent</p>
</blockquote>
<pre><code class="language-tsx">    @Component({
      selector: &#39;app-parent&#39;,
      template: `
        &lt;h1&gt;식품 / 과자류&lt;/h1&gt;

            &lt;app-child
                *ngFor=&quot;let item of list&quot;
                [item]=&quot;item&quot;
                (pushItem)=&quot;pushItem($event)&quot;&gt;
            &lt;/app-child&gt;

        &lt;h1&gt;내 장바구니&lt;/h1&gt;
        &lt;div *ngFor=&quot;let item of cartList&quot;&gt;
          {{item}}: {{cart[item]}}개
        &lt;/div&gt;
        `
    })
    export class ParentComponent {
      cart={};
      cartList = [];
      list=[&#39;초코파이&#39;, &#39;빅파이&#39;, &#39;참크래커&#39;, &#39;마이쮸&#39;, &#39;자이리톨&#39;, &#39;꼬깔콘&#39;]
      pushItem (item) {
        this.cart[item]
        ? this.cart[item]++
        : (this.cart[item] = 1, this.cartList.push(item))
      }
    }</code></pre>
<h4 id="-참고">(+) 참고</h4>
<h4 id="🚀-입력-프로퍼티를-세터setter로-가로채기">🚀 입력 프로퍼티를 세터(setter)로 가로채기</h4>
<pre><code>부모 컴포넌트에서 값이 전달될 때 추가 로직을 실행하기 위해 입력 프로퍼티에 세터를 사용할 수 있습니다.</code></pre><blockquote>
<p>child</p>
</blockquote>
<pre><code class="language-tsx">    import { Component, Input } from &#39;@angular/core&#39;;

    @Component({
      selector: &#39;app-name-child&#39;,
      template: &#39;&lt;h3&gt;&quot;{{name}}&quot;&lt;/h3&gt;&#39;
    })
    export class NameChildComponent {
      @Input()
      get name(): string { return this._name; }
      set name(name: string) {
        this._name = (name &amp;&amp; name.trim()) || &#39;&lt;no name set&gt;&#39;;
      }
      private _name = &#39;&#39;;
    }</code></pre>
<blockquote>
<p>parent</p>
</blockquote>
<pre><code class="language-tsx">    import { Component } from &#39;@angular/core&#39;;

    @Component({
      selector: &#39;app-name-parent&#39;,
      template: `
      &lt;h2&gt;Master controls {{names.length}} names&lt;/h2&gt;
      &lt;app-name-child *ngFor=&quot;let name of names&quot; [name]=&quot;name&quot;&gt;&lt;/app-name-child&gt;
      `
    })
    export class NameParentComponent {
      // &#39;Dr IQ&#39;, &#39;&lt;빈 값&gt;&#39;, &#39;Bombasto&#39;를 표시합니다.
      names = [&#39;Dr IQ&#39;, &#39;   &#39;, &#39;  Bombasto  &#39;];
    }</code></pre>
<br>

<h4 id="🚀-ngonchanges로-입력-프로퍼티-가로채기">🚀 ngOnChanges()로 입력 프로퍼티 가로채기</h4>
<pre><code>입력 프로퍼티는 `[OnChanges](https://angular.kr/api/core/OnChanges)` 라이프싸이클 후킹 인터페이스를 사용하는 `ngOnChanges()` 메소드로도 가로챌 수 있습니다.</code></pre><blockquote>
<p>입력 프로퍼티 여러개를 가로채야 한다면 세터를 사용하는 것보다 이 방식이 더 편할 수 있습니다.</p>
</blockquote>
<pre><code class="language-tsx">    import { Component, Input, OnChanges, SimpleChanges } from &#39;@angular/core&#39;;

    @Component({
      selector: &#39;app-version-child&#39;,
      template: `
        &lt;h3&gt;Version {{major}}.{{minor}}&lt;/h3&gt;
        &lt;h4&gt;Change log:&lt;/h4&gt;
        &lt;ul&gt;
          &lt;li *ngFor=&quot;let change of changeLog&quot;&gt;{{change}}&lt;/li&gt;
        &lt;/ul&gt;
      `
    })
    export class VersionChildComponent implements OnChanges {
      @Input() major: number;
      @Input() minor: number;
      changeLog: string[] = [];

      ngOnChanges(changes: SimpleChanges) {
        const log: string[] = [];
        for (const propName in changes) {
          const changedProp = changes[propName];
          const to = JSON.stringify(changedProp.currentValue);
          if (changedProp.isFirstChange()) {
            log.push(`Initial value of ${propName} set to ${to}`);
          } else {
            const from = JSON.stringify(changedProp.previousValue);
            log.push(`${propName} changed from ${from} to ${to}`);
          }
        }
        this.changeLog.push(log.join(&#39;, &#39;));
      }
    }</code></pre>
<p>child는 major와 minor 두 입력 프로퍼티 값이 변경되는 것을 감지하고 이 내용을 로그로 출력합니다:</p>
<blockquote>
<p>parent</p>
</blockquote>
<pre><code class="language-tsx">    import { Component } from &#39;@angular/core&#39;;

    @Component({
      selector: &#39;app-version-parent&#39;,
      template: `
        &lt;h2&gt;Source code version&lt;/h2&gt;
        &lt;button (click)=&quot;newMinor()&quot;&gt;New minor version&lt;/button&gt;
        &lt;button (click)=&quot;newMajor()&quot;&gt;New major version&lt;/button&gt;
        &lt;app-version-child [major]=&quot;major&quot; [minor]=&quot;minor&quot;&gt;&lt;/app-version-child&gt;
      `
    })
    export class VersionParentComponent {
      major = 1;
      minor = 23;

      newMinor() {
        this.minor++;
      }

      newMajor() {
        this.major++;
        this.minor = 0;
      }
    }</code></pre>
<br>

<h4 id="🚀-템플릿-지역변수로-자식-컴포넌트-접근하기-부모-템플릿에서-자식-컴포넌트-접근">🚀 템플릿 지역변수로 자식 컴포넌트 접근하기 <strong>(부모 템플릿에서 자식 컴포넌트 접근)</strong></h4>
<p>부모 컴포넌트는 자식 컴포넌트의 프로퍼티나 메소드에 직접 접근할 수 없습니다. 
하지만 부모 템플릿에 템플릿 참조 변수를 선언하면,
자식 컴포넌트의 프로퍼티나 메소드에 접근할 수 있습니다.</p>
<blockquote>
<p>child</p>
</blockquote>
<pre><code class="language-tsx">    import { Component, OnDestroy } from &#39;@angular/core&#39;;

    @Component({
      selector: &#39;app-countdown-timer&#39;,
      template: &#39;&lt;p&gt;{{message}}&lt;/p&gt;&#39;
    })
    export class CountdownTimerComponent implements OnDestroy {

      intervalId = 0;
      message = &#39;&#39;;
      seconds = 11;

      ngOnDestroy() { this.clearTimer(); }

      start() { this.countDown(); }
      stop()  {
        this.clearTimer();
        this.message = `Holding at T-${this.seconds} seconds`;
      }

      private clearTimer() { clearInterval(this.intervalId); }

      private countDown() {
        this.clearTimer();
        this.intervalId = window.setInterval(() =&gt; {
          this.seconds -= 1;
          if (this.seconds === 0) {
            this.message = &#39;Blast off!&#39;;
          } else {
            if (this.seconds &lt; 0) { this.seconds = 10; } // reset
            this.message = `T-${this.seconds} seconds and counting`;
          }
        }, 1000);
      }
    }</code></pre>
<blockquote>
<p>parent</p>
</blockquote>
<pre><code class="language-tsx">    import { Component } from &#39;@angular/core&#39;;
    import { CountdownTimerComponent } from &#39;./countdown-timer.component&#39;;

    @Component({
      selector: &#39;app-countdown-parent-lv&#39;,
      template: `
          &lt;h3&gt;Countdown to Liftoff (via local variable)&lt;/h3&gt;
          &lt;button (click)=&quot;timer.start()&quot;&gt;Start&lt;/button&gt;
          &lt;button (click)=&quot;timer.stop()&quot;&gt;Stop&lt;/button&gt;
          &lt;div class=&quot;seconds&quot;&gt;{{timer.seconds}}&lt;/div&gt;
          &lt;app-countdown-timer #timer&gt;&lt;/app-countdown-timer&gt;
      `,
      styleUrls: [&#39;../assets/demo.css&#39;]
    })
    export class CountdownLocalVarParentComponent { }</code></pre>
<p>👉
원래 부모 컴포넌트는 자식 컴포넌트의 <code>seconds</code> 프로퍼티나 <code>start</code>, <code>stop</code> 메소드에 직접 접근할 수 없습니다.
하지만 <code>&lt;countdown-timer&gt;</code>를 템플릿 지역 변수 <code>#timer</code>로 선언하면 이 변수를 사용해서 자식 컴포넌트에 접근할 수 있습니다.</p>
<p><strong>이 템플릿 지역 변수는 자식 컴포넌트 자체를 가리키며, 템플릿 지역 변수를 선언한 후에는 부모 컴포넌트의 템플릿에서 자식 컴포넌트의 프로퍼티나 메소드에 자유롭게 접근할 수 있습니다.</strong></p>
<br>

<h4 id="🚀-viewchild로-자식-컴포넌트-접근하기-부모-클래스에서-자식-컴포넌트-접근">🚀 ViewChild로 자식 컴포넌트 접근하기 (부모 클래스에서 자식 컴포넌트 접근)</h4>
<p>템플릿 지역 변수는 부모 컴포넌트의 템플릿에서만 자식 컴포넌트에 접근할 수 있기 때문에 자유롭게 활용하기에는 제한이 있습니다. 부모 컴포넌트의 클래스에서는 자식 컴포넌트에 접근할 수 없기 때문입니다.</p>
<p><strong>부모 컴포넌트의 클래스에서 자식 컴포넌트에 접근하려면 자식 컴포넌트에 ViewChild를 사용해서 부모 컴포넌트로 주입(inject)해야 합니다.</strong></p>
<blockquote>
<p>child</p>
</blockquote>
<pre><code class="language-tsx">    import { Component, OnDestroy } from &#39;@angular/core&#39;;

    @Component({
      selector: &#39;app-countdown-timer&#39;,
      template: &#39;&lt;p&gt;{{message}}&lt;/p&gt;&#39;
    })
    export class CountdownTimerComponent implements OnDestroy {

      intervalId = 0;
      message = &#39;&#39;;
      seconds = 11;

      ngOnDestroy() { this.clearTimer(); }

      start() { this.countDown(); }
      stop()  {
        this.clearTimer();
        this.message = `Holding at T-${this.seconds} seconds`;
      }

      private clearTimer() { clearInterval(this.intervalId); }

      private countDown() {
        this.clearTimer();
        this.intervalId = window.setInterval(() =&gt; {
          this.seconds -= 1;
          if (this.seconds === 0) {
            this.message = &#39;Blast off!&#39;;
          } else {
            if (this.seconds &lt; 0) { this.seconds = 10; } // reset
            this.message = `T-${this.seconds} seconds and counting`;
          }
        }, 1000);
      }
    }</code></pre>
<blockquote>
<p>parent</p>
</blockquote>
<pre><code class="language-tsx">    import { AfterViewInit, ViewChild } from &#39;@angular/core&#39;;
    import { Component } from &#39;@angular/core&#39;;
    import { CountdownTimerComponent } from &#39;./countdown-timer.component&#39;;

    @Component({
      selector: &#39;app-countdown-parent-vc&#39;,
      template: `
      &lt;h3&gt;Countdown to Liftoff (via ViewChild)&lt;/h3&gt;
      &lt;button (click)=&quot;start()&quot;&gt;Start&lt;/button&gt;
      &lt;button (click)=&quot;stop()&quot;&gt;Stop&lt;/button&gt;
      &lt;div class=&quot;seconds&quot;&gt;{{ seconds() }}&lt;/div&gt;
      &lt;app-countdown-timer&gt;&lt;/app-countdown-timer&gt;
      `,
      styleUrls: [&#39;../assets/demo.css&#39;]
    })
    export class CountdownViewChildParentComponent implements AfterViewInit {

      @ViewChild(CountdownTimerComponent)
      private timerComponent: CountdownTimerComponent;

      seconds() { return 0; }

      ngAfterViewInit() {
        // `seconds()` 메소드는 `CountdownTimerComponent.seconds`에서 다시 구현합니다.
        // 이 때 개발 모드에서 출력하는 단방향 바인딩 검사 에러를 방지하기 위해
        // 한 싸이클 기다려야 합니다.
        setTimeout(() =&gt; this.seconds = () =&gt; this.timerComponent.seconds, 0);
      }

      start() { this.timerComponent.start(); }
      stop() { this.timerComponent.stop(); }
    }</code></pre>
<p>👉
먼저, <a href="https://angular.kr/api/core/ViewChild">ViewChild</a> 데코레이터와 <a href="https://angular.kr/api/core/AfterViewInit">AfterViewInit</a> 라이프싸이클 후킹 인터페이스를 로드합니다.</p>
<p>그리고 <code>CountdownTimerComponent</code>를 <code>timerComponent</code> 프로퍼티로 선언하면서 @<a href="https://angular.kr/api/core/ViewChild">ViewChild</a> 데코레이터를 사용했습니다.</p>
<p>템플릿 지역변수가 자식 컴포넌트의 메소드를 직접 실행했던 것과는 달리,
자식 컴포넌트를 직접 호출하지 않고 부모 컴포넌트에 있는 <code>start</code>, <code>stop</code> 메소드를 사용하며, 현재 남아있는 초를 확인할 때도 부모 컴포넌트의 <code>seconds</code> 메소드를 활용합니다. 각각의 메소드에서 자식 컴포넌트에 접근하는 식으로 구현하는 것입니다.</p>
<p><strong>⚠️ 라이프 사이클 고려하기</strong></p>
<p>이 때 <code>ngAfterViewInit()</code> 라이프싸이클 후킹 함수가 중요합니다.</p>
<p>자식 컴포넌트인 타이머 컴포넌트는 Angular가 부모 컴포넌트의 뷰를 화면에 표시한 이후에야 사용할 수 있습니다. 그래서 뷰가 완전히 준비되기 전까지는 <code>0</code>을 표시합니다.</p>
<p>부모 컴포넌트의 뷰가 준비되면 자식 컴포넌트에서 시간을 가져오기 위해 <code>ngAfterViewInit</code> 라이프싸이클 후킹 함수를 실행하는데, Angular는 단방향 데이터 흐름을 권장하기 때문에 부모 컴포넌트의 뷰를 같은 JavaScript 실행 싸이클 안에서 갱신하는 것을 금지합니다.</p>
<p>그래서 <code>ngAfterViewInit()</code>에서 자식 컴포넌트의 시간을 가져와서 부모 컴포넌트 프로퍼티에 할당하는 것은 <code>setTimeout()</code> 으로 한 싸이클 늦췄습니다.</p>
<br>

<h4 id="4-service를-이용한-통신">4) <strong>Service를 이용한 통신</strong></h4>
<p>부모 컴포넌트와 자식 컴포넌트가 같은 서비스를 주입받는다면
이 서비스를 활용해서 양방향으로 데이터를 주고받을 수 있습니다.</p>
<p>컴포넌트에 주입되는 서비스는 그 컴포넌트에서 자유롭게 사용할 수 있습니다.
⚠️ 이 때 주입되는 서비스의 인스턴스가 동일해야 하기 때문에,
서비스 프로바이더를 별도로 지정하면 컴포넌트 통신에 활용할 수 없습니다.</p>
<blockquote>
<p>service</p>
</blockquote>
<pre><code class="language-tsx">import { Injectable } from &#39;@angular/core&#39;;
import { Subject } from &#39;rxjs&#39;;

@Injectable()
export class MissionService {

  // 문자열 타입의 옵저버블 소스
  private missionAnnouncedSource = new Subject&lt;string&gt;();
  private missionConfirmedSource = new Subject&lt;string&gt;();

  // 문자열 옵저버블 스트림
  missionAnnounced$ = this.missionAnnouncedSource.asObservable();
  missionConfirmed$ = this.missionConfirmedSource.asObservable();

  // 서비스가 옵저버블을 전달할 때 사용하는 메소드
  announceMission(mission: string) {
    this.missionAnnouncedSource.next(mission);
  }

  confirmMission(astronaut: string) {
    this.missionConfirmedSource.next(astronaut);
  }
}</code></pre>
<blockquote>
<p>MissionControl (Parent)</p>
</blockquote>
<pre><code class="language-tsx">import { Component } from &#39;@angular/core&#39;;

import { MissionService } from &#39;./mission.service&#39;;

@Component({
  selector: &#39;app-mission-control&#39;,
  template: `
    &lt;h2&gt;Mission Control&lt;/h2&gt;
    &lt;button (click)=&quot;announce()&quot;&gt;Announce mission&lt;/button&gt;
    &lt;app-astronaut *ngFor=&quot;let astronaut of astronauts&quot;
      [astronaut]=&quot;astronaut&quot;&gt;
    &lt;/app-astronaut&gt;
    &lt;h3&gt;History&lt;/h3&gt;
    &lt;ul&gt;
      &lt;li *ngFor=&quot;let event of history&quot;&gt;{{event}}&lt;/li&gt;
    &lt;/ul&gt;
  `,
  providers: [MissionService]
})
export class MissionControlComponent {
  astronauts = [&#39;Lovell&#39;, &#39;Swigert&#39;, &#39;Haise&#39;];
  history: string[] = [];
  missions = [&#39;Fly to the moon!&#39;,
              &#39;Fly to mars!&#39;,
              &#39;Fly to Vegas!&#39;];
  nextMission = 0;

  constructor(private missionService: MissionService) {
    missionService.missionConfirmed$.subscribe(
      astronaut =&gt; {
        this.history.push(`${astronaut} confirmed the mission`);
      });
  }

  announce() {
    const mission = this.missions[this.nextMission++];
    this.missionService.announceMission(mission);
    this.history.push(`Mission &quot;${mission}&quot; announced`);
    if (this.nextMission &gt;= this.missions.length) { this.nextMission = 0; }
  }
}</code></pre>
<p>MissionControlComponent는 생성자를 통해 MissionService의 인스턴스를 주입받으며,<img src="https://images.velog.io/images/sang-gyeong/post/6cb56a46-9fd0-4898-8846-607978d8c275/bidirectional-service%20(1).gif" alt="">
providers 메타데이터를 사용해서 서비스 인스턴스를 자식 컴포넌트에서도 사용할 수 있도록 공유합니다:</p>
<blockquote>
<p>Astronaut (Child)</p>
</blockquote>
<pre><code class="language-tsx">import { Component, Input, OnDestroy } from &#39;@angular/core&#39;;

import { MissionService } from &#39;./mission.service&#39;;
import { Subscription } from &#39;rxjs&#39;;

@Component({
  selector: &#39;app-astronaut&#39;,
  template: `
    &lt;p&gt;
      {{astronaut}}: &lt;strong&gt;{{mission}}&lt;/strong&gt;
      &lt;button
        (click)=&quot;confirm()&quot;
        [disabled]=&quot;!announced || confirmed&quot;&gt;
        Confirm
      &lt;/button&gt;
    &lt;/p&gt;
  `
})
export class AstronautComponent implements OnDestroy {
  @Input() astronaut: string;
  mission = &#39;&lt;no mission announced&gt;&#39;;
  confirmed = false;
  announced = false;
  subscription: Subscription;

  constructor(private missionService: MissionService) {
    this.subscription = missionService.missionAnnounced$.subscribe(
      mission =&gt; {
        this.mission = mission;
        this.announced = true;
        this.confirmed = false;
    });
  }

  confirm() {
    this.confirmed = true;
    this.missionService.confirmMission(this.astronaut);
  }

  ngOnDestroy() {
    // prevent memory leak when component destroyed
    this.subscription.unsubscribe();
  }
}</code></pre>
<p>그리고 자식 컴포넌트 AstronautComponent도 생성자를 통해 서비스 인스턴스를 주입 받습니다:</p>
<p><img src="https://images.velog.io/images/sang-gyeong/post/de0b45e7-423a-4ae0-bb9e-d18c5d715fc5/bidirectional-service%20(1).gif" alt=""></p>
<br>

<h3 id="2-서로-다른-트리에-존재할-때">2. 서로 다른 트리에 존재할 때</h3>
<h4 id="1-observable-서비스를-사용한-컴포넌트간의-통신">1) Observable, 서비스를 사용한 컴포넌트간의 통신</h4>
<p>앵귤러에서는 Subject 객체를 생성함으로써 Observable 변수를 만들 수 있습니다.</p>
<p>Observable 변수는 해당 변수의 변화를 구독자에게 전달할 수 있는 기능을 가지고 있는 변수를 말합니다.
즉, Global하게 접근할 수 있는 Observable 변수를 만들어 놓고, 해당 변수의 변화되는 값을 사용하고자 하는 컴포넌트에서 Observable 변수를 구독하여 사용하면 됩니다.
(출처 : <a href="https://secjong.tistory.com/8">https://secjong.tistory.com/8</a>)</p>
<blockquote>
<p>매개자 역할의 서비스 생성</p>
</blockquote>
<pre><code class="language-tsx">import { Injectable } from &#39;@angular/core&#39;;
import { Subject, Observable } from &#39;rxjs&#39;;

@Injectable()
export class DataService {
  private subject = new Subject&lt;any&gt;();

  constructor() { }

  sendData(data){
    this.subject.next(data);
    console.log(&quot;sendData() data : &quot; , data);
  }

  getData(){
    return this.subject.asObservable();
  }
}</code></pre>
<p>👉
먼저, Observable 변수를 선언하기 위한 서비스를 생성합니다.
rxjs 의 Subject 객체는 Observable 인 동시에 Observer입니다.</p>
<p>한 컴포넌트에서 서비스의 sendData() 를 호출하여
데이터를 Subject 의 next 메서드를 통해서 데이터 스트림에 밀어넣습니다.
다른 getData() 메서드는 데이터를 받을 컴포넌트에서 호출하여 데이터스트림에서 Observable 객체를 받은 후, 데이터 전송이 완료되었을 때 구독(subscribe) 할 것입니다.</p>
<blockquote>
<p>데이터를 보낼 컴포넌트 (sendData(data))</p>
</blockquote>
<pre><code class="language-tsx">import { Component, OnInit, OnDestroy } from &#39;@angular/core&#39;;
import { PostService } from &#39;../../services/post/post.service&#39;;
import { DataService } from &#39;../../services/data.service&#39;;

import { Post } from &#39;../../models/post&#39;;
import { ActivatedRoute, Params } from &#39;@angular/router&#39;;
import { Subscription } from &#39;rxjs/Subscription&#39;;

@Component({
  selector: &#39;app-post&#39;,
  templateUrl: &#39;./post.component.html&#39;,
  styleUrls: [&#39;./post.component.css&#39;]
})
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) =&gt; {
      this.postNo = params[&#39;postNo&#39;];
      this.postService.getPost(this.postNo)
      .subscribe(
        (post) =&gt; {
          this.post = post;
          dataService.sendData(this.post);
        },
        (error) =&gt; {
          console.log(error);
        }
      );
    });
  }

  ngOnInit() {

  }

  ngOnDestroy() {
    this.subscription.unsubscribe();
  }
}</code></pre>
<p>핵심은 위에서 생성한 서비스의 sendData() 를 호출하는 것입니다.
DataService 를 주입받고, 보내고자 하는 데이터를 dataService.sendData() 로 호출하여 넘겨줍니다.
Subscription 객체는 구독했던 객체를 파괴하기 위해 사용합니다.
(ngOnDestroy() 라이프사이클에서, 저장했던 subscription 을 unsubscribe)</p>
<blockquote>
<p>데이터를 받을 컴포넌트 (getData())</p>
</blockquote>
<pre><code class="language-tsx">import { Component, OnInit, OnDestroy } from &#39;@angular/core&#39;;
import { DataService } from &#39;../../services/data.service&#39;;

import { Subscription } from &#39;rxjs/Subscription&#39;;

@Component({
  selector: &#39;app-banner&#39;,
  templateUrl: &#39;./banner.component.html&#39;,
  styleUrls: [&#39;./banner.component.css&#39;]
})
export class BannerComponent implements OnInit, OnDestroy {
  title = &#39;Blog&#39;;
  regDate = &#39;&#39;;
  categoryName = &#39;&#39;;

  subscription: Subscription;

  constructor(private dataService: DataService) {
    console.log(&quot;banner 컴포넌트 생성!&quot;);

    this.subscription = dataService.getData().subscribe(data =&gt; {
      console.log(&quot;banner subscription : &quot; , data);
      this.title = data.title;
      this.regDate = data.regTime;
      this.categoryName = data.categoryId;
    })

  }

  ngOnInit() {
  }

  ngOnDestroy(){
    this.subscription.unsubscribe();
  }
}</code></pre>
<p>데이터를 받을 컴포넌트에서는 DataService 를 주입받고,
이 DataService 의 getData() 메서드를 호출하여 Observable 객체를 받습니다. 
이를 subscribe 메서드를 사용하여 데이터가 전달되면 수행할 작업을 진행하면 됩니다.
데이터를 수신할 컴포넌트에서도 마찬가지로 Subscription 객체를 이용해 구독한 객체를 파괴시켜주어야 합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[2. Angular의 구성요소 (Summary)]]></title>
            <link>https://velog.io/@sang-gyeong/2.-Angular%EC%9D%98-%EA%B5%AC%EC%84%B1%EC%9A%94%EC%86%8C-Summary</link>
            <guid>https://velog.io/@sang-gyeong/2.-Angular%EC%9D%98-%EA%B5%AC%EC%84%B1%EC%9A%94%EC%86%8C-Summary</guid>
            <pubDate>Sat, 07 Aug 2021 09:27:19 GMT</pubDate>
            <description><![CDATA[<h3 id="컴포넌트">컴포넌트</h3>
<p>컴포넌트는 템플릿과 메타데이터, 컴포넌트 클래스로 구성되며 데이터 바인딩에 의해 연결됩니다.
컴포넌트는 화면을 구성하는 View를 생성하고 관리하는 것이 주된 역할이며 화면은 1개 이상의 컴포넌트를 조립하여 구성합니다.</p>
<h3 id="디렉티브">디렉티브</h3>
<p>애플리케이션 전역에서 사용할 수 있는 뷰에 관련한 공통 관심사를 컴포넌트에서 분리하여 구현한 것으로,
컴포넌트의 복잡도를 낮추고 가독성을 높입니다.
구조 디렉티브와 어트리뷰트 디렉티브로 구분할 수 있으며,
큰 틀에서 컴포넌트 또한 디렉티브로 구분할 수 있습니다.</p>
<h3 id="서비스">서비스</h3>
<p>다양한 목적의 애플리케이션 공통 로직을 담당합니다.
컴포넌트에서 애플리케이션 전역 관심사를 분리하기 위해 사용하며, 의존성 주입이 가능한 클래스로 작성됩니다.</p>
<h3 id="라우터">라우터</h3>
<p>컴포넌트를 교체하는 방법으로 뷰를 전환하여 화면 간 이동을 구현합니다.</p>
<h3 id="모듈">모듈</h3>
<p>기능적으로 관련된 구성요소를 하나의 단위로 묶는 매커니즘을 말합니다.
모듈은 관련이 있는 기능들이 응집된 기능 블록으로 애플리케이션을 구성하는 하나의 단위를 만듭니다.
모듈은 다른 모듈과 결합할 수 있으며 Angular는 여러 모듈을 조합하여 애플리케이션을 구성합니다.
컴포넌트, 디렉티브, 파이프, 서비스 등의 Angular의 구성요소는 모듈에 등록되어야 사용할 수 있습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[1. 앵귤러(Angular) 시작하기]]></title>
            <link>https://velog.io/@sang-gyeong/1.-Angular-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@sang-gyeong/1.-Angular-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sun, 01 Aug 2021 07:31:27 GMT</pubDate>
            <description><![CDATA[<h1 id="1-angular-소개">1. Angular 소개</h1>
<p>Angular는 SPA 개발을 위한 구글의 오픈소스 프레임워크입니다.</p>
<p>정적 타입을 제공하는 TypeScript를 주력 언어로 채택하여,
대규모 애플리케이션 개발에 보다 적합한 환경을 제공합니다.</p>
<h3 id="angular의-장점">Angular의 장점</h3>
<p><strong>1) 컴포넌트 기반 개발</strong>
컴포넌트 기반 개발(CBD)은 개발 생산성을 높이며 대규모 애플리케이션에 적합한 구조입니다.</p>
<p><strong>2) TypeScript 채택</strong>
Angular가 기본언어로 채택하고 있는 TypeScript의 명시적 타입 지정은
코드의 가독성을 높이고 예측할 수 있게 하며, 컴파일 단계에서 오류를 포착할 수 있는 장점이 있습니다.</p>
<p><strong>3) 개발도구의 통합 및 개발 환경 구축 자동화</strong>
Angular CLI를 통해 간편한 개발 환경 구축을 지원합니다.
프레임워크를 도입할 때 장벽으로 손꼽히는 것 중 하나가 개발 환경 구축임을 미루어보면 이는 큰 장점입니다.
간단한 명령어를 사용하여 프로젝트 생성에서 빌드, 테스트, 구성요소 추가 등을 간편하게 할 수 있으며,
개발용 서버를 내장하고 있어 실행 또한 가능합니다.</p>
<p><strong>4) 성능의 향상</strong></p>
<ul>
<li><p>AoT 컴파일
  AoT 컴파일(Ahead of Time compilation)은 사전 컴파일 방식을 의미합니다.
  예를 들어, ngIf, ngFor와 같은 구조 디렉티브를 브라우저가 실행 가능한 코드로 변환해야 하는데
  이러한 과정을 런타임에서 실시하지 않고, 사전에 컴파일하여 실행 속도를 향상시키는 기법입니다.</p>
</li>
<li><p>Lazy Loading
  SPA의 단점을 극복하기 위한 대안으로, 애플리케이션 실행시점에
  모든 모듈을 한꺼번에 로딩하지 않고 필요한 시점에 필요한 모듈만을 로딩하는 방식입니다.
  현재 페이지에서 불필요한 모듈까지 로딩하는 낭비를 방지함으로써 페이지 로딩 속도를 높입니다.</p>
</li>
<li><p>코드 최적화
  &#39;Mobile First&#39;를 지향하는 고성능 프레임워크를 표방하는 만큼
  Angular 코드는 지속적으로 최적화되고 있습니다.</p>
</li>
</ul>
<br>
<br>


<h1 id="2-angular-cli">2. Angular CLI</h1>
<p>Angular CLI는 Angular 프로젝트 스캐폴딩을 생성, 실행, 빌드할수 있으며,
Angular의 다양한 구성요소를 선별적으로 추가할 수 있는 강력한 커맨드-라인 인터페이스입니다.
개발용 서버를 내장하고 있어서 간단히 프로젝트를 실행시켜서 동작을 확인할 수 있습니다.</p>
<h3 id="지원기능">지원기능</h3>
<ul>
<li>Angular 프로젝트 생성</li>
<li>Angular 프로젝트에 컴포넌트, 디렉티브, 서비스, 파이프, 클래스, 인터페이스 등의 구성요소 추가</li>
<li>LiveReload를 지원하는 내장 개발 서버를 이용한 Angular 프로젝트 실행</li>
<li>Unit/E2E 테스트 환경 지원</li>
<li>배포를 위한 Angular 프로젝트 패키징<br>

</li>
</ul>
<h3 id="기본-사용-방법">기본 사용 방법</h3>
<p>npm 패키지 매니저를 사용하여 간단하게 Angular CLI를 설치할 수 있습니다.</p>
<pre><code>$ npm install -g @angular/cli</code></pre><p>@angular/cli는 퍼스트 파티 패키지로, SPA개발에 필요한 모든 도구를 포함하고 있습니다.
컴포넌트, 서비스, 모듈 등 모든 파일들을 명령어로 쉽게 추가할 수 있고 기존 코드를 읽어 import문도 작성해줍니다.
새로운 앵귤러 프로젝트를 생성하고, 빌드한 뒤에 실행하려면 다음과 같이 명령어를 실행하면 됩니다.</p>
<br>

<pre><code class="language-bash">$ ng new &lt;프로젝트 이름&gt; 
# 프로젝트 실행
$ cd &lt;프로젝트 이름&gt;
$ ng serve (--open, --port ..)</code></pre>
<p>ng serve는 로컬 개발환경에서 애플리케이션을 빌드한 후에 서버를 실행하는 명령어입니다.
소스 코드가 변경되면 자동으로 재빌드하고 환경을 갱신합니다.</p>
<p>앵귤러에서 기본 포트는 <strong>4200번</strong> 입니다.
브라우저에서 <a href="http://localhost:4200/">http://localhost:4200</a> 으로 접근하면 애플리케이션이 실행되는 것을 확인할 수 있습니다.</p>
<h3 id="프로젝트-구성요소-생성">프로젝트 구성요소 생성</h3>
<pre><code class="language-bash">$ ng generate (ng g)</code></pre>
<p>컴포넌트, 디렉티브, 서비스, 파이프, 모듈, 가드, 클래스, 인터페이스, Enum 등을 생성할 수 있습니다.</p>
<br>
<br>


<h1 id="3-angular의-파일-구조와-처리-흐름">3. Angular의 파일 구조와 처리 흐름</h1>
<h2 id="3-1-angular-애플리케이션의-파일-구조">3-1. Angular 애플리케이션의 파일 구조</h2>
<p><strong>1) src 폴더</strong></p>
<p>Angular 프로젝트는 컴포넌트, 디렉티브, 서비스, 모듈 등 Angular 구성 요소와 각종 설정 파일로 구성됩니다.
src 폴더는 이러한 Angular의 모든 구성요소, 공통 CSS, 이미지나 폰트와 같은 정적파일, 설정 파일 등
애플리케이션 필수 파일을 담고 있습니다.
src/app 폴더에는 Angular구성요소가 위치하며, 개발자가 작성하는 대부분의 파일은 이곳에 포함됩니다.</p>
<pre><code>- app/app.components.(ts, html, css, spec.ts) : 모든 컴포넌트의 부모 컴포넌트인 루트 컴포넌트를 구성하는 컴포넌트 클래스, 템플릿, CSS 유닛 테스트용 스펙 파일
- app/app.module.ts : Angular 구성요소를 등록하는 루트 모듈
- assets/ : 이미지나 폰트와 같은 정적 파일을 위한 폴더
- environments/ : 프로젝트 빌드 시에 사용될 프로덕션용/개발용 환경설정 파일이 담겨있는 폴더
- browserslist: Autoprefixer, babel과 같은 프론트엔드 도구 간에 적용 대상 브라우저를 공유하는 browserslist 라이브러리 설정 파일
- favicon.ico: 파비콘 파일
- index.html : 웹 애플리케이션 방문 시 처음으로 로딩되는 디폴트 페이지.
    루트 컴포넌트의 셀렉터인 &lt;app-root&gt;에 의해 루트 컴포넌트의 뷰가 로드되어 브라우저에 표시됩니다.
    빌드 시에는 번들링된 자바스크립트 파일이 자동 추가된 index.html이 /dist 폴더에 생성됩니다
- karma.conf.js : Karma test runner를 위한 설정 파일로. ng test 명령어 실행 시 참조됩니다
- main.ts : 프로젝트의 메인 진입점. 루트 모듈을 사용하여 애플리케이션을 부트스트랩(기동)합니다
- polyfills.ts: 크로스 브라우징을 위한 폴리필들을 임포트하는 역할을 합니다.(browser support 참고)
- styles.css: 애플리케이션 전역에 적용되는 글로벌 css 파일
- test.ts : 유닛 테스트를 위한 메인 진입점
- tsconfig.(app|spec).json: TypeScript 컴파일 옵션 설정 파일
- typings.d.ts : TypeScript를 위한 타입 선언 파일</code></pre><br>

<p><strong>2) 기타 설정 파일</strong>
src 폴더 밖의 파일들은 테스트, 빌드, 배포 등을 위한 각종 설정 파일입니다.</p>
<pre><code>- e2e/: e2e(end-to-end) 테스트 관련 파일을 위한 폴더. e2e 테스트를 위해 Protractor가 사용하는 설정 파일인 protractor.conf.js가 담겨있습니다. ng e2e 명령어 실행 시 참조됩니다.
- node_modules/ : package.json에 등록된 의존 모듈이 패키지 매니저에 의해 설치되는 의존 모듈 저장소
- .editorconfig: 코드 에디터 기본 설정 파일
- .gitignore : Git 소스 관리 제외 대상을 위한 설정 파일
- angular.json : Angular CLI를 위한 설정 파일
- package.json : 의존 모듈 관리를 위해 패키지 매니저가 사용하는 모듈 관리 파일
- README.md : 프로젝트의 개요를 기술한 README 파일. Angular CLI가 기본적인 내용을 자동 생성합니다
- tsconfig.json : TypeScript 컴파일 옵션 설정 파일
- tslint.json : TSLint가 사용하는 linting(구문 체크) 설정 파일. ng lint 명령어 실행 시 참조됩니다.</code></pre><br>

<h2 id="3-2-angular-애플리케이션의-처리-흐름">3-2. Angular 애플리케이션의 처리 흐름</h2>
<h3 id="indexhtml">index.html</h3>
<p>웹 브라우저가 가장 먼저 로딩하는 프로젝트 파일은 /my-app/dist/index.html 입니다.
이것은 ng build 명령어로 프로젝트를 빌드했을 때 /my-app/dist/index.html에 번들링된 자바스크립트 파일이 추가되어 자동으로 생성되는 파일입니다.</p>
<pre><code>🚀 index.html은 빌드의 결과물로 실제 배포시에는 서버로 이관됩니다.

ng serve 명령어에 의해 내장 개발 서버를 이용하여 로컬 환경에서 프로젝트를 실행(preview)하는 경우,
Angular CLI 내부적으로 빌드를 자동 수행하므로 빌드를 별도 실행하여 index.html를 생성할 필요는 없습니다.
자동으로 빌드되어 가상 드라이브에 저장되어있는 index.html을 내장 개발 서버가 로드한다고 이해하면 됩니다.</code></pre><p>Angular 애플리케이션을 가동하기 위해서는 수많은 의존성 모듈(@angular/*, core-js, zone.js, RxJs 등)과
TypeScript 파일의 컴파일 결과물인 자바스크립트 파일을 로드할 필요가 있습니다.
Angular는 모듈 번들러 Webpack을 사용하여 의존성 모듈과 자바스크립트 파일을 번들링 한 후,
수작업 없이 간편하게 로드할 수 있도록 자동화 기능을 제공합니다.</p>
<p>번들링의 결과물로 생성된 자바스크립트 파일들이 로드되어 실행되면서
Angular 애플리케이션은 동작하기 시작합니다. 번들링된 자바스크립트 파일은 아래와 같습니다.</p>
<ul>
<li>main.js</li>
<li>polifills.js</li>
<li>styles.js</li>
<li>vendor.js</li>
<li>runtime.js</li>
</ul>
<br>

<h3 id="maints">main.ts</h3>
<p>main.ts는 프로젝트의 메인 진입점 입니다.
루트 모듈을 사용하여 애플리케이션을 부트스트랩(기동)합니다.</p>
<pre><code class="language-tsx">// src/main.ts

...
platformBrowserDynamic().bootstrapModule(AppModule)
    .catch(err =&gt; console.log(err));</code></pre>
<p>main.ts는 angular.json의 main 프로퍼티의 설정에 의해 로드됩니다.</p>
<pre><code class="language-json">// angular.json
{
    ...
    &quot;architect&quot;: {
        &quot;build&quot; : {
            &quot;builder&quot; : &quot;@angular-devkit/build-angular:browser&quot;,
            &quot;options&quot; : {
                &quot;outputPath&quot; : &quot;dist/my-app&quot;,
                &quot;index&quot; : &quot;src/index.html&quot;,
                &quot;main&quot; : &quot;src/main.ts&quot;,
    ...</code></pre>
<br>

<h3 id="appmodulets">app.module.ts</h3>
<p>@NgModule 데코레이터의 인자로 전달되는 메타데이터에 애플리케이션 전체의 설정정보를 기술한 루트 모듈.
루트 모듈은 (/src/app/app.component.ts)를 부트스트랩 합니다.
<br></p>
<h3 id="appcomponentts">app.component.ts</h3>
<p>모든 컴포넌트의 부모 역할을 담당하는 루트 컴포넌트입니다.
my-app 프로젝트의 경우 /dist/index.html의 app-root에 의해 루트 컴포넌트의 뷰가 로드되어
app-root의 콘텐츠로 브라우저에 표시됩니다.</p>
<br>
]]></description>
        </item>
        <item>
            <title><![CDATA[MutableOn]]></title>
            <link>https://velog.io/@sang-gyeong/MutableOn</link>
            <guid>https://velog.io/@sang-gyeong/MutableOn</guid>
            <pubDate>Mon, 14 Jun 2021 04:40:49 GMT</pubDate>
            <description><![CDATA[<p>mutableOn
Without the mutableOn function our entityReducer would look something like this.</p>
<pre><code>const entityReducer = createReducer&lt;{ entities: Record&lt;number, { id: number; name: string }&gt; }&gt;(
  {
    entities: {},
  },
  on(create, (state, { type, ...entity }) =&gt; ({
    ...state,
    entities: { ...state.entities, [entity.id]: entity }
  }),
  on(update, (state, { id, newName }) =&gt; {
    const entity = state.entities[id]

    if (entity) {
      return {
        ...state,
        entities: {
          ...state.entities,
          [entity.id]: { ...entity, name: newName }
        }
      }
    }

    return state;
  },
  on(remove, (state, { id }) =&gt; {
    const { [id]: removedEntity, ...rest } = state.entities;

    return { ...state, entities: rest };
  }),
)</code></pre><p>With the mutableOn function we are able to directly perform state mutations in reducers with less noise, and more concise code.</p>
<pre><code>const entityReducer = createReducer&lt;{ entities: Record&lt;number, { id: number; name: string }&gt; }&gt;(
  {
    entities: {},
  },
  mutableOn(create, (state, { type, ...entity }) =&gt; {
    state.entities[entity.id] = entity
  }),
  mutableOn(update, (state, { id, newName }) =&gt; {
    const entity = state.entities[id]
    if (entity) {
      entity.name = newName
    }
  }),
  mutableOn(remove, (state, { id }) =&gt; {
    delete state.entities[id]
  }),
)</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[FE 개발자에게 묻고싶은 질문]]></title>
            <link>https://velog.io/@sang-gyeong/FE-%EA%B0%9C%EB%B0%9C%EC%9E%90%EC%97%90%EA%B2%8C-%EB%AC%BB%EA%B3%A0%EC%8B%B6%EC%9D%80-%EC%A7%88%EB%AC%B8</link>
            <guid>https://velog.io/@sang-gyeong/FE-%EA%B0%9C%EB%B0%9C%EC%9E%90%EC%97%90%EA%B2%8C-%EB%AC%BB%EA%B3%A0%EC%8B%B6%EC%9D%80-%EC%A7%88%EB%AC%B8</guid>
            <pubDate>Wed, 06 Jan 2021 11:38:38 GMT</pubDate>
            <description><![CDATA[<p><img src="https://images.velog.io/images/sang-gyeong/post/e84ad23f-bb3e-4b53-91a2-22739f1afbe9/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-01-06%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%208.36.31.png" alt="">
<img src="https://images.velog.io/images/sang-gyeong/post/38291bdc-16c9-47b5-9651-1e788e608d1c/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-01-06%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%208.36.39.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[프로그래머스/python] 자물쇠와 열쇠]]></title>
            <link>https://velog.io/@sang-gyeong/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4python-%EC%9E%90%EB%AC%BC%EC%87%A0%EC%99%80-%EC%97%B4%EC%87%A0</link>
            <guid>https://velog.io/@sang-gyeong/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4python-%EC%9E%90%EB%AC%BC%EC%87%A0%EC%99%80-%EC%97%B4%EC%87%A0</guid>
            <pubDate>Wed, 06 Jan 2021 10:33:56 GMT</pubDate>
            <description><![CDATA[<p>⏰ 소요 시간 : 1시간</p>
<h3 id="나의-풀이">나의 풀이</h3>
<ol start="0">
<li>n,m의 크기가 작아(20) 브루트 포스가 적합할 것이라고 판단했습니다</li>
<li>키가 자물쇠를 벗어나는 경우를 처리하기 위해 배열 크기를 확장(n+(m-1)*2)</li>
<li>모든 배열의 좌표를 순회하며 키와 자물쇠의 숫자를 합함.</li>
<li>이때 key를 90, 180, 270, 360도 회전하며 모든 경우를 대조.</li>
<li>배열 내 자물쇠 영역의 모든 숫자가 1이라면 키와 열쇠가 일치한다고 판단.</li>
</ol>
<pre><code class="language-python">import copy

def solution(key, lock):
    answer = False
    n = len(lock)
    m = len(key)
    v_lock = [[0] * (n+(m-1)*2) for _ in range(n+(m-1)*2)]
    for i in range(n):
        for j in range(n):
            v_lock[i+(m-1)][j+(m-1)] = lock[i][j]
    test_lock = copy.deepcopy(v_lock)
    for i in range(n+m-1):
        for j in range(n+m-1):
            for _ in range(4):
                key = rotate90(key)
                for k in range(m):
                    for l in range(m):
                        test_lock[i+k][j+l] += key[k][l]
                if isFit(test_lock, n, m): return True
                else: test_lock = copy.deepcopy(v_lock)
    return answer

def rotate90(key):
    m = len(key)
    ret = [[0] * m for _ in range(m)]
    for r in range(m):
        for c in range(m):
            ret[c][m-1-r] = key[r][c]
    return ret

def isFit(test_lock, n, m):
    for i in range(n):
        for j in range(n):
            if test_lock[i+(m-1)][j+(m-1)] != 1: return False
    return True
</code></pre>
]]></description>
        </item>
    </channel>
</rss>