我使用 angular-cli 来搭建项目。

  1. ng new infinite-scroller-poc --style=scss

项目生成好后,进入 infinite-scroller-poc 目录下。

Angular CLI 提供了一堆命令用来生成组件、指令、服务和模块。

我们来生成一个服务和一个指令。

  1. ng g service hacker-news
  2. ng g directive infinite-scroller

注意: Angular CLI 会自动在 app.module.ts 里注册指令,但不会将服务添加到 providers 数组中。你需要手动添加。app.module.ts 如下所示。

  1. import { BrowserModule } from '@angular/platform-browser';
  2. import { NgModule } from '@angular/core';
  3. import { HttpModule } from '@angular/http';
  4. import { AppComponent } from './app.component';
  5. import { InfiniteScrollerDirective } from './infinite-scroller.directive';
  6. import { HackerNewsService } from './hacker-news.service';
  7. @NgModule({
  8. declarations: [
  9. AppComponent,
  10. InfiniteScrollerDirective
  11. ],
  12. imports: [
  13. BrowserModule,
  14. HttpModule
  15. ],
  16. providers: [HackerNewsService],
  17. bootstrap: [AppComponent]
  18. })
  19. export class AppModule { }

接下来,我们在服务中添加 HackerNews 的 API 调用。下面是 hacker-news.service.ts,它只有一个函数 getLatestStories

  1. import { Injectable } from '@angular/core';
  2. import { Http } from '@angular/http';
  3. const BASE_URL = 'http://node-hnapi.herokuapp.com';
  4. @Injectable()
  5. export class HackerNewsService {
  6. constructor(private http: Http) { }
  7. getLatestStories(page: number = 1) {
  8. return this.http.get(`${BASE_URL}/news?page=${page}`);
  9. }
  10. }

现在来构建我们的无限滚动加载指令。下面是指令的完整代码,别担心代码太长,我们会分解来看。

  1. import { Directive, AfterViewInit, ElementRef, Input } from '@angular/core';
  2. import { Observable, Subscription } from 'rxjs/Rx';
  3. import 'rxjs/add/observable/fromEvent';
  4. import 'rxjs/add/operator/pairwise';
  5. import 'rxjs/add/operator/map';
  6. import 'rxjs/add/operator/exhaustMap';
  7. import 'rxjs/add/operator/filter';
  8. import 'rxjs/add/operator/startWith';
  9. interface ScrollPosition {
  10. sH: number;
  11. sT: number;
  12. cH: number;
  13. };
  14. const DEFAULT_SCROLL_POSITION: ScrollPosition = {
  15. sH: 0,
  16. sT: 0,
  17. cH: 0
  18. };
  19. @Directive({
  20. selector: '[appInfiniteScroller]'
  21. })
  22. export class InfiniteScrollerDirective implements AfterViewInit {
  23. private scrollEvent$;
  24. private userScrolledDown$;
  25. private requestStream$;
  26. private requestOnScroll$;
  27. @Input()
  28. scrollCallback;
  29. @Input()
  30. immediateCallback;
  31. @Input()
  32. scrollPercent = 70;
  33. constructor(private elm: ElementRef) { }
  34. ngAfterViewInit() {
  35. this.registerScrollEvent();
  36. this.streamScrollEvents();
  37. this.requestCallbackOnScroll();
  38. }
  39. private registerScrollEvent() {
  40. this.scrollEvent$ = Observable.fromEvent(this.elm.nativeElement, 'scroll');
  41. }
  42. private streamScrollEvents() {
  43. this.userScrolledDown$ = this.scrollEvent$
  44. .map((e: any): ScrollPosition => ({
  45. sH: e.target.scrollHeight,
  46. sT: e.target.scrollTop,
  47. cH: e.target.clientHeight
  48. }))
  49. .pairwise()
  50. .filter(positions => this.isUserScrollingDown(positions) && this.isScrollExpectedPercent(positions[1]))
  51. }
  52. private requestCallbackOnScroll() {
  53. this.requestOnScroll$ = this.userScrolledDown$;
  54. if (this.immediateCallback) {
  55. this.requestOnScroll$ = this.requestOnScroll$
  56. .startWith([DEFAULT_SCROLL_POSITION, DEFAULT_SCROLL_POSITION]);
  57. }
  58. this.requestOnScroll$
  59. .exhaustMap(() => { return this.scrollCallback(); })
  60. .subscribe(() => { });
  61. }
  62. private isUserScrollingDown = (positions) => {
  63. return positions[0].sT < positions[1].sT;
  64. }
  65. private isScrollExpectedPercent = (position) => {
  66. return ((position.sT + position.cH) / position.sH) > (this.scrollPercent / 100);
  67. }
  68. }

