Angular を使用して画像スライダー要素を作成する

ゲスト著者: Aayush Arora

Angular Elements は、カスタム要素としてパッケージ化された Angular コンポーネントです。現在、Chrome、Opera、Safari でサポートされており、それ以外のブラウザでもポリフィルを通して利用することができます。Angular Elements を登録すると、Angular の一般的なインターフェースと変更検知戦略を用いて、すべての Angular のインフラストラクチャをブラウザで使用できるようになります。

この Codelab では、独自の画像スライダー Angular コンポーネントを作成し、このコンポーネントを Angular 要素に変換して、Angular フレームワークから独立して動作させる方法を学びます。

演習内容

この Codelab では、Angular を使用して、次のような画像スライダー要素を作成します。

  • ブラウザの HTML 要素と同じように動作する
  • DOM と通信する任意のフレームワークに組み込める

学習内容

  • 画像スライダー カスタム コンポーネントを作成する方法
  • 画像スライダー カスタム コンポーネントをカスタム要素に変換する方法
  • ブラウザで動作するようにコンポーネントをパッケージ化する方法

必要なもの

この Codelab では、主に Angular 要素について説明します。関連のない概念やコードブロックについては詳しく触れず、コードはコピーして貼るだけの状態で提供されています。

コードをダウンロードする

次のリンクをクリックして、この Codelab のコードをすべてダウンロードします。

ソースコードをダウンロード

ダウンロードした zip ファイルを解凍すると、ルートフォルダ (angular-element-codelab-master) が展開されます。このフォルダには、

(image-slider)(image-slider-finished) の 2 つのフォルダがあります。すべてのコーディング作業は、「image-slider」という名前のディレクトリで行います。

プロジェクトを実行する

プロジェクトを実行するには、ルート ディレクトリ(image-slider)でコマンド(ng-serve)を実行する必要があります。

アプリがブートストラップされ、次のように表示されます。

19ffd082e2f024a5.png

画像スライダーの作成方法

ここでは、Angular クリック バインディングでボタンをバインドして画像スライダーを作成します。画像、alt タグ、リンクなどを含むオブジェクトの配列を作成し、これらの画像をコンテナ内に上から順に配置して、クリック時にコンテナを変換します。

まず最初に画像スライダー コンポーネントを作成し、それから Angular 要素に変換します。

  • 画像とタイトルのコンテナ
  • データを含む配列
  • データをバインドするテンプレート

プロジェクトの作業を開始する方法はいくつかありますが、今回はプロジェクトをできるだけシンプルにして、Angular 要素に集中できるように、基本的なコードと CSS は作成済みの状態から始めます。

配列とデータサービスを作成する

sliderArray は以下のアイテムで構成します。

  • スライダー画像の URL の img キー
  • 画像の代替テキストを指定した alt タグ
  • 画像の説明テキスト

src/assets ディレクトリには、次の内容の data.json ファイルがすでに用意されています。

sliderArray = [
 {img: 'http://bloquo.cc/img/works/1.jpg', alt: '', text: '365 Days Of weddings a year'},
 {img: 'http://bloquo.cc/img/works/2.jpg', alt: '',  text: '365 Days Of weddings a year'},
 {img: 'http://bloquo.cc/img/works/3.jpg', alt: '', text: '365 Days Of weddings a year'},
 {img: 'http://bloquo.cc/img/works/4.jpg', alt: '',  text: '365 Days Of weddings a year'},
 {img: 'http://bloquo.cc/img/works/5.jpg', alt: '', text: '365 Days Of weddings a year'}
];

このデータは、コンポーネントでサービスを使用して取得する必要があります。data.service.ts ファイルで、@angular/common/httphttpClient モジュールを使用して getData() メソッドを作成します。このメソッドにより、先ほど作成した配列からデータを取得します。


import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http'

const URL = '../assets/data.json';

@Injectable({
 providedIn: 'root'
})
export class DataService {

 constructor(private http: HttpClient) {}

 getData() {
   return this.http.get(URL)
 }
}

データサービスからデータを取得する

data.json からオブジェクトを取得するには、コンポーネント内にサービスをインポートして、Observable にサブスクライブする必要があります。

