首页 > 其他分享 >Skeleton Design 理念在 Angular 应用开发中的具体应用一例

Skeleton Design 理念在 Angular 应用开发中的具体应用一例

时间:2023-12-12 12:32:36浏览次数:34  
标签:Observable pagination startWith 用户 Design 加载 Angular Skeleton

Skeleton Design 这个概念,想必前端开发人员比较熟悉,而普通的 App 用户,可能甚至都没有听说过,但实际上我们每个人,每天几乎都会受益于这个设计理念。

Skeleton Design 在一些国内技术社区的技术博客里,通常被称为骨架屏设计,是一种用户界面设计策略。骨架屏设计不是一种具体的基于某种编程语言的技术,而是一种设计理念,旨在改善用户体验。通用的骨架屏设计,在页面内容完全加载前,会显示一个包含页面主要布局和元素位置的空白版本。这种方式能够让用户在等待页面加载的过程中,有一个直观的感知,知道接下来会出现什么内容,减少用户的等待焦虑感。

我目前工作在一个使用 Angular 开发的电商 Storefront 开源项目上,项目的 Github 地址如下,项目代号为 Spartacus.

Spartacus B2B 功能模块里,正常的 Cost Centers 列表显示如下:

Skeleton Design 理念在 Angular 应用开发中的具体应用一例_数据

在这些 Cost Center 的数据从后台取回来之前,页面显示是这样的:

Skeleton Design 理念在 Angular 应用开发中的具体应用一例_Angular_02

大家注意到上图一行行灰色的横条吗?这就是一种典型的 Skeleton Design 理念在 Angular 应用里的实现。这种灰色的横条显示,主要目标就是提供一个视觉提示,让用户知道他们可以期待什么类型的信息。

如上图所示,骨架屏通常使用灰色调的占位符来表示正在加载的元素,这种设计方式提供了一种平滑的用户体验,使用户觉得网页的加载更快,更流畅。

试想一下,在用户网络接入速度比较慢,或者后端 API 响应速度比较慢的情况下,如果没有 Skeleton Design 这种设计,那么用户需要在页面加载完成后才能看到完整的内容。特别是在笔者负责开发的 Spartacus 这种电商应用领域内,超过3秒的页面等待就很可能会导致用户的不耐烦,甚至导致他们关闭网站。通过使用骨架屏设计,开发者可以立即显示一个页面的概要,把用户在等待时的注意力分散开,从而提高用户的参与度和用户体验。

本文余下部分,介绍 Spartacus 骨架屏设计的详细技术实现。

从 Spartacus 源代码的 list.service.ts 的实现源代码能看出,幽灵数据就是一个 length 属性值为10的空数组。

/**
   * The ghost data contains an empty list of objects that is used in the UI
   * to render the HTML elements.
   *
   * This list contains 10 items, so that the ghost will show 10 rows by default.
   */
  protected ghostData = { values: new Array(10) } as EntitiesModel<T>;

Skeleton Design 理念在 Angular 应用开发中的具体应用一例_加载_03

在 Chrome 开发者工具里,能观察到这些幽灵数据具有对应的 CSS class,这使得它们具有灰色矩形的视觉外观:

Skeleton Design 理念在 Angular 应用开发中的具体应用一例_数据_04

Cost Center 表格显示的数据最终通过 list.service.ts 从 SAP Commerce Cloud 后台取出,取数逻辑通过 Angular 响应式编程库 RxJS的 pipe 方法驱动:第 101 行 switchMap 操作符里的箭头函数,输入参数 pagination 包含了去 Commerce Cloud 取数据使用的分页设置,函数体 this.load 发送 HTTP 请求,消费 Commerce Cloud 的 OCC API.

而第 102 行的 startWith 操作符,语义上相当于给 pipe 驱动的 Observable 流赋上一个初始值,该初始值即为 length 属性为10的空数组。

Skeleton Design 理念在 Angular 应用开发中的具体应用一例_Angular_05

/**
   * Loads the data by delegating to the `load` method, which must be implemented
   * in specific implementations of this abstract class.
   *
   * The load method is streamed from the `pagination$` stream, which is initialized
   * with default pagination and structure drive properties.
   */
  getData(...args: any): Observable<EntitiesModel<T> | undefined> {
    return this.pagination$.pipe(
      // we merge any configured pagination from the table structure
      switchMap((pagination) =>
        this.getStructure().pipe(
          map((config) => ({ ...pagination, ...config.options?.pagination }))
        )
      ),
      switchMap((pagination) => this.load(pagination, ...args)),
      startWith(this.ghostData)
    );
  }

startWith 是一个非常有用的 RxJS 操作符,它的主要作用是在 Observable 序列开始之前插入一个指定的元素。这意味着,当你订阅一个 Observable 时,startWith 操作符会立即发出它的参数,然后继续发出 Observable 的值。

例如,我们有一个 Observable,它将发出一个数字序列:

const numbers$ = of(1, 2, 3, 4, 5);

我们可以使用 startWith 操作符在这个数字序列开始前插入一个数字 0:

const numbersWithZero$ = numbers$.pipe(startWith(0));

现在,当我们订阅 numbersWithZero$ 时,它会首先发出 0,然后发出 1, 2, 3, 4, 5。

这种功能在很多情况下都非常有用。例如,在 Angular 中,我们可能会用到 HttpClient 服务来从服务器获取数据。这个过程是异步的,所以我们会使用 RxJS 来处理它。但在数据到达之前,我们可能想要显示一些默认的数据或者加载指示器。这时,startWith 就能派上用场。

// 从服务器获取用户数据,但在数据到达之前,显示一个空的用户对象
const user$ = this.http.get<User>('api/users/1').pipe(
  startWith({id: null, name: 'Loading...'})
);

在这个例子中,user$ 是一个 Observable,它将发出从服务器获取的用户数据。但在数据到达之前,它会先发出一个包含 id: nullname: 'Loading...' 的对象。这样,我们就可以立即更新界面,显示一个加载指示器,然后当真实的数据到达时,再更新为真实的数据。

回到本文的例子,从运行时序来说,任何消费 getData 函数返回的 Observable 对象的 Angular UI 组件,都会先显示 startWith 设置的初始值,即幽灵数据。待从 Commerce Cloud 后台加载的真实数据返回给浏览器之后,组件自动刷新并显示这些真实的业务数据。

总结

本文首先介绍了 Skeleton Design 设计理念的引入初衷和旨在解决的用户体验,接着以 Spartacus Storefront B2B 页面为例,介绍了 Angular 应用里采用 Skeleton Design 改善页面加载用户体验的例子,希望对 Angular 同行有参考作用。



标签:Observable,pagination,startWith,用户,Design,加载,Angular,Skeleton
From: https://blog.51cto.com/jerrywangsap/8785392

相关文章

  • Angular Component 内 set 关键字的使用
    "set"关键字在Angular组件的TypeScript代码中通常用于创建和定义类的属性的setter方法。它是一种特殊的方法,负责设置类的私有成员变量的值。通过使用"set"关键字,我们可以在设置属性值时执行一些额外的逻辑,如输入验证、触发事件等。让我们通过一个简单的例子来说明"set"关键字的用......
  • Angular Renderer2 的作用和使用场景介绍
    下图将cssclasscx-icon添加到hostdom上。最后效果如下:使用的renderer来自:import{Component,ElementRef,HostBinding,Input,Renderer2,}from'@angular/core';Angular的Renderer2是Angular框架中用于操作DOM元素的重要工具之一。Renderer2的主要作用是提......
  • 谈谈企业级 Angular 应用的二次开发 - 基于 Angular Component 替换的 Extensibility
    我们知道面向个人用户(toCustomer,简称2C)软件和面向企业级用户(toBusiness,简称2B)的软件,在设计和实现上都存在一些区别,比如个人软件通常注重直观的用户界面和简单易用的设计,其中用户体验是关键,因为个人软件的目标是满足个人用户的需求和偏好。想想我们每天都在刷的抖音和头......
  • Angular 应用里异步打开对话框的技术实现
    constdialog=this.launchDialogService.openDialog();dialog.pipe(take(1)).subscribe();这段代码涉及到Angular中处理对话框(Dialog)的逻辑,其中openDialog方法返回一个Observable对象。我们将分两部分来详细解释这段代码。1.openDialog方法首先,我们来讨论openDial......
  • 如何在 Angular 应用中发起 HTTP 302 redirect
    代码如下:import{RESPONSE}from'@nguniversal/express-engine/tokens'import{Response}from'express'...constructor(protected@Optional()@Inject(RESPONSE)serverResponse:Response){}...//forexample:this.serverResponse?.status......
  • 用AntDesignBlazor快速开发一个权限系统
    写在前面:如果您是一个C#的后台开发人员,又或是C#的WPF开发人,如果想快速开发自己的网站系统,那么选择Blazor技术是太适合你不过了。(在没有Blazor之前,我会推荐Vue),尤其当我看到AntDesginBlazor(https://antblazor.com/zh-CN/)全家桶的时候,毫不犹豫就开始了我的愉快之旅。一、登录界面......
  • 无涯教程-Angular7 - 测试和构建
    本章将讨论以下内容-ToTestAngular7ProjectToBuildAngular7Project测试Angular7项目在项目设置过程中,已经安装了所需的测试软件包。为每个新组件,服务,指令等创建一个.spec.ts文件。要运行测试用例,使用的命令如下-ngtest以下是app.component.ts的app.componen......
  • 无涯教程-Angular7 - Materials模块
    Materials为您的项目提供了许多内置模块。autocomplete,datepicker,slider,menus,grids和toolbar等函数。要使用Materials,我们需要导入包装。Angular2也具有上述所有函数,但可以作为@angular/core模块的一部分使用。从Angular4开始,Materials模块提供了一个单独的模块@angular/mat......
  • 无涯教程-Angular7 - 动画效果
    Animations在html元素之间增加了很多交互,Angular2可以使用动画,从Angular4开始,动画不再是@angular/core库的一部分,而是一个单独的程序包,需要将其导入app.module.ts中。首先,我们需要使用下面的代码行导入库-import{BrowserAnimationsModule}from'@angular/platform-browse......
  • 无涯教程-Angular7 - Http Client
    HttpClient将帮助我们提供POST,GET相关方法,使用时需要导入http模块。我们需要将模块导入app.module.ts中,如下所示-import{BrowserModule}from'@angular/platform-browser';import{NgModule}from'@angular/core';import{AppRoutingModule,RoutingComponent}from......