先看看我这个支持无限滚动的Angular应用的运行时效果:https://jerry-infinite-scroll...鼠标中键滚动,向下滚动,可以触发列表不断向后台发起加载新数据的请求:下面是具体的开发步骤。(1)app.component.html源码:
这里我们对列表元素ul应用了自定义命令appInfiniteScroller,从而赋予其支持无限滚动的功能。[scrollCallback]="scrollCallback"这一行语句,前者是自定义执行的input属性,后者是appComponent定义的函数,用于指定当scroll事件发生时应该执行什么样的业务逻辑列表发生。app组件中设置了一个news类型的属性,通过结构指令ngFor展开,显示为列表行项。(2)appComponent的实现:import{Component}from'@angular/core';import{HackerNewsService}from'./hacker-news.service';import{tap}from'rxjs/operators';@Component({selector:'app-root',templateUrl:'./app.component.html',styleUrls:['./app.component.scss'],})exportclassAppComponent{currentPage:number=1;标题='';消息:Array
=[];滚动回调;构造函数(私有hackerNewsSerivce:HackerNewsService){this.scrollCallback=this.getStories.bind(this);}getStories(){返回this.hackerNewsSerivce.getLatestStories(this.currentPage).pipe(tap(this.processData));//.do(this.processData);}privateprocessData=(news)=>{this.currentPage++;this.news=this.news.concat(新闻);};}将函数getStories绑定到属性scrollCallback,这样当列表滚动事件发生时,调用getStories函数,读取新页面的故事数据,并将结果合并到数组属性this.news中。阅读Stories的逻辑是在hackerNewsService中完成的。(3)hackerNewsService通过依赖注入被appComponent消费。import{Injectable}from'@angular/core';import{HttpClient}from'@angular/common/http';constBASE_URL='https://node-hnapi.herokuapp.com';@Injectable()exportclassHackerNewsService{constructor(privatehttp:HttpClient){}getLatestStories(page:number=1){returnthis.http.get(`${BASE_URL}/news?page=${page}`);}}(4)核心部分是自定义指令。import{Directive,AfterViewInit,ElementRef,Input}from'@angular/core';import{fromEvent}from'rxjs';import{pairwise,map,exhaustMap,filter,startWith}from'rxjs/operators';interfaceScrollPosition{sH:数字;sT:数量;cH:number;}constDEFAULT_SCROLL_POSITION:ScrollPosition={sH:0,sT:0,cH:0,};@Directive({selector:'[appInfiniteScroller]',})exportclassInfiniteScrollerDirective实现AfterViewInit{privatescrollEvent$;私人用户向下滚动$;//私有请求流$;私人请求滚动$;@Input()滚动回调;@Input()立即回调;@Input()滚动百分比=70;构造函数(私有榆树:ElementRef){}ngAfterViewInit(){this.registerScrollEvent();this.streamScrollEvents();this.requestCallbackOnScroll();}privateregisterScrollEvent(){this.scrollEvent$=fromEvent(this.elm.nativeElement,'scroll');}privatestreamScrollEvents(){this.userScrolledDown$=this.scrollEvent$.pipe(map((e:any):ScrollPosition=>({sH:e.target.scrollHeight,sT:e.target.scrollTop,cH:e.target.clientHeight,})),成对(),filter((positions)=>this.isUserScrollingDown(positions)&&this.isScrollExpectedPercent(positions[1])));}privaterequestCallbackOnScroll(){this.requestOnScroll$=this.userScrolledDown$;如果(this.immediateCallback){this.requestOnScroll$=this.requestOnScroll$.pipe(startWith([DEFAULT_SCROLL_POSITION,DEFAULT_SCROLL_POSITION]));}this.requestOnScroll$.pipe(exhaustMap(()=>{returnthis.scrollCallback();})).subscribe(()=>{});}privateisUserScrollingDown=(positions)=>{returnpositions[0].sT{return(position.sT+position.cH)/position.sH>this.scrollPercent/100;};}首先定义一个ScrollPosition接口,该接口包含三个字段sH、sT和cH,分别维护滚动事件对象的三个字段:scrollHeight、scrollTop和clientHeight。我们从应用自定义指令的dom元素的滚动事件构造一个scrollEvent$Observable对象。这样当滚动事件发生时,scrollEvent$就会自动发出事件对象。因为我们对这个事件对象的大部分属性信息不感兴趣,所以我们使用map将scroll事件对象映射到我们只感兴趣的三个字段:scrollHeight、scrollTop和clientHeight:但是只有这三个点的数据,我们还是不能确定当前列表的滚动方向。于是使用rxjs提供的pairwise算子将每两次点击产生的坐标放入一个数组中,然后使用函数this.isUserScrollingDown判断当前用户滚动的方向。如果后一个元素的scrollTop大于前一个元素的scrollTop,则表示它正在向下滚动:privateisUserScrollingDown=(positions)=>{returnpositions[0].sT{console.log('Jerryposition:',position);constreachThreshold=(position.sT+position.cH)/position.sH>this.scrollPercent/100;常量百分比=((position.sT+position.cH)*100)/position.sH;console.log('达到阈值:',reachThreshold,'百分比:',百分比);返回到达阈值;};如下如图:当阈值达到70时,返回true:更多Jerry原创文章在:《王子熙》: