Skip to content

4. 파이프


1. 파이프로 데이터 표시형식 변환하기

파이프를 사용하면 문자열, 통화, 일자와 같은 데이터를 원하는 형태로 화면에 표시할 수 있다. 파이프는 템플릿 표현식과 마찬가지로 어떤 값을 입력받아서 변환된 데이터를 반환하는 함수이다. 특히 파이프는 한 번만 선언해두면 애플리케이션 어느 곳이든 자유롭게 사용할 수 있기 때문에 유용하다. 문자열을 toString()으로 변환해서 화면에 표시하는 것보다는 April 15, 1988이라고 표시하는 것이 사용자가 알아보기 편하기 때문에 파이프는 Angular 앱 곳곳에 자주 사용한다.


Note


Angular는 일반적인 데이터 변환용으로 사용할 수 있는 기본 파이프를 몇 가지 지원하며, 이 때 지역이나 국가에서 자주 사용하는 형식에 맞는 국제화(internationalization, i18n)도 지원한다. 자주 사용하는 기본 파이프는 이런 것들이 있다:


  • DatePipe: 날짜 데이터를 원하는 형식으로 변환한다.
  • UpperCasePipe: 문자열을 모두 대문자로 변환한다.
  • LowerCasePipe: 문자열을 모두 소문자로 변환한다.
  • CurrencyPipe: 숫자를 통화 단위로 변환한다. 지역에 맞는 표시 형식도 적용할 수 있다.
  • DecimalPipe: 숫자가 표시되는 자릿수를 지정한다. 지역에 맞는 표시 형식도 적용할 수 있다.
  • PercentPipe: 숫자를 백분율로 변환한다. 지역에 맞는 표시 형식도 적용할 수 있다.


Note


기본 파이프가 제공하는 기능 외에 데이터를 변환하는 로직이 별도로 필요하면 커스텀 파이프를 만들어서 활용할 수 있다.


2. 사전지식

파이프에 대해 이해하려면 다음 내용들을 먼저 이해하고 있는 것이 좋습니다:



3. 템플릿에 파이프 사용하기

파이프를 사용하려면 템플릿 표현식에 파이프 연산자(|)를 사용하고 파이프 이름을 지정하면 된다. date 프로퍼티에 DatePipe를 적용해 보자:


  • app.component.html에 있는 birthday 프로퍼티는 템플릿에 생일을 표시할 때 사용하는 프로퍼티이다.
  • hero-birthday1.component.ts 파일에서도 인라인 템플릿에 날짜 파이프를 사용해서 생일이 표시되는 형식을 변환한다.


<p>The hero's birthday is {{ birthday | date }}</p>
import { Component } from "@angular/core";

@Component({
  selector: "app-hero-birthday",
  template: `<p>The hero's birthday is {{ birthday | date }}</p>`,
})
export class HeroBirthdayComponent {
  birthday = new Date(1988, 3, 15); // April 15, 1988 -- 월은 0부터 시작합니다.
}


컴포넌트 birthday 값은 파이프 연산자 |를 거쳐 DatePipe로 전달된다.


4. 추가 형식 지정하기, 체이닝하기

파이프에는 추가 형식을 인자로 전달할 수 있다. 그래서 CurrencyPipe를 사용할 때 EUR 같은 통화 단위를 직접 지정할 수 있다. 템플릿 표현식에 {{ amount | currency:'EUR' }} 라고 사용하면 amount에 있는 숫자가 유로 단위로 표시된다. 파이프 이름(current) 뒤에 붙은 콜론(:) 다음에 전달하는 것이 추가 형식 인자('EUR')이다.


파이프에 인자를 여러 개 전달할 수 있다면 이 인자들은 콜론으로 구분한다. 그래서 {{ amount | currency:'EUR':'Euros '}} 와 같이 사용하면 두 번째 인자로 전달한 'Euros '가 문자열 뒤에 붙는다. 템플릿 표현식 문법에 맞기만 하면 템플릿에 선언한 문자열 리터럴이나 컴포넌트 프로퍼티에 모두 파이프를 적용할 수 있다.


파이프 중에는 인자 하나는 반드시 지정해야 하는 경우가 있다. SlicePipe가 그런데, {{ slice:1:5 }}라고 사용하면 배열이나 문자열에서 1번째 엘리먼트부터 5번째 엘리먼트까지를 반환한다.


1) 예제: 날짜 형식 지정하기

아래 예제에서 탭을 변경해보면 두 종류 형식('shortDate''fullDate')으로 날짜 형식을 지정한 것을 확인할 수 있다.


  • app.component.html 템플릿에는 04/15/88이라는 형식으로 표시하기 위해 DatePipe를 사용했다.
  • hero-birthday2.componen.ts에는 컴포넌트에 있는 format 프로퍼티를 사용해서 파이프의 날짜 형식을 지정한다. 그리고 화면에서 버튼을 클릭하면 toggleFormat() 메서드가 실행되면서 날짜 형식을 변경한다.
  • hero-birthday2.component.ts에 정의된 toggleFormat()는 컴포넌트 format 프로퍼티를 두 종류 날짜 형식('shortDate''fullDate')으로 변경한다.


<p>The hero's birthday is {{ birthday | date:"MM/dd/yy" }}</p>
template: `
  <p>The hero's birthday is {{ birthday | date:format }}</p>
  <button (click)="toggleFormat()">Toggle Format</button>
`;
export class HeroBirthday2Component {
  birthday = new Date(1988, 3, 15); // April 15, 1988 -- 월은 0부터 시작합니다.
  toggle = true; // 기본 형식을 shortDate로 지정하기 위해 true 값을 할당합니다.

  get format() {
    return this.toggle ? "shortDate" : "fullDate";
  }
  toggleFormat() {
    this.toggle = !this.toggle;
  }
}


이제 화면에서 Toggle Format 버튼을 클릭하면 날짜 형식이 04/15/1988이라고 표시되거나 Friday, April 15, 1988라고 표시되는 것을 확인할 수 있다.


2) 예제: 파이프 체이닝

파이프는 하나를 통과한 결과를 다른 파이프로 전달하는 방식으로 체이닝할 수 있다.


아래 예제는 첫 번째 파이프로 날짜 데이터의 형식을 지정한 후에, 이렇게 변환된 문자열을 다른 파이프로 전달해서 대문자로 변환하는 예제이다. 첫 번째 탭은 src/app/app.component.html 템플릿 파일에서 DatePipe와 UpperCasePipe를 사용해서 APR 15, 1988이라고 표시하는 코드이다. 그리고 두 번째 탭은 src/app/app.component.html 템플릿 파일에서 date 프로퍼티를 fullDate 형식으로 변환한 후에 uppercase 파이프를 한 번 더 사용해서 FRIDAY, APRIL 15, 1988이라고 표시하는 코드이다.


The chained hero's birthday is {{ birthday | date | uppercase}}
The chained hero's birthday is {{ birthday | date:'fullDate' | uppercase}}


5. 커스텀 파이프 만들기

Angular 기본 파이프로 원하는 변환작업을 수행할 수 없다면 커스텀 파이프를 만들어서 활용하면 된다. 커스텀 파이프는 기본 파이프와 같은 방식으로 템플릿 표현식에 사용할 수 있으며, 데이터를 화면에 원하는 형태로 표시하기 위해 어떤 값을 받아서 가공하고 반환한다는 점도 같다.


1) 클래스를 파이프로 지정하기

클래스에 @Pipe 데코레이터를 지정하고 필요한 내용을 메타데이터에 작성하면 클래스를 파이프로 만들 수 있다. 이 때 파이프 클래스 이름은 일반적인 스타일로 대문자 캐멀 케이스(UpperCamelCase)로 작성하며 메타데이터 name에 해당하는 문자열은 캐멀 케이스(camelCase)로 지정한다. name에 하이픈(-)을 사용하지 않는다.


파이프의 name으로 지정한 문자열은 Angular 기본 파이프와 같은 방식으로 템플릿 표현식에 사용할 수 있다.


Note

  • 클래스에 파이프 데코레이터를 붙여서 파이프로 선언한 후에는 NgModule 메타데이터의 declarations 필드에 이 파이프를 등록해야 템플릿에 사용할 수 있다.
  • 예제 앱(라이브 예제 링크 / 다운로드 링크)의 app.module.ts 파일에서 이 내용을 확인할 수 있으며 자세한 내용은 NgModules 문서를 참고한다.
  • Angular CLI로 ng generate pipe 명령을 실행하면 파이프가 자동으로 등록된다.


2) PipeTransform 인터페이스 적용하기

커스텀 파이프 클래스가 제대로 동작하려면 PipeTransform 인터페이스를 받아서(implements) 구현해야 한다.


템플릿 표현식에 커스텀 파이프가 사용되면 Angular가 transform() 메소드를 실행하며 이 때 인자가 함께 전달되면 transform() 메소드가 실행될 때 인자로 전달된다.


3) 예제: 지수 변환하기

게임을 만드는데 어떤 히어로의 파워를 제곱해서 표현하는 기능을 구현한다고 하자. 히어로 점수가 2라면 파워는 2의 10제곱인 1024라고 표현하는 식이다. 이런 데이터 변환은 커스텀 파이프를 활용하면 구현할 수 있다.


아래 두 파일의 내용을 확인해 보자:


  • exponential-strength.pipe.ts에는 exponentialStrength라는 이름으로 커스텀 파이프가 정의되어 있으며, 변환 로직은 이 파이프 클래스의 transform() 메서드에 구현되어 있다.
  • 이 커스텀 파이프는 power-booster.component.ts 파일 템플릿에서 사용한다. 이 때 2 값을 10 제곱하도록 파이프 인자를 지정했다. 이렇게 지정한 인자는 커스텀 파이프의 transform() 메서드로 전달된다.


어떻게 처리되는지 확인해 보자.


import { Pipe, PipeTransform } from "@angular/core";
/*
 * 히어로의 힘을 증폭합니다.
 * 증폭값은 파이프 인자로 전달하며, 기본값은 1입니다.
 * 사용방법:
 *   값 | exponentialStrength:증폭값
 * 사용예:
 *   {{ 2 | exponentialStrength:10 }}
 *   변환 결과: 1024
 */
@Pipe({ name: "exponentialStrength" })
export class ExponentialStrengthPipe implements PipeTransform {
  transform(value: number, exponent = 1): number {
    return Math.pow(value, exponent);
  }
}
import { Component } from "@angular/core";

@Component({
  selector: "app-power-booster",
  template: `
    <h2>Power Booster</h2>
    <p>Super power boost: {{ 2 | exponentialStrength: 10 }}</p>
  `,
})
export class PowerBoosterComponent {}
Power Booster

Superpower boost: 1024


Note

  • exponentialStrength 파이프가 직접 동작하는 것을 확인하려면 라이브 예제 링크  / 다운로드 링크를 참고한다.
  • 템플릿에 지정한 파이프 인자 값을 바꾸면 결과값도 변경된다.


6. 파이프에 바인딩된 데이터 변화감지

파이프에 데이터를 바인딩하면 사용자의 동작에 반응하는 방식으로 파이프를 실행할 수 있다. 이 때 파이프에 전달되는 인자가 StringNumber와 같은 기본 자료형이라면 값이 변경될 때, DateArray와 같은 객체 참조 형태라면 참조하는 객체가 변경될 때마다 파이프의 변환로직이 실행된다.


그래서 이전 섹션에서 구현했던 커스텀 파이프에 ngModel로 양방향 바인딩을 연결하면 다음과 같이 구현할 수 있다.


src/app/power-boost-calculator.component.ts
import { Component } from "@angular/core";