指令接收3个输入值:

  1. scrollPercent - 用户需要滚动到容器的百分比,达到后方可调用 scrollCallback
  2. scrollCallback - 返回 observable 的回调函数。
  3. immediateCallback - 布尔值,如果为 true 则指令初始化后会立即调用 scrollCallback

Angular 为组件和指令提供了4个生命周期钩子。

对于这个指令,我们想要进入 ngAfterViewInit 生命周期钩子以注册和处理滚动事件。在 constructor 中,我们注入了 ElementRef,它可以让我们引用应用了指令的元素,即滚动容器。

  1. constructor(private elm: ElementRef) { }
  2. ngAfterViewInit() {
  3. this.registerScrollEvent();
  4. this.streamScrollEvents();
  5. this.requestCallbackOnScroll();
  6. }

ngAfterViewInit 生命周期钩子中,我们执行了3个函数:

  1. registerScrollEvent - 使用 Observable.fromEvent 来监听元素的滚动事件。
  2. streamScrollEvents - 根据我们的需求来处理传入的滚动事件流,当滚动到给定的容器高度百分比时发起 API 请求。
  3. requestCallbackOnScroll - 一旦达到我们设定的条件后,调用 scrollCallback 来发起 API 请求。

还有一个可选的输入条件 immediateCallback,如果设置为 true 的话,我们会将 DEFAULT_SCROLL_POSITION 作为流的起始数据,它会触发 scrollCallback 而无需用户滚动页面。这样的话会调用一次 API 以获取初始数据展示在页面中。上述所有函数的作用都与我的上篇文章中是一样的,上篇文章中已经详细地解释了 RxJS Observable 各个操作符的用法,这里就不赘述了。

接下来将无限滚动指令添加到 AppComponent 中。下面是 app.component.ts 的完整代码。

  1. import { Component } from '@angular/core';
  2. import { HackerNewsService } from './hacker-news.service';
  3. @Component({
  4. selector: 'app-root',
  5. templateUrl: './app.component.html',
  6. styleUrls: ['./app.component.css']
  7. })
  8. export class AppComponent {
  9. currentPage: number = 1;
  10. news: Array<any> = [];
  11. scrollCallback;
  12. constructor(private hackerNewsSerivce: HackerNewsService) {
  13. this.scrollCallback = this.getStories.bind(this);
  14. }
  15. getStories() {
  16. return this.hackerNewsSerivce.getLatestStories(this.currentPage).do(this.processData);
  17. }
  18. private processData = (news) => {
  19. this.currentPage++;
  20. this.news = this.news.concat(news.json());
  21. }
  22. }

getStories - 调用 hackerNewsService 并处理返回数据。

注意 constructor 中的 this.scrollCallback 写法

  1. this.scrollCallback = this.getStories.bind(this);

我们将 this.getStories 函数赋值给 scrollCallback 并将其上下文绑定为 this 。这样可以确保当回调函数在无限滚动指令里执行时,它的上下文是 AppComponent 而不是 InfiniteScrollerDirective 。更多关于 .bind 的用法,可以参考这里

  1. <ul id="infinite-scroller"
  2. appInfiniteScroller
  3. scrollPerecnt="70"
  4. immediateCallback="true"
  5. [scrollCallback]="scrollCallback"
  6. >
  7. <li *ngFor="let item of news">{{item.title}}</li>
  8. </ul>

html 想当简单,ul 作为 appInfiniteScroller 指令的容器,同时还传入了参数 scrollPercentimmediateCallbackscrollCallback。每个 li 表示一条新闻,并只显示新闻的标题。

为容器设置基础样式。

  1. #infinite-scroller {
  2. height: 500px;
  3. width: 700px;
  4. border: 1px solid #f5ad7c;
  5. overflow: scroll;
  6. padding: 0;
  7. list-style: none;
  8. li {
  9. padding : 10px 5px;
  10. line-height: 1.5;
  11. &:nth-child(odd) {
  12. background : #ffe8d8;
  13. }
  14. &:nth-child(even) {
  15. background : #f5ad7c;
  16. }
  17. }
  18. }

下面的示例是使用了 Angular 指令的无限滚动加载,注意观察右边的滚动条。

在线示例: ashwin-sureshkumar.github.io/angular-inf…