これには、次の 3 つの手順を行います。

  • コンポーネント配列を初期化する
  • getData() 関数から返された Observable にサブスクライブする
  • Observable にサブスクライブした後にデータの型をチェックするインターフェース Result を作成する
  • データをコンポーネント配列に割り当てる

コンポーネント配列を初期化する

slider.component.ts 内でオブジェクトの配列であるコンポーネント配列を宣言し、初期化します。

宣言するには:

sliderArray: object[];

初期化するには:

constructor(private data: DataService) {
 this.sliderArray = [];
}

次に、コンストラクタ内にサービスをインポートして初期化する必要があります。

constructor(private data: DataService) {}

これで、サービスを使用して、サービス メソッドを呼び出す準備が整いました。

データサービスからデータを取得する

サービスからデータを取得するには、getData() メソッドを呼び出して、返される Observable にサブスクライブし、インターフェース Result, を作成します。これにより、データの型をチェックして、正しいデータを取得しているかどうかを確認できます。

これは ngOnInit メソッド内で行います。

this.data.getData().subscribe((result: Result)=>{
})

コンポーネント配列にデータを割り当てる

最後に、コンポーネント配列にデータを割り当てます。

this.data.getData().subscribe((result: Result)=>{
  this.sliderArray = result.sliderArray;
})

コンポーネントの配列内でデータを取得したら、テンプレートにこのデータをバインドできます。

slider.component.html, には、HTML テンプレートがすでに用意されています。次の手順では、このテンプレートに sliderArray をバインドします。

*ngFor ディレクティブを使用してテンプレートにデータをバインドし、最後にテンプレートに変換を追加して、スライダーが動作するようにします。

ここでは、次の 3 つの手順を行います。

  • テンプレートに sliderArray をバインドする
  • スライダー ボタンのイベント バインディングを追加する
  • ngStylengClass を使用して CSS 変換を追加する

コンポーネントに slideArray をバインドする

img-containertext-containerslider. のコンテナがあります。

*ngFor ディレクティブを使用して、3 つすべてのコンテナでデータをバインドします。

<div class="container">
 <div class="img-container" *ngFor="let i of sliderArray; let select = index;">
   <img src="{{i.img}}" alt="{{i.alt}}" >
 </div>

 <div>
   <div class="text-container">
     <div class="page-text" *ngFor="let i of sliderArray;let select = index;">
       <h3>{{i.text}}</h3>
     </div>
   </div>
 </div>

</div>

<div class="slider">
 <div class="slide-button-parent-container" *ngFor="let i of sliderArray; let x =index">
    <div class="select-box">
     <div class="slide-button">
     </div>
    </div>
 </div>
</div>

slideArray にイベントをバインドする

データをバインドしたら、Angular click binding を使用して、すべてのスライドボタンにクリック イベントをバインドします。これには、selected(x) という関数を作成します。x は配列のインデックスです。

selected(x) {
 this.downSelected(x);
 this.selectedIndex = x;
}

downSelected(i) {
  this.transform =  100 - (i) * 50;
  this.selectedIndex = this.selectedIndex + 1;
  if(this.selectedIndex > 4) {
    this.selectedIndex = 0;
  }
}

ここでは次の点に注意してください。

  • downSelected 関数では、変換プロパティの値が、selected 関数でクリック時に渡されるインデックスに 50 を掛けた分だけ減らされています。
  • このロジックでは、テキスト コンテナが 100%、50%、-50%、-100% に変換され、4 つの異なる状態になります。

ngStyle と ngClass を使用して CSS 変換を追加する

最初にすべての画像を不透明度 0 に設定します。選択したインデックスが画像インデックスと等しい場合は、ngClass directive を使用してクラス selected を追加します。この selected クラスは、不透明度 1 を画像に追加して、ユーザーに画像が表示されるようにします。

<div class="img-container"  *ngFor="let i of sliderArray; let select = index;"
      [ngClass]="{'selected': select == selectedIndex}">
</div>

その後、select() 関数を使用して計算された transform 値に沿って、テキスト コンテナを変換します。

<div [ngStyle]="{'transform': 'translateY('+ transform + '%' +')', 'transition': '.8s'}">
</div>

これらすべてを行った最終的なコードは次のようになります。