@Component({
  selector: "app-power-boost-calculator",
  template: `
    <h2>Power Boost Calculator</h2>
    <label for="power-input">Normal power: </label>
    <input id="power-input" type="text" [(ngModel)]="power" />
    <label for="boost-input">Boost factor: </label>
    <input id="boost-input" type="text" [(ngModel)]="factor" />
    <p>Super Hero Power: {{ power | exponentialStrength: factor }}</p>
  `,
  styles: ["input {margin: .5rem 0;}"],
})
export class PowerBoostCalculatorComponent {
  power = 5;
  factor = 1;
}


이제 exponentialStrength 파이프는 사용자가 "normal power" 값이나 "boost factor" 값을 변경할 때마다 실행되는 것을 확인할 수 있다.


Angular는 인자값이 변경되거나 참조 객체가 변경되는 것을 감지할 때마다 파이프 함수를 다시 실행한다. 인자가 기본 자료형인 경우에는 크게 문제되지 않는다. 하지만 Date 객체의 월이 바뀌거나 배열 안에 있는 엘리먼트가 변경되는 경우, 오브젝트 프로퍼티가 변경되는 경우와 같이 객체 안에 있는 데이터가 변경되는 경우에는 파이프가 변화를 어떻게 감지하고 동작하는지 제대로 이해해야 한다. 그런 다음에는 순수하지 않은(impure) 파이프를 어떻게 사용하는지 알아보자.


1) 변화를 감지하는 방식

Angular는 키입력, 마우스 이동, 타이머 만료, 서버 응답과 같은 DOM 이벤트가 발생할 때마다 변화 감지 동작을 실행하고 바인딩된 데이터가 변경되었는지 검사한다. 아래 예제는 파이프를 사용하지 않았지만 Angular의 기본 변화 감지 로직이 어떻게 동작하는지 확인할 수 있는 예제 코드이다. 이런 내용을 확인해 보자:


  • flying-heroes.component.html (v1) 템플릿에는 heroes 배열을 순회하기 위해 ngFor를 사용했다.
  • 컴포넌트 클래스 파일 flying-heroes.component.ts (v1)에는 템플릿에 사용할 heroes 배열이 선언되어 있으며, 이 배열에 히어로를 추가하거나 배열을 초기화하는 메서드도 정의되어 있다.


<label for="hero-name">New hero name: </label>
<input
  type="text"
  #box
  id="hero-name"
  (keyup.enter)="addHero(box.value); box.value=''"
  placeholder="hero name"
/>
<button (click)="reset()">Reset list of heroes</button>
<div *ngFor="let hero of heroes">{{hero.name}}</div>
export class FlyingHeroesComponent {
  heroes: any[] = [];
  canFly = true;
  constructor() {
    this.reset();
  }

  addHero(name: string) {
    name = name.trim();
    if (!name) {
      return;
    }
    const hero = { name, canFly: this.canFly };
    this.heroes.push(hero);
  }

  reset() {
    this.heroes = HEROES.slice();
  }
}


이제 사용자가 히어로를 추가하면 그 때마다 Angular가 화면을 갱신한다. 사용자가 Reset 버튼을 클릭하면 Angular가 heroes 배열을 새 배열로 교체하면서 이 때 변경된 내용도 화면에 표시된다. 히어로 한 명을 제거하거나 추가하는 경우에도 변경된 내용이 화면에 표시된다.


그런데 화면이 갱신될 때마다 파이프가 계속 실행되면 앱 성능을 저하할 수 있다. 그래서 Angular는 파이프를 실행할 때 조금 다른 변화 감지 알고리즘을 사용한다. 다음 섹션에서 이 내용에 대해 알아보자.


2) 기본 자료형의 값이 변경되었는지, 객체 참조가 변경되었는지 감지하기

기본적으로 파이프는 순수한(pure) 상태로 정의되며 Angular는 이 파이프로 입력받는 값이 정말 변경된 것(pure change)을 감지할 때만 파이프 로직을 실행한다. 이 때 값이 정말 변경되었다는 것은 StringNumberBooleanSymbol과 같은 기본 자료형의 값이 변경되었거나, DateArrayFunctionObject와 같은 객체 참조가 변경된 것을 의미한다.


순수한 파이프(pure pipe)는 순수 함수(pure function)로 구현해야 하며, 이 말은 입력값을 받고 값을 반환할 때까지 외부의 영향을 받지 않아야 한다는 것을 의미한다. 이를 다르게 표현하면, 어떤 값이 입력된다면 언제나 값을 반환해야 한다는 것을 의미한다.


순수한 파이프를 사용하면 Angular는 객체 안에서 발생한 변화는 무시한다. 그래서 배열 참조가 그대로인 상태로 항목이 추가되는 것을 감지하지 않는데, 이는 객체를 깊이 검사(deep check)하는 것보다 객체 참조가 변경되는 것만 감지하는 것이 훨씬 빠르기 때문이다. 이 과정에서 변화가 감지되지 않으면 파이프 실행을 생략하고 넘어간다.


하지만 이 때문에 순수한 파이프에 배열을 인자로 전달하면 예상한 대로 파이프가 실행되지 않는다. 이 현상을 확인하기 위해 이전에 만들었던 예제 코드를 살짝 바꿔서, 히어로 중에 날 수 있는 히어로만 필터링하는 파이프를 만들어 보자. *ngFor로 heroes 배열을 순회할 때 FlyingHeroesPipe를 사용해서 다음과 같이 구현한다:


  • 새로 만드는 파이프는 템플릿 파일 flying-heroes.component.html에 사용한다.
  • 커스텀 파이프 FlyingHeroesPipe는 flying-heroes.pipe.ts 파일에 구현한다.


<div *ngFor="let hero of (heroes | flyingHeroes)">{{hero.name}}</div>
import { Pipe, PipeTransform } from "@angular/core";

import { Hero } from "./heroes";

@Pipe({ name: "flyingHeroes" })
export class FlyingHeroesPipe implements PipeTransform {
  transform(allHeroes: Hero[]) {
    return allHeroes.filter((hero) => hero.canFly);
  }
}


이렇게 구현하면 앱이 이상하게 동작한다. 사용자가 비행할 수 있는 히어로를 추가해도 이 히어로는 "Heroes who fly." 아래에 표시되지 않는다:


src/app/flying-heroes.component.ts
this.heroes.push(hero);


Angular 변화 감지기(change detector)는 배열의 항목이 변경된 것을 감지하지 않기 때문에 파이프도 실행되지 않는다.


원인은 이전에 설명했듯이 heroes 배열을 참조하는 것은 그대로인 채로 배열 안에 항목을 추가했기 때문이다. 그래서 파이프 인자가 변경되지 않은 것으로 간주하고 화면을 갱신하지 않는다.


이 예제에서 참조하는 객체를 바꾸면 처음 의도한 대로 동작한다. 원래 배열에 있던 항목과 새로 추가된 항목을 더해서 새로운 배열을 생성하고 이 배열을 heroes에 할당한 후에 파이프의 인자로 전달하면 된다. 그러면 파이프가 참조하는 배열이 변경되었기 때문에 Angular도 파이프 로직을 실행한다.


정리하자면, 순수한 파이프에 입력 배열을 인자로 전달하면 이 배열 참조가 변경되지 않는 이상 파이프가 동작하지 않는다. 이 경우에는 인자로 전달되는 배열 자체를 교체해야 한다.


이 예제를 통해 파이프와 컴포넌트가 어떻게 연동되는지 알아봤다.


하지만 컴포넌트를 간단하게 유지하면서 HTML 템플릿과 분리된 상태를 유지하려는 용도로 파이프를 사용한다면, 순수하지 않은(impure) 파이프를 사용해서 변화를 감지하는 방식이 더 나을 수 있다. 이 내용은 다음 섹션에서 살펴보자.


3) 객체 안에서 변경된 내용 감지하기

배열 안에 있는 항목이 변경되는 것과 같이 객체 안에서 변경된 것을 감지해서 커스텀 파이프가 실행되려면 파이프를 순수하지 않게(impure) 선언해야 한다. 그러면 Angular 변화 감지가 동작하는 시점마다 순수하지 않은 파이프도 항상 실행된다.


Note

  • 순수하지 않은 파이프가 유용하긴 하지만 사용에 주의해야 한다.
  • 이런 파이프 안에서 무거운 로직을 실행하면 앱 성능이 급격하게 저하될 수 있다.


순수하지 않은 파이프를 정의하려면 pure 플래그에 false 값을 지정하면 된다:


src/app/flying-heroes.pipe.ts
@Pipe({
  name: 'flyingHeroesImpure',
  pure: false
})


아래 코드에서 FlyingHeroesImpurePipe는 FlyingHeroesPipe를 상속받도록 구현했다. 따라서 파이프가 동작하는 로직은 모두 똑같고 파이프 메타데이터의 pure 플래그에 false 값을 지정한 것만 다르다.


@Pipe({
  name: "flyingHeroesImpure",
  pure: false,
})
export class FlyingHeroesImpurePipe extends FlyingHeroesPipe {}
import { Pipe, PipeTransform } from "@angular/core";

import { Hero } from "./heroes";

@Pipe({ name: "flyingHeroes" })
export class FlyingHeroesPipe implements PipeTransform {
  transform(allHeroes: Hero[]) {
    return allHeroes.filter((hero) => hero.canFly);
  }
}


이 정도라면 FlyingHeroesImpurePipe에 있는 transform() 함수가 간단하고 실행도 빨리 끝나기 때문에 사용하기에 문제가 없다:


src/app/flying-heroes.pipe.ts (필터)
return allHeroes.filter((hero) => hero.canFly);


FlyingHeroesImpureComponent는 FlyingHeroesComponent에 사용할 수 있다. 아래 코드처럼 템플릿에서 사용하는 파이프를 변경하기만 하면 된다.


src/app/flying-heroes-impure.component.html (일부)
<div *ngFor="let hero of (heroes | flyingHeroesImpure)">{{hero.name}}</div>


7. 옵저버블에서 데이터 추출하기

옵저버블은 애플리케이션 안에서 메시지를 전달하는 용도로 활용할 수 있다. 그리고 옵저버블은 동기/비동기 방식을 선택해서 실행할 수 있으며 데이터를 여러 번 전달할 수도 있기 때문에 이벤트를 처리할 때 자주 사용된다.


Angular가 제공하는 AsyncPipe를 사용하면서 파이프 인자로 옵저버블을 지정하면, 이 파이프는 옵저버블을 자동으로 구독하고 데이터를 추출해서 파이프 로직을 실행한다. 원래는 컴포넌트 코드에서 옵저버블을 구독해야 하고, 옵저버블로 전달되는 데이터를 추출해야 하며, 이 데이터를 템플릿에 바인딩해야 하고 옵저버블이 종료되었을 때 메모리 누수를 방지하기 위해 옵저버블 구독을 해지해야 한다. AsyncPipe는 사용하면 이 과정을 자동으로 처리하며, 옵저버블 객체를 처리할 수 있도록 순수하지 않은 파이프로 구현되었다.


아래 예제 코드는 옵저버블 메시지 스트림 message$로 데이터를 전달받기 위해 async 파이프를 사용하는 예제 코드다.


src/app/hero-async-message.component.ts
import { Component } from "@angular/core";

import { Observable, interval } from "rxjs";
import { map, take } from "rxjs/operators";

@Component({
  selector: "app-hero-async-message",
  template: ` <h2>Async Hero Message and AsyncPipe</h2>
    <p>Message: {{ message$ | async }}</p>
    <button (click)="resend()">Resend</button>`,
})
export class HeroAsyncMessageComponent {
  message$: Observable<string>;

  private messages = [
    "You are my hero!",
    "You are the best hero!",
    "Will you be my hero?",
  ];

  constructor() {
    this.message$ = this.getResendObservable();
  }

  resend() {
    this.message$ = this.getResendObservable();
  }

  private getResendObservable() {
    return interval(500).pipe(
      map((i) => this.messages[i]),
      take(this.messages.length)
    );
  }
}


8. HTTP 요청 캐싱하기

백엔드 서비스와 HTTP로 통신하려면 HttpClient 서비스가 제공하는 HttpClient.get()와 같은 함수로 서버에서 데이터를 받아와야 하는데 이런 함수는 옵저버블을 반환한다.


이전 섹션에서 알아본 것처럼 AsyncPipe는 순수하지 않은 파이프이며, 파이프 인자로 받은 옵저버블을 자동으로 구독하며 옵저버블로 전달된 데이터를 추출하는 것도 자동으로 한다. 이 방식을 활용하면 HTTP 요청을 보내고 캐싱하는 파이프를 만들 수 있다.


순수하지 않은 파이프는 컴포넌트에서 변화감지 로직이 동작할 때마다 실행되기 때문에 몇 밀리초마다 실행될 수도 있다. 이 때 성능이 저하되는 것을 피하려면 서버로 요청하는 URL이 변경될 때만 파이프 로직을 실행하고 서버에서 받은 응답을 캐싱하는 방법을 사용하는 것이 좋다. 이 내용을 확인해 보자:


  • fetch-json.pipe.ts 파일에 fetch 파이프를 구현한다.
  • 파이프를 사용해서 요청을 보내는 컴포넌트는 hero-list.component.ts이다. 이 컴포넌트 템플릿은 heroes.json 파일에서 히어로 목록을 파이프로 불러오는 코드가 두 번 사용되었는데, 두 번째 사용된 코드에는 fetch 파이프와 기본 파이프 JsonPipe를 사용해서 데이터를 JSON 형식으로 화면에 표시한다.


import { HttpClient } from "@angular/common/http";
import { Pipe, PipeTransform } from "@angular/core";

@Pipe({
  name: "fetch",
  pure: false,
})
export class FetchJsonPipe implements PipeTransform {
  private cachedData: any = null;
  private cachedUrl = "";

  constructor(private http: HttpClient) {}

  transform(url: string): any {
    if (url !== this.cachedUrl) {
      this.cachedData = null;
      this.cachedUrl = url;
      this.http.get(url).subscribe((result) => (this.cachedData = result));
    }

    return this.cachedData;
  }
}
import { Component } from "@angular/core";

@Component({
  selector: "app-hero-list",
  template: ` <h2>Heroes from JSON File</h2>

    <div *ngFor="let hero of 'assets/heroes.json' | fetch">
      {{ hero.name }}
    </div>

    <p>
      Heroes as JSON:
      {{ "assets/heroes.json" | fetch | json }}
    </p>`,
})
export class HeroListComponent {}


이 예제를 실행했을 때 파이프는 다음과 같이 동작한다:


  • 파이프가 사용된 곳마다 개별 파이프 인스턴스가 생성된다.
  • 개별 파이프 인스턴스는 URL과 데이터를 캐싱하기 때문에 서버로 요청을 한 번만 보낸다.


그래서 브라우저를 보면 이렇게 동작하는 것을 확인할 수 있다.


Heroes from JSON File

Windstorm
Bombasto
Magneto
Tornado

Heroes as JSON: [ { "name": "Windstorm", "canFly": true }, { "name": "Bombasto", "canFly": false }, { "name": "Magneto", "canFly": false }, { "name": "Tornado", "canFly": true } ]


Note

  • JsonPipe는 데이터 바인딩이 왜 실패했는지 확인하거나 바인딩할 객체를 미리 확인하는 용도로도 활용할 수 있다.


9. 파이프와 우선순위

파이프 연산자는 삼항연산자(?:)보다 우선순위가 높기 때문에 a ? b : c | x 라고 작성하면 a ? b : (c | x)로 처리된다. 이 경우에 괄호를 사용하지 않고서는 삼항연산자의 첫 번째 항목이나 두 번째 항목에 파이프를 적용할 수 없다.


이런 우선순위를 고려해서 삼항연산자의 결과에 파이프를 적용하려면 괄호를 사용해서 (a ? b : c) | x라고 작성해야 한다.


src/app/precedence.component.html
<!-- use parentheses in the third operand so the pipe applies to the whole expression -->
{{ (true ? 'true' : 'false') | uppercase }}

References