Angular 101 - workshop 1st

2woongjae@gmail.com

Mark Lee (이웅재)

  • Studio XID inc. ProtoPie Engineer
  • Seoul.JS 오거나이저
  • 타입스크립트 한국 유저 그룹 오거나이저
  • 일렉트론 한국 유저 그룹 운영진
  • Seoul.js 오거나이저
  • Microsoft MVP - Visual Studio and Development Technologies
  • Code Busking with Jimmy
    • https://www.youtube.com/channel/UCrKE8ihOKHxYHBzI0Ys-Oow

Quick Start

npm install @angular/cli -g

ng new angular-quick-start

ng serve

Edit HTML

Edit Component

개발 환경 설정

Angular Component

Component 는 무엇인가

  • 독립적으로 동작 가능한 UI 요소
  • HTML 템플릿, 스타일과 로직을 결합 (형태 표현과 기능 제어) 
  • DOM 과 같은 트리 구조로 UI 요소들을 구성
  • W3C 표준 웹컴포넌트 기술을 기반으로 함

Component Tree & Data Flow

Component 를 만드는 방법

  • 데코레이터가 달린 클래스를 정의하는 것
  • 데코레이터에 컴포넌트의 메타데이터를 작성
    • selector
    • template, templateUrl
    • styles, styleUrls

template, styles

// sample.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-sample',
  template: `
<p>sample component</p>
`,
  styles: [`
p {
  color: red;
}
`]
})
export class SampleComponent {

}

templateUrl, styleUrls

// sample.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-sample',
  templateUrl: './sample.component.html',
  styleUrls: ['./sample.component.css']
})
export class SampleComponent {

}

/*
sample.component.html
<p>sample component</p>
*/

/*
sample.component.css
p {
    color: red;
}
*/
  • ng generate component <컴포넌트 이름> --inline-template --inline-style
  • ng g c <컴퍼넌트 이름> -it -is

ng g c sample2 -it -is

src/sample2/sample2.component.ts

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

@Component({
  selector: 'app-sample2',
  template: `
    <p>
      sample2 works!
    </p>
  `,
  styles: []
})
export class Sample2Component implements OnInit {

  constructor() { }

  ngOnInit() {
  }

}
  • ng generate component <컴포넌트 이름>
  • ng g c <컴퍼넌트 이름>

ng g c sample3

src/sample3/sample3.component.ts

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

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

  constructor() { }

  ngOnInit() {
  }

}

Component 를 사용하는 방법

  • 컴포넌트를 모듈에 등록
    • angular-cli 를 사용하면 자동 업데이트
  • 컴포넌트의 셀렉터를 다른 템플릿에서 사용

src/app.module.ts

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


import { AppComponent } from './app.component';
import { SampleComponent } from './sample.component';
import { Sample2Component } from './sample2/sample2.component';
import { Sample3Component } from './sample3/sample3.component';