<div class="container">
 <div class="img-container"  *ngFor="let i of sliderArray; let select = index;"
      [ngClass]="{'selected': select == selectedIndex}">
   <img src="{{i.img}}" alt="{{i.alt}}" >
 </div>

 <!--</div>-->
 <div [ngStyle]="{'transform': 'translateY('+ transform + '%' +')', 'transition': '.8s'}">
   <div class="text-container">
     <div class="page-text" *ngFor="let i of sliderArray;let select = index;" [ngClass]="{'selected': select == selectedIndex}">
       <h3>{{i.text}}</h3>
     </div>
   </div>
 </div>

</div>

<div class="slider">
 <div class="slide-button-parent-container"  *ngFor="let i of sliderArray; let x =index" (click)="selected(x)" >
    <div class="select-box">
     <div   class="slide-button" [ngClass]="{'slide-button-select': x == selectedIndex}" >
     </div>
    </div>
 </div>
</div>

ここでは、次の 5 つの手順を行います。

  • Angular 要素に Shadow DOM を使用する
  • entryComponents を使用する
  • @angular/elements から CreateCustomElement モジュールをインポートして使用する
  • custom-element を定義する
  • ngDoBootstrap メソッドを実行する

Angular 要素に Shadow DOM を使用する

画像スライダーが動作するようになったら、あとは Angular Element に変換するだけです。

ここでは、コンポーネントの DOM(Shadow DOM)の作成方法を少しだけ変更します。

具体的には、ViewEncapsulation モジュールをインポートし、その ShadowDom メソッドを使用します。

@Component({
 selector: 'app-slider',
 templateUrl: './slider.component.html',
 styleUrls: ['./slider.component.css'],
 encapsulation: ViewEncapsulation.ShadowDom
})

entryComponents を使用する

エントリー コンポーネントは、Angular で強制的に読み込まれるコンポーネントです。エントリー コンポーネントを指定するには、NgModule でブートストラップします。

ここでは、@NgModule 内の entryComponents 配列で SliderComponent を指定します。

@NgModule({
 declarations: [
   SliderComponent
 ],
 imports: [
   BrowserModule,
   HttpClientModule
 ],
 entryComponents: [SliderComponent],
})

createCustomElement モジュールをインポートして使用する

ここでは、@angular/elements.createCustomElement モジュールを使用します。createCustomElement 関数のパラメータには SliderComponent, を使用する必要があります。その後、slider を DOM に登録します。

import { createCustomElement } from '@angular/elements';

export class AppModule {
 constructor(private injector: Injector) {
   const slider = createCustomElement(SliderComponent, { injector });
    }
}

スライダーを DOM 要素として登録するには、customElements.define メソッドを使用してスライダーを定義します。

customElements.define('motley-slider', slider);

最後に、ngDoBootstrap() メソッドを使用してこのカスタム要素をブートストラップします。完成したコードは次のようになります。

import { BrowserModule } from '@angular/platform-browser';
import { NgModule, Injector } from '@angular/core';
import { createCustomElement } from '@angular/elements';
import { SliderComponent } from './slider/slider.component';
import { HttpClientModule} from "@angular/common/http";

@NgModule({
 declarations: [
   SliderComponent
 ],
 imports: [
   BrowserModule,
   HttpClientModule
 ],
})
export class AppModule {
 constructor(private injector: Injector) {
   const slider = createCustomElement(SliderComponent, { injector });
   customElements.define('motley-slider', slider);
 }

 ngDoBootstrap() {}

}

Angular 要素をパッケージ化する

新しいコマンドで package.json を変更する必要があるため、package.json ファイル内のスクリプト オブジェクトを修正します。

修正したスクリプト オブジェクトは次のようになります。

"scripts": {
 "ng": "ng",
 "start": "ng serve",
 "build": "ng build --prod --output-hashing=none",
 "package": "cat dist/my-app/{runtime,polyfills,scripts,main}.js | gzip > elements.js.gz",
 "serve": "http-server",
 "test": "ng test",
 "lint": "ng lint",
 "e2e": "ng e2e"
}

これで、コマンド ng build & ng package を実行できるようになりました。最後に、ng serve を実行して、ビルドコマンドを使用して生成した dist フォルダを配信します。ng package コマンドで取得した gzip を展開して、npm module として公開することもできます。