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

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

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

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

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

ng g service hacker-news
ng g directive infinite-scroller

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

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { HttpModule } from '@angular/http'; import { AppComponent } from './app.component';
import { InfiniteScrollerDirective } from './infinite-scroller.directive';
import { HackerNewsService } from './hacker-news.service';
@NgModule({
declarations: [
AppComponent,
InfiniteScrollerDirective
],
imports: [
BrowserModule,
HttpModule
],
providers: [HackerNewsService],
bootstrap: [AppComponent]
})
export class AppModule { }

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

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

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

import { Directive, AfterViewInit, ElementRef, Input } from '@angular/core';

import { Observable, Subscription } from 'rxjs/Rx';
import 'rxjs/add/observable/fromEvent';
import 'rxjs/add/operator/pairwise';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/exhaustMap';
import 'rxjs/add/operator/filter';
import 'rxjs/add/operator/startWith'; interface ScrollPosition {
sH: number;
sT: number;
cH: number;
}; const DEFAULT_SCROLL_POSITION: ScrollPosition = {
sH: 0,
sT: 0,
cH: 0
}; @Directive({
selector: '[appInfiniteScroller]'
})
export class InfiniteScrollerDirective implements AfterViewInit { private scrollEvent$; private userScrolledDown$; private requestStream$; private requestOnScroll$; @Input()
scrollCallback; @Input()
immediateCallback; @Input()
scrollPercent = 70; constructor(private elm: ElementRef) { } ngAfterViewInit() { this.registerScrollEvent(); this.streamScrollEvents(); this.requestCallbackOnScroll(); } private registerScrollEvent() { this.scrollEvent$ = Observable.fromEvent(this.elm.nativeElement, 'scroll'); } private streamScrollEvents() {
this.userScrolledDown$ = this.scrollEvent$
.map((e: any): ScrollPosition => ({
sH: e.target.scrollHeight,
sT: e.target.scrollTop,
cH: e.target.clientHeight
}))
.pairwise()
.filter(positions => this.isUserScrollingDown(positions) && this.isScrollExpectedPercent(positions[1]))
} private requestCallbackOnScroll() { this.requestOnScroll$ = this.userScrolledDown$; if (this.immediateCallback) {
this.requestOnScroll$ = this.requestOnScroll$
.startWith([DEFAULT_SCROLL_POSITION, DEFAULT_SCROLL_POSITION]);
} this.requestOnScroll$
.exhaustMap(() => { return this.scrollCallback(); })
.subscribe(() => { }); } private isUserScrollingDown = (positions) => {
return positions[0].sT < positions[1].sT;
} private isScrollExpectedPercent = (position) => {
return ((position.sT + position.cH) / position.sH) > (this.scrollPercent / 100);
} }

指令接收3个输入值:

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

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

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

constructor(private elm: ElementRef) { }

ngAfterViewInit() {

    this.registerScrollEvent();  

    this.streamScrollEvents();

    this.requestCallbackOnScroll();

}

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 的完整代码。

import { Component } from '@angular/core';
import { HackerNewsService } from './hacker-news.service'; @Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent { currentPage: number = 1; news: Array<any> = []; scrollCallback; constructor(private hackerNewsSerivce: HackerNewsService) { this.scrollCallback = this.getStories.bind(this); } getStories() {
return this.hackerNewsSerivce.getLatestStories(this.currentPage).do(this.processData);
} private processData = (news) => {
this.currentPage++;
this.news = this.news.concat(news.json());
} }

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

注意 constructor 中的 this.scrollCallback 写法

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

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

<ul id="infinite-scroller"
appInfiniteScroller
scrollPerecnt="70"
immediateCallback="true"
[scrollCallback]="scrollCallback"
>
<li *ngFor="let item of news">{{item.title}}</li>
</ul>

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

为容器设置基础样式。

#infinite-scroller {
height: 500px;
width: 700px;
border: 1px solid #f5ad7c;
overflow: scroll;
padding: 0;
list-style: none; li {
padding : 10px 5px;
line-height: 1.5;
&:nth-child(odd) {
background : #ffe8d8;
}
&:nth-child(even) {
background : #f5ad7c;
}
}
}

下面的示例是使用了 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. VS2015 IIS Express 无法启动 解决办法(转)

    因为安装各种乱七八糟的软件,然后不小心把IIS Express卸载掉了,网上下载了一个IIS Express 7,安装之后本地使用VS 2015无法启动调试,F5 无法启动IIS, 再次F5调试,没有 ...

  2. 关于onetoone 的2张表关联中间表的策略

    ProductCategoryVO.java 中间关联表 package com.syscxp.header.billing; import com.syscxp.header.search.SqlT ...

  3. MongoDB账号管理及实践

    此文已由作者温正湖授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 目前蜂巢(云计算基础服务)MongoDB上已经有数十个实例,其中不少是企业用户或公司内部产品用户的.用户多了 ...

  4. django系列6--Ajax05 请求头ContentType, 使用Ajax上传文件

    一.请求头ContentType ContentType指的是请求体的编码类型,常见的类型共有三种: 1.application/x-www-form-urlencoded 这应该是最常见的 POST ...

  5. ADV三星

    #include <iostream> using namespace std; #define SIZE 12 int data[SIZE]; int data1[SIZE]; int ...

  6. Zipper(动态规划)

    点击打开链接 描述 Given three strings, you are to determine whether the third string can be formed by combin ...

  7. sublime text 显示 typescript高亮

    用ionic angular2写东西,还是用我的sublime 发现ts文件不识别,没有高亮.搜呗. 搜索出来的博客地址:http://www.cnblogs.com/happen-/p/638553 ...

  8. IE 8-不支持 placeholder 解决方法

    ;!function fixPlaceholder() { var hasPlaceholder = 'placeholder' in document.createElement('input'); ...

  9. 哈工大ComingX-创新工场俱乐部正式成立

    当我把这两个Logo放在一起的时候,我有一种感觉,这种感觉同样存在于ComingX队员的心中.大学我们走到了一起,非你我所预料,却又如此自然.在感恩节的零点,我迫不及待地告诉各位ComingX队员和关 ...

  10. python --爬虫--爬取百度翻译

    import requestsimport json class baidufanyi: def __init__(self, trans_str): self.lang_detect_url = ' ...