Google is committed to advancing racial equity for Black communities. See how.

使用 Angular 构建图像滑块元素

客座作者:Aayush Arora

Angular 元素是打包为自定义元素的 Angular 组件。其目前受 Chrome、Opera 和 Safari 支持,而在其他浏览器中则可通过 polyfill 使用。这些元素借助通用 Angular 接口和变更检测策略利用整个 Angular 基础架构。注册后,这些元素可在浏览器内使用。

此 Codelab 将引导您创建自己的 Angular 图像滑块组件,然后帮助您将其转换为 Angular 元素,使其在 Angular 框架之外也可正常运行。

构建内容

在此 Codelab 中,您将使用 Angular 构建一个图像滑块元素。该元素将:

  • 像 HTML 元素一样在浏览器中运行
  • 能够插入与 DOM 通信的任何框架中

学习内容

  • 如何构建图像滑块自定义组件
  • 如何将图像滑块自定义组件转换为自定义元素
  • 如何打包组件以使其在浏览器内正常运行

所需条件

此 Codelab 重点介绍 Angular 元素。对不相关的概念和代码块仅做简略介绍,并直接提供代码块供您复制粘贴后使用。

下载代码

点击下面的链接可下载此 Codelab 的所有代码:

下载源代码

解压下载的 ZIP 文件。此操作将解压根文件夹 (angular-element-codelab-master),其中包含

两个文件夹 (image-slider)(image-slider-finished)。我们将在 image-slider 目录下执行所有编码工作。

运行项目

为了运行项目,您需要在根目录 (image-slider) 下运行命令 (ng-serve)。

应用引导完毕后,您将能看到以下屏幕:

19ffd082e2f024a5.png

如何创建图像滑块?

就此图像滑块而言,我们将使用 Angular 点击绑定来绑定按钮。我们将创建一个对象数组,其中包含图像、alt 标记、链接等。然后,我们要将这些图像叠放在一个容器中,并在点击事件发生时转换该容器。

我们将创建一个图像滑块组件,然后将其转换为 Angular 元素。

  • 用于存储图像和标题的容器
  • 一个包含数据的数组
  • 用于绑定数据的模板

启动任何项目都可以采用多种方式,在本例中,为了使项目尽可能简单而让您专注于 Angular 元素,我们为您提供了基本代码以及 css。

创建数组和数据服务

请注意,sliderArray 将包含:

  • 一个 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/http 中的 httpClient 模块编写 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 获取对象

为此,我们需要执行以下三个步骤:

  • 初始化组件数组
  • 订阅由 getData() 函数返回的可观察对象
  • 创建接口 Result,以在订阅可观察对象后对数据进行类型检查。
  • 将数据分配到组件数组。

初始化组件数组

我们将在对象数组 slider.component.ts 内声明并初始化组件数组:

为了进行声明,请编写以下代码:

sliderArray: object[];

为了进行初始化,请编写以下代码:

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

接下来,我们需要在构造函数内导入并初始化服务

constructor(private data: DataService) {}

现在,我们就可以使用服务并调用服务方法了。

从数据服务中获取数据

为了从服务中获取数据,我们将调用 getData() 方法并订阅其返回的可观察对象。此外,我们还将创建一个接口 Result,,以便进行类型检查来确保获取的数据正确。

我们将在 ngOnInit 方法内执行此操作:

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

将数据分配到组件数组

最后,我们要将数据分配到组件数组:

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

我们获取组件数组内的数据后,就可以将模板与此数据绑定了。

slider.component.html, 中已有一个 HTML 模板。下一步是将此模板与 sliderArray 绑定。

我们要使用 *ngFor 指令将数据与模板绑定,最后在模板中添加转换以使滑块正常运行。

此过程包含三个步骤:

  • sliderArray 绑定到模板
  • 为滑块按钮添加事件绑定
  • 使用 ngStylengClass 添加 css 转换

将 slideArray 绑定到组件

我们已经有一个包含 img-containertext-containerslider. 的容器。

我们将使用 *ngFor 指令绑定全部三个容器中的数据

<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%,从而造成四种不同状态。

使用 ngStyle 和 ngClass 添加 CSS 转换

最初,我们将所有图像的不透明度设置为“零”,当所选索引等于图像索引时,我们使用 ngClass directive 添加一个 selected 类。此 selected 类会为图像添加不透明度“一”,使图像对用户可见。

<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>

此过程包括五个步骤:

  • 对 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 模块。您需要将 SliderComponent, 用作 createCustomElement 函数的参数。然后,我们需要在 DOM 中注册 slider

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

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

为了将 slider 注册为 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
 ],
 entryComponents: [SliderComponent],
})
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 以提供使用 build 命令生成的 dist/ 文件夹。此外,我们还可以使用通过 ng package 命令获取的 gzip,将其解压缩,并可将其发布为 npm module