@NgModule({
  declarations: [
    AppComponent,
    SampleComponent,
    Sample2Component,
    Sample3Component
  ],
  imports: [
    BrowserModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Component 내 로직 처리 방법

  • 이것은 데이터(클래스)와 뷰(템플릿)의 이야기
  • 데이터가 바뀌면 뷰는 자동으로 바뀐다.
  • 데이터를 바꾸려면 사용자의 입력이 필요하다.
    • 항상 그런것은 아니다. (Electron)
  • 사용자가 입력함과 동시에 뷰를 바꾸려면 양방향 데이터 바인딩을 사용한다.
    • [()]
    • [(ngModel)]

데이터가 바뀌면 뷰는 자동으로 바뀐다.

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

@Component({
  selector: 'app-sample2',
  template: `
    <p>
      {{ after5Seconds }}
    </p>
  `,
  styles: []
})
export class Sample2Component implements OnInit {
  public after5Seconds = 'hi';

  constructor() { }

  ngOnInit() {
    setTimeout(() => {
      this.after5Seconds = 'hello';
    }, 5000);
  }

}

데이터를 바꾸려면 사용자의 입력이 필요하다.

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

@Component({
  selector: 'app-sample2',
  template: `
    <p>
      {{ after5Seconds }}
      <button (click)="click()">클릭</button>
    </p>
  `,
  styles: []
})
export class Sample2Component implements OnInit {
  public after5Seconds = 'hi';

  constructor() { }

  ngOnInit() {
    setTimeout(() => {
      this.after5Seconds = 'hello';
    }, 5000);
  }

  public click() {
    this.after5Seconds = 'clicked';
  }
}

양방향 데이터 바인딩의 예

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

@Component({
  selector: 'app-sample2',
  template: `
    <p>
      {{ after5Seconds }}
      <button (click)="click()">클릭</button>
      <input type="text" [(ngModel)]="after5Seconds">
    </p>
  `,
  styles: []
})
export class Sample2Component implements OnInit {
  public after5Seconds = 'hi';

  constructor() { }

  ngOnInit() {
    setTimeout(() => {
      this.after5Seconds = 'hello';
    }, 5000);
  }

  public click() {
    this.after5Seconds = 'clicked';
  }
}

ngModel

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';

import { AppComponent } from './app.component';
import { SampleComponent } from './sample.component';
import { Sample2Component } from './sample2/sample2.component';
import { Sample3Component } from './sample3/sample3.component';


@NgModule({
  declarations: [
    AppComponent,
    SampleComponent,
    Sample2Component,
    Sample3Component
  ],
  imports: [
    BrowserModule,
    FormsModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Component 간 통신 (의존 관계)

  • 컴포넌트 트리 상에 붙어 있다면 ? => @Input, @Output
  • 붙어있지 않다면 ?
    • 같은 가지에 있다면 ?
    • 같은 가지가 아니라면 ?
      • 중개자
      • 서비스

중재자

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

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

  @Input() input;
  @Output() output = new EventEmitter();

  constructor() { }

  ngOnInit() {
    setTimeout(() => {
      this.output.emit({});
    }, 2000);
  }

}

템플릿과 데이터 바인딩

<app-sample test="문자열"></app-sample>

<app-sample [test]="'hello' + 'world'"></app-sample>

<button [disabled]="myVal">요런거</button>

<app-sample (click)="onClick()"></app-sample>

<app-sample [(test)]="here"></app-sample>

input ref

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

@Component({
  selector: 'app-sample',
  template: `<div>

<input #myInput type="text">
<button (click)="onClick(myInput.value)">Click me!</button>

</div>`,
  styles: []
})
export class sampleComponent implements OnInit {

  onClick(value){
    console.log(value);
  }

  constructor() { }

  ngOnInit() {
  }

}

$event

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

@Component({
  selector: 'app-sample',
  template: `<div>

<input #myInput type="text">
<button (mouseover)="onClick($event, myInput.value)">Click me!</button>

</div>`,
  styles: []
})
export class SampleComponent implements OnInit {

  onClick(event, value){
    console.log(event);
    console.log(value);
  }

  constructor() { }

  ngOnInit() {
  }

}

컴포넌트의 생명 주기

  • constructor
  • ngOnChanges - DoCheck
  • ngOnInit
  • ngAfterContentInit
  • ngAfterContentChecked
  • ngAfterViewInit
  • ngAfterViewChecked
  • ngOnDestroy

app.component.html

<!--The content below is only a placeholder and can be replaced.-->
<div style="text-align:center">
  <h1>
    Welcome to {{ title }}!
  </h1>
  <app-sample></app-sample>
  <app-sample2></app-sample2>
  <app-sample3 [test]="test"></app-sample3>
  <button (click)="onClick()">test</button>
  <img width="300" alt="Angular Logo" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTAgMjUwIj4KICAgIDxwYXRoIGZpbGw9IiNERDAwMzEiIGQ9Ik0xMjUgMzBMMzEuOSA2My4ybDE0LjIgMTIzLjFMMTI1IDIzMGw3OC45LTQzLjcgMTQuMi0xMjMuMXoiIC8+CiAgICA8cGF0aCBmaWxsPSIjQzMwMDJGIiBkPSJNMTI1IDMwdjIyLjItLjFWMjMwbDc4LjktNDMuNyAxNC4yLTEyMy4xTDEyNSAzMHoiIC8+CiAgICA8cGF0aCAgZmlsbD0iI0ZGRkZGRiIgZD0iTTEyNSA1Mi4xTDY2LjggMTgyLjZoMjEuN2wxMS43LTI5LjJoNDkuNGwxMS43IDI5LjJIMTgzTDEyNSA1Mi4xem0xNyA4My4zaC0zNGwxNy00MC45IDE3IDQwLjl6IiAvPgogIDwvc3ZnPg==">
</div>

app.component.ts

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

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'angular-quick-start';
  test = 'angular';
  onClick() {
    this.test = this.test + ' angular';
  }
}

sample3.component.html

<p>hi {{ test }}</p>

sample3.component.ts

import { Component, OnInit, Input } from '@angular/core';
import { AfterContentInit, OnChanges, AfterViewInit, AfterContentChecked, AfterViewChecked } from '@angular/core/src/metadata/lifecycle_hooks';

@Component({
  selector: 'app-sample3',
  templateUrl: './sample3.component.html',
  styleUrls: ['./sample3.component.css']
})
export class Sample3Component implements OnInit, AfterContentInit, OnChanges, AfterViewInit, AfterContentChecked, AfterViewChecked {
  @Input() test;

  constructor() {
    console.log('constructor');
  }

  ngOnChanges() {
    console.log('ngOnChanges');
  }

  ngOnInit() {
    console.log('ngOnInit');
  }

  ngAfterContentInit() {
    console.log('ngAfterContentInit');
  }

  ngAfterViewInit() {
    console.log('ngAfterViewInit');
  }

  ngAfterContentChecked() {
    console.log('ngAfterContentChecked');
  }

  ngAfterViewChecked() {
    console.log('ngAfterViewChecked');
  }

}

Result

정리

  • constructor - 누가 뭐래도 1빠
  • ngOnChanges - 최초 초기화때 / Input 프로퍼티가 변경될때
    • Input 이 없으면 실행되지 않는다.
  • ngOnInit - 프로퍼티 초기화된 직후
  • ngAfterContentInit - ngContent 사용 시 자식이 초기화 된 직후
  • ngAfterContentChecked - ngContent 를 통해 HTML 을 받을때
    • 최초 / 변경시
  • ngAfterViewInit - 템플릿이 모두 초기화되었을때
  • ngAfterViewChecked - 템플릿에 바인딩된 값이 변경되었을때

angular-cli

DI

[코드버스킹] Angular 101 - 1

By Woongjae Lee

[코드버스킹] Angular 101 - 1

코드버스킹 Angular 101 - 첫번째

  • 1,732