Angular: 使用 RxJS Observables 来实现简易版的无限滚动加载指令的更多相关文章

  1. 使用 Angular 和 RxJS 实现的无限滚动加载

    无限滚动加载应该是怎样的? 无限滚动加载列表在用户将页面滚动到指定位置后会异步加载数据.这是避免寻主动加载(每次都需要用户去点击)的好方法,而且它能真正保持应用的性能.同时它还是降低带宽和增强用户体验 ...

  2. 简易数据分析 10 | Web Scraper 翻页——抓取「滚动加载」类型网页

    这是简易数据分析系列的第 10 篇文章. 友情提示:这一篇文章的内容较多,信息量比较大,希望大家学习的时候多看几遍. 我们在刷朋友圈刷微博的时候,总会强调一个『刷』字,因为看动态的时候,当把内容拉到屏 ...

  3. Angular ui-roter 和AngularJS 通过 ocLazyLoad 实现动态(懒)加载模块和依赖

    什么是ui-router ui-router是AngularUI库最有用的组件之一(AngularUI库由AngularJS社区构建).它是一个第三方路由框架,允许通过状态机制组织接口,而不是简单的U ...

  4. win7(旗舰版)下,OleLoadPicture 加载内存中的图片(MagickGetImageBlob),返回值 < 0

    昨天去三哥家,想把拍好的照片缩小一下,我用很久前写的一个软件进行缩小,然后进行一次效果预览,这个时候弹出: Call OleLoadPicture Fail - loadPictureFromMW 奇 ...

  5. WebGL简易教程(十五):加载gltf模型

    目录 1. 概述 2. 实例 2.1. 数据 2.2. 程序 2.2.1. 文件读取 2.2.2. glTF格式解析 2.2.3. 初始化顶点缓冲区 2.2.4. 其他 3. 结果 4. 参考 5. ...

  6. angular源码分析:angular的整个加载流程

    在前面,我们讲了angular的目录结构.JQLite以及依赖注入的实现,在这一期中我们将重点分析angular的整个框架的加载流程. 一.从源代码的编译顺序开始 下面是我们在目录结构哪一期理出的an ...

  7. .NET Core的文件系统[5]:扩展文件系统构建一个简易版“云盘”

    FileProvider构建了一个抽象文件系统,作为它的两个具体实现,PhysicalFileProvider和EmbeddedFileProvider则分别为我们构建了一个物理文件系统和程序集内嵌文 ...

  8. MVC 验证码实现( 简易版)

    现在网站上越来越多的验证码,使用场景也是越来越多,登陆.注册.上传.下载...等等地方,都有可能大量使用到验证码,那么制作验证码到底有多简单呢?我们一起来看下最简易版的验证码实现过程- 验证码的基本步 ...

  9. 简易版自定义BaseServlet

    这几天在学Java Web,一直在思考Servlet重用的问题,就用java的反射机制实现自定义的简易版BaseServlet; 该方式有点像struts2 利用映射获取前端的参数.有兴趣的同学可以自 ...

随机推荐

  1. PostgreSQL创建表及约束

    创建表 语法: create table table_name ( column_name type column_constraint, table_constraint table_constra ...

  2. 自己从0开始学习Unity的笔记 III (C#随机数产生基础练习)

    自己开始尝试弄一下随机数,照着方法,自己做了个英雄打怪兽的测试 int heroAttack; ; ; Random attack = new Random(); //初始化一个随机数的类 heroA ...

  3. c# 线程的基本使用

    创建线程 线程的基本操作 线程和其它常见的类一样,有着很多属性和方法,参考下表: 创建线程的方法有很多种,这里我们先从thread开始创建线程 class Program { static void ...

  4. .net程序员书单

    C# 基础 <CLR via C#> <c# 高级编程> 框架学习 <WPF编程宝典 > (英文名:<Pro WPF 4.5 in C#. Windows P ...

  5. Webpack vs Rollup

    本文由作者余伯贤授权网易云社区发布. 2017年4月份的时候,Facebook将React的构建工具换成了Rollup.很多人就有疑问了,Webpack不也是Facebook团队开发的吗,为什么不使用 ...

  6. php扩展memcache和memcached区别?以及memcached软件的介绍

    引用“http://www.vicenteforever.com/2012/03/memcache-different-memcached/” memcached是一个软件,而PHP包括了memcac ...

  7. P5038 [SCOI2012]奇怪的游戏

    题目链接 题意分析 首先我们需要求的是统一以后的值\(x\) 并且一般的棋盘操作我们都需要黑白染色 那么对于棋盘格子是偶数的情况的话 答案是存在单调性的 因为如果统一之后 两两搭配还是可以再加一个的 ...

  8. Visual Studio性能计数器,负载测试结果分析- Part III

    对于一个多用户的应用程序,性能是非常重要的.性能不仅是执行的速度,它包括负载和并发方面.Visual Studio是可以用于性能测试的工具之一.Visual Studio Test版或Visual S ...

  9. MAVEN打zip包

    https://blog.csdn.net/yulin_hu/article/details/81835945 https://www.cnblogs.com/f-zhao/p/6929814.htm ...

  10. [转] CDH6 安装文章链接收集

    CentOS 7下Cloudera Manager及CDH 6.0.1安装过程详解 http://blog.51cto.com/wzlinux/2321433?source=dra Cloudera ...