[ Angular 2: Directives ] 지시자의 개념과 종류

Component 는 일종의 Directive 이다. 따라서, 결국 Directive 가 각 애플리케이션의 핵심인 셈이다. Directive 는 Angular2가 각 부분에서 어떤 일을 해야하는지에 대한 Instruction이다. 예를 들어, ‘<app-root></app-root>’ 와 같은 selector 를 이용한 tag는 Angular2에게 해당 컴포넌트를 표시하도록 하는 Directive 또는 Instruction 이다. Directive는 View와 Content가 표시되도록 할 뿐만 아니라, Dom이 표현되는 방식이나 구조를 바꾸는 일도 한다.

Directives are Instructions.

다음의 *ngFor 라는 Directive는  items 라는 array 를 읽어들이면서, 각각의 값을 item이라는 변수에 할당하고 해당 값을 <li> element 로 감싸도록 하는 Instruction 이다.

<div>
  <ul>
    <li *ngFor="let item of items">{{item}}</li>
  </ul>
</div>

다음 Directive는 특정 컴포넌트를 만들어, 해당 컴포넌트의 View (template)를 지정된 위치에 표시하도록 하는 Instruction이다.

<div>
  <my-component></my-component>
</div>

 

Attribute & Structural Directives

Attribute Directive는 자신이 속한 Element 와 상호작용한다. 스타일을 바꾸는 것이 한 예이다. 마치 HTML Element 의 attribute와 같은 모습으로 사용되기 때문에 Attribute Directive라고 이름 붙여졌다. ngClass, ngStyle 과 같은 Directive 가 Attribute Directive 이다. ( <input [ngClass]>와 같은 식으로 표현되는데, databinding과 비슷한 형식을 가진다. 하지만, Directive가 꼭 Property나 Event Binding을 가져야할 필요는 없음을 명심하자.)

Structural Directive는 현재의 View Container와 상호작용하며, DOM이나 HTML Code의 구조를 바꾸는 역할을 한다. *ngIf, *ngFor 가 그 예이다. (e.g. <div *ngIf=”…”>)

Attribute Directive Examples

콘솔 창에서 다음과 같이 입력하여 새 프로젝트를 생성해보자.

$ ng new directives --directory directive --prefix dir

app.component.css

.border {
    border: 3px solid blue;
}

.background {
    background-color: green;
}

div {
    width: 100px;
    height: 100px;
}

app.component.html

<h1>
  Attribute Directives
</h1>
<h2>ngClass / ngStyle</h2>
<div [ngClass]="{ border:false, background:true }"></div>
<div [class.border]="true"></div>
<!-- Don't forget single quotation marks on attribute name when dash is used in it -->
<div [ngStyle]="{'background-color':'red'}"></div>

 Custom Attribute Directive Example

ngClass, ngStyle 와 같이 Angular2가 지정해놓은 Directive 외에 우리가 직접 만들 수도 있다. highlight 라는 Cumtom Directive 를 만들어보자.

$ ng generate directive highlight
//또는
$ ng g d highlight

다음과 같이 src/app/highlight.directive.ts 파일이 생성된다. selector 가 [dirHighlight] 로 된 것은 angular cli 프로젝트를 설정할 때 –prefix를 dir로 설정했기 때문이다.

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

@Directive({
  selector: '[dirHighlight]'
})
export class HighlightDirective {

  constructor() { }

}

selector 는 어떤 형식으로든 자신에게 맞게 수정하면된다.

//기본 directive 형식 (e.g. <div highlight>
selector: '[highlight]'
// highlight 라는 클라스에 적용하고 싶으면
selector: '.highlight'
//highlight 라는 아이디에 적용하고 싶으면
selector: '#highlight'

 적용해보자.

app.component.html

<h1>
  Attribute Directives
</h1>
<h2>ngClass / ngStyle</h2>
<div [ngClass]="{ border:false, background:true }"></div>
<div [class.border]="true"></div>
<!-- Don't forget single quotation marks on attribute name when dash is used in it -->
<div [ngStyle]="{'background-color':'red'}"></div>
<h2>Custom Attribute Directive</h2>
<div dirHighlight>Some text</div>

 highlight.directive.ts

import { Directive,ElementRef } from '@angular/core';

@Directive({
  selector: '[dirHighlight]'
})
export class HighlightDirective {
  private elementRef: ElementRef;
  constructor(elementRef:ElementRef) {
    this.elementRef = elementRef;
    this.elementRef.nativeElement.style.backgroundColor = 'green';
  }
}

  private elementRef: ElementRef;
  constructor(elementRef:ElementRef) {
    this.elementRef = elementRef;
  }

를 아래와 같이 줄여서 쓸 수 있다.

  constructor(private elementRef:ElementRef) {
  }

결과적으로 아래와 같이 된다.

import { Directive,ElementRef } from '@angular/core';

