티스토리 뷰

728x90

1. 이 포스트는 structural directive를 생성하는 방법에 대한 내용이다.

 

2. 설명은 ngFor 처럼 반복하는 구문이지만 특정한 횟수를 단순히 반복하는 기능의 directive를 구현한다.

  2-0 개발 하다 보면 이 기능이 필요한 경우가 상당히 많다. 

  2-1 하지만, 단순히 n회 반복하는 구문을 작성하려면 컴포넌트 클래스에 배열까지 생성해야 해서 귀찮다.

  2-2 바로 이전에 구현한 pagenation 역시 총 자료의 갯수만큼 반복하여 숫자를 나열하는 로직을 가지고 있다.

 

3. 예제는 아래 pagenation 포스트를 가지고 설명한다.

 

Angular : Pagenation 구현하기

1. 이 포스트는 bootstrap을 이용하여 수동으로 pagenation을 구현하는 내용이다. 1-1 사실 ng-bootstrap에서 pagenation 기능을 제공하므로 굳이 이렇게 구현할 필요는 없다. 2. component template 2-1 bootstra..

kogle.tistory.com

 

4. 순서는 Directive이기 때문에 직전 포스트와 동일하다.

 

5. Directive 생성

  5-1 아래 처럼 실행하면 빈 TimesDirective클래스가 생성되고 app.module.ts에도 자동으로 등록된다.

 

$ ng generate directive times

 

6. times.directive.ts 파일

  6-1 2개의 Ref타입의 컴포넌트를 주입받는다. ViewContainerRef, TemplateRef 이다.

    6-1-1 ViewContainerRef는 해당 directive가 지정된 element 자체를 참조한다. 

      6-1-1-1 ElementRef와 유사한 것 같지만,

      6-1-1-2 ViewContainerRef는 컨테이너 기능이 특화되어 내부에 자식 element를 추가,삭제 할 수있다.

      6-1-1-3 아래 로직을 보면 알겠지만, 이 컨테이너에 자식 element를 받은 인자값 만큼 추가하는 것이 전부이다.

    6-1-2 TemplateRef는 directive가 지정된 element의 자식 element를 참조한다.

 

  6-2 *appTimes로 지정이 되면 여기에 대입되는 값은 @Input('appTimes')로 접근할 수 있고 인자로 값을 받아온다.

    6-2-1 appTimes 아닌 별도의 속성값을 사용하여 값을 받아올 수도 있지만 클래스 이름과 동일하게 하는 경우가 많다.

    6-2-2 인자로 받은 숫자만큼 반복하는 로직을 보면

      6-2-2-1 viewContainerRef내에 자식을 추가하는 createEmbeddedView를 사용하고 있다.

      6-2-2-2 이 두번째 인자는 ngFor의 exported value와 동일한 쓰임새로 사용되고 아래의 경우는 index가 지정된다.

      6-2-2-3 exported value에 대한 내용은 아래의 링크를 참조한다.

 

Angular : Angular 기본 지식들

0. 용어 정리 0-1 Component Template - 사용자에게 User Interface를 보여주는 HTML 형식의 코드 0-2 Component Class - 이벤트 발생시 처리를 위한 코드를 가지고 있다. 페이지에 대한 정보와 상태를 저장한다...

kogle.tistory.com

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

@Directive({
  selector: '[appTimes]'
})
export class TimesDirective {

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

  @Input('appTimes') set render(times: number) {
    this.viewContainer.clear()
    
    for (let i = 0; i < times; i++) {
      this.viewContainer.createEmbeddedView(this.templateRef, {
        index: i
      })
    }
  }

}

 

7. 사용하기

  7-1 app.component.html

    7-1-1 ngFor 구문이 사라지고 *appTimes로 대체되어 있다.

 

<div class="container">
  <nav>
    <ul class="pagination">
      <li class="page-item" [appClass]="{ disabled: currentPage === 0 }">
        <a (click)="currentPage = currentPage - 1" class="page-link">Prev</a>
      </li>
      <ng-container *appTimes="images.length; let i=index;">
        <li [appClass]="getClass(i)" class="page-item" (click)="currentPage = i" 
          *ngIf="checkWindowIndex(i)">
          <a class="page-link">{{ i + 1 }}</a>
        </li>
      </ng-container>
      <li class="page-item" [appClass]="{ disabled: currentPage === images.length-1 }">
        <a class="page-link" (click)="currentPage = currentPage + 1">Next</a>
      </li>
    </ul>
  </nav>
  <div>
    <h4>{{ images[currentPage].title }}</h4>
    <img [src]="images[currentPage].url" alt="">
  </div>
</div>

 

  7-2 설명할 것은 없지만 완결성을 위해 app.component.ts를 추가한다.

 

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

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

  currentPage = 0

  images = [
    {
      title: 'At the Beach',
      url: 'https://images.unsplash.com/photo-1552379080-7bf7d131b129?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1050&q=80'
    },
    {
      title: 'At the Beach',
      url: 'https://images.unsplash.com/photo-1475503572774-15a45e5d60b9?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1050&q=80'
    },
    {
      title: 'At the Beach',
      url: 'https://images.unsplash.com/photo-1496046744122-2328018d60b6?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1064&q=80'
    },
    {
      title: 'At the Beach',
      url: 'https://images.unsplash.com/photo-1502860372601-2a663136d5a2?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1132&q=80'
    },
    {
      title: 'At the Beach',
      url: 'https://images.unsplash.com/photo-1552379080-7bf7d131b129?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1050&q=80'
    },
    {
      title: 'At the Beach',
      url: 'https://images.unsplash.com/photo-1475503572774-15a45e5d60b9?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1050&q=80'
    },
    {
      title: 'At the Beach',
      url: 'https://images.unsplash.com/photo-1496046744122-2328018d60b6?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1064&q=80'
    },
    {
      title: 'At the Beach',
      url: 'https://images.unsplash.com/photo-1502860372601-2a663136d5a2?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1132&q=80'
    },
  ]

  getClass(value: number): any {
    const classes: any = {}

    if (value === this.currentPage) {
      classes.active = true
    } else {
      classes.active = false
    }
    return classes
  }

  checkWindowIndex(value: number): boolean {
    return Math.floor(this.currentPage/10) === Math.floor(value/10)
  }
}
728x90
댓글