@Directive({
  selector: '[dirHighlight]'
})
export class HighlightDirective {
  constructor(private elementRef:ElementRef) {
    this.elementRef.nativeElement.style.backgroundColor = 'green';
  }
}

위와 같이 ElementRef 를 통해 DOM에 접근 할 수 있다. 하지만, 위와 같이 ElementRef를 통해 직접적으로 DOM에 접근하는 것은 권고하지 않는다. 다양한 사용자 환경에 적응하지 못하는 경우가 생기기 때문이다. 피치못할 경우에만 이 방법을 사용한다.

대신, Renderer 를 통해 간접적으로 View 에 접근해본다.

import { Directive,ElementRef,Renderer } from '@angular/core';

@Directive({
  selector: '[dirHighlight]'
})
export class HighlightDirective {
  constructor(private elementRef:ElementRef, private renderer:Renderer) {
    //this.elementRef.nativeElement.style.backgroundColor = 'green';
    this.renderer.setElementStyle(this.elementRef.nativeElement,'background-color', 'green');
  }
}

HostListener & HostBinding

마우스 포인터가 올려졌을 때 background-color 가 바뀌도록 하려면 어떻게 해야할까? HostListener 와 HostBinding을 사용한다.

highlight.directive.ts

import { Directive,HostListener,HostBinding } from '@angular/core';

@Directive({
  selector: '[dirHighlight]'
})
export class HighlightDirective {
  @HostListener('mouseenter') mouseover() {
    this.backgroundColor='green';
  };
  @HostListener('mouseleave') mouseleave() {
    this.backgroundColor='white';
  };
  @HostBinding('style.backgroundColor') get setColor(){
    return this.backgroundColor;
  };
  private backgroundColor = 'white';
  constructor() {
  }
}

다음과 같이 HostBinding을 쓰지 않고 Renderer를 사용해 같은 기능을 구현할 수도 있다.

import { Directive,ElementRef,Renderer,HostListener,HostBinding } from '@angular/core';

@Directive({
  selector: '[dirHighlight]'
})
export class HighlightDirective {
  @HostListener('mouseenter') mouseover() {
    this.backgroundColor='green';
    this.renderer.setElementStyle(this.elementRef.nativeElement,'background-color', 'green');
  };
  @HostListener('mouseleave') mouseleave() {
    this.backgroundColor='white';
    this.renderer.setElementStyle(this.elementRef.nativeElement,'background-color', 'white');
  };
  // @HostBinding('style.backgroundColor') get setColor(){
  //   return this.backgroundColor;
  // };
  private backgroundColor = 'white';
  constructor(private elementRef:ElementRef, private renderer:Renderer) {
    //this.elementRef.nativeElement.style.backgroundColor = 'green';
    this.renderer.setElementStyle(this.elementRef.nativeElement,'background-color', this.backgroundColor);
  }
}

참고로, HostListener를 통해 이벤트 값을 넘겨주려면 다음과 같이 한다.

@HostListener('click',['$event']) onClick(event){
    console.log("Event Target"+event.target);
};

HostListener 의 두번째 argment로 사용된 ‘$event’는 자동으로 생성된 이벤트 값이므로 반드시 그렇게 적어주어야한다. 하지만, onClick() 함수에 사용된 event 라는 argument의 이름은 바꾸어주어도 상관없다.

html 파일에서 highlight.directive.ts 로 highlightColor 값을 넘겨 줄 수 있도록 Input Directive 를 활용해보자.

import { Directive,HostListener,HostBinding,Input,OnInit } from '@angular/core';

@Directive({
 selector: '[dirHighlight]'
})
export class HighlightDirective implements OnInit {
 @HostListener('mouseenter') mouseover() {
 this.backgroundColor=this.highlightColor;
 };
 @HostListener('mouseleave') mouseleave() {
 this.backgroundColor=this.defaultColor;
 };
 @HostBinding('style.backgroundColor') get setColor(){
 return this.backgroundColor;
 }

 @Input() defaultColor = 'white';
 @Input() highlightColor = 'green';
 private backgroundColor:string;

 constructor() { }
 ngOnInit(){
 this.backgroundColor = this.defaultColor;
 }
}

html 에서 다음과 같이 적용 가능하다.

<div dirHighlight [highlightColor]="'blue'" [defaultColor]="'red'">Text</div>

만약 @Input() 디렉티브안에 이름을 Directive selector와 같이 지정해주면

@Input(‘dirHighlight’) highlightColor = ‘green’;

html 에서 적용 코드가 더 짧아진다.

<div [dirHighlight]="'blue'" [defaultColor]="'red'">Text</div>

 

@HostListener를 사용하지 않거나 인터액티브한 요소가 없을 경우에는, 다음처럼 @HostBinding 과 @Input을 함께 사용해도 작동한다. 하지만, 권고할 만한 방법은 아니다.

@HostBinding('style.backgroundColor')
@Input() defaultColor: string;

@HostBinding('style.color')
@Input() colorHex: string;

@HostBinding('style.font-family')
@Input() fontFamily: string;

@HostBinding('style.font-weight')
@Input() fontWeight: string;

@HostBinding('style.font-style')
@Input() fontStyle: string;

 

+1 HostBinding&HostListener Example

import { Directive,HostBinding,HostListener } from '@angular/core';

@Directive({
  selector: '[rbDropdown]'
})
export class DropdownDirective {
  @HostBinding('class.open') get opened(){
    return this.isOpen;
  }
  @HostListener('click') open(){
    this.isOpen = true;
  }
  @HostListener('mouseleave') close(){
    this.isOpen = false;
  }
  private isOpen = false;
  constructor() { }

}

rbDropdown  Directive는 click하면 open class를 더하고 마우스가 멀어지면 open class를 제거한다.

<div class="dropdown" rbDropdown>Click Me</div>
//클릭을 받으면 다음과 같이 변한다.
<div class="dropdown open" rbDropdown>Click Me</div>

 

 

Structural Directive Example

Structural Directive는 코드를 잘 살펴보면 직관적으로 쉽게 이해할 수 있다.

*ngIf

app.component.html

<h1>
  Structural Directives
</h1>
<h2>*ngIf</h2>
<div *ngIf="switch">Conditional Text</div>
<button (click)="onSwitch()">Switch</button>
<br><br>
// 위 내용은 아래와 동일한 결과를 낳는다.
// 여기서 *ngIf에 붙은 *의 의미를 알 수 있다.
<h1>
Structural Directives
</h1>
<h2>*ngIf</h2>
<template [ngIf]="switch">
  <div>Conditional Text</div>
</template>
<button (click)="onSwitch()">Switch</button>
<br><br>

 

app.component.ts

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

@Component({
  selector: 'dir-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  private switch = true;
  onSwitch(){
    this.switch =!this.switch;
  }
}

 *ngFor

app.component.html

<h1>
  Structural Directives
</h1>
<h2>*ngFor</h2>
<ul>
  <li *ngFor="let item of items">{{item}}</li>
</ul>
//index를 표시하고 싶다면...
<li *ngFor="let item of items; let i = index">{{item}} - (Index: {{i}})</li>

app.component.ts

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

@Component({
  selector: 'dir-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  items:string[]=['airplane','train','bus','taxi'];
}

 

*ngSwitch

app.component.html

<h1>
  Structural Directives
</h1>
<h2>*ngSwitch</h2>
<ul [ngSwitch]="person">
  <li *ngSwitchCase="'Mohan'">Hello Mohan</li>
  <li *ngSwitchCase="'Sohan'">Hello Sohan</li>
  <li *ngSwitchCase="'Vijay'">Hello Vijay</li>
  <li *ngSwitchDefault>Bye Bye</li>
</ul>  
<br><br>

 

// *ngIf 의 *에서와 마찬가지로 여기서도 *는 템플레이팅을 의미한다.
// 아래는 위와 같은 결과를 낳는다.
<h1>
  Structural Directives
</h1>
<h2>*ngSwitch</h2>
<ul [ngSwitch]="person">
  <template [ngSwitchCase]="'Mohan'"><li>Hello Mohan</li></template>
  <template [ngSwitchCase]="'Sohan'"><li>Hello Sohan</li></template>
  <template [ngSwitchCase]="'Vijay'"><li>Hello Vijay</li></template>
  <template ngSwitchDefault><li>Bye Bye</li></template>
</ul>  
<br><br>

app.component.ts

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

@Component({
  selector: 'dir-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  person:string = 'Mohan';
}

※ *ngSwitch 에 대한 다양한 사용법은 링크 참고.

Custom Structural Directive Example

unless.directive.ts 파일을 생성한다.

$ ng generate directive unless
//또는
$ ng g d unless

src/app/unless.directive.ts

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

@Directive({
  selector: '[dirUnless]'
})
export class UnlessDirective {

  constructor() { }

}

‘dirUnless’ Directive 를 만들어보자.

unless.directive.ts

import { Directive,TemplateRef,ViewContainerRef,Input } from '@angular/core';

@Directive({
  selector: '[dirUnless]'
})
export class UnlessDirective {
  @Input() set dirUnless(condition:boolean){
    if (!condition){
      this.vcRef.createEmbeddedView(this.templateRef);
    } else {
      this.vcRef.clear();
    }
  }

  constructor(
    private templateRef:TemplateRef<any>,
    private vcRef:ViewContainerRef
  ) { }

}

app.component.html

<h1>
  Structural Directives
</h1>
<h2>Unless Directive</h2>
<div *dirUnless="switch">Some text</div>
<button (click)="onSwitch()">Switch</button>
<br><br>

app.component.ts

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

@Component({
  selector: 'dir-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  switch:boolean = true;
  onSwitch(){
    this.switch = !this.switch;
  }
}

 

 

 

 

“[ Angular 2: Directives ] 지시자의 개념과 종류”에 대한 한개의 댓글

댓글 남기기