Codeburst. IO/Angular-2-s…

This article is translated by RxJS Chinese community, if you need to reprint, please indicate the source, thank you for your cooperation!

If you would like to translate more quality RxJS articles with us, please click here.

This article is a continuation of my previous article on using reactive programming to implement a simple version of infinite scroll loading. In this article, we will create an Angular directive to implement infinite scroll loading. We will also continue to use HackerNews’s unofficial API to get data to populate the page.

I used Angular-CLI to build the project.

ng new infinite-scroller-poc --style=scss
Copy the code

After the project is generated, enter the directory of infinite- scroller-POc.

The Angular CLI provides a bunch of commands to generate components, directives, services, and modules.

Let’s generate a service and a directive.

ng g service hacker-news
ng g directive infinite-scroller
Copy the code

Note: The Angular CLI automatically displaysapp.module.tsRegister directives, but do not add services toprovidersIn the array. You need to add it manually.app.module.tsAs shown below.

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 { }
Copy the code

Next, we add the HackerNews API call to the service. Below is hacker-news.service.ts, which has only one function, getLatestStories.

import { Injectable } from '@angular/core';
import { Http } from '@angular/http';

const BASE_URL = 'http://node-hnapi.herokuapp.com';

@Injectable(a)export class HackerNewsService {
  
  constructor(private http: Http) { }

  getLatestStories(page: number = 1) {
    return this.http.get(`${BASE_URL}/news? page=${page}`); }}Copy the code

Now let’s build our infinite scroll load instruction. Here is the full code of the directive, don’t worry about the length of the code, we will break it down.

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((a)= > { return this.scrollCallback(); })
      .subscribe((a)= >{}); }private isUserScrollingDown = (positions) = > {
    return positions[0].sT < positions[1].sT;
  }

  private isScrollExpectedPercent = (position) = > {
    return ((position.sT + position.cH) / position.sH) > (this.scrollPercent / 100); }}Copy the code

The command receives three input values:

  1. scrollPercent– The user needs to scroll to a percentage of the container before it can be calledscrollCallback
  2. scrollCallbackReturns the observable callback.
  3. immediateCallback– Boolean. If true, the instruction will be called immediately after initializationscrollCallback

Angular provides four lifecycle hooks for components and directives.

For this directive, we want to enter the ngAfterViewInit lifecycle hook to register and handle rolling events. From constructor, we inject ElementRef, which allows us to reference the element to which the directive is applied, namely the scroll container.

constructor(private elm: ElementRef) { }

ngAfterViewInit() {

    this.registerScrollEvent();  

    this.streamScrollEvents();

    this.requestCallbackOnScroll();

}
Copy the code

In the ngAfterViewInit lifecycle hook, we execute three functions:

  1. registerScrollEvent– useObservable.fromEventTo listen for the element’s scroll event.
  2. streamScrollEvents-Handle incoming stream of scrolling events according to our requirements, making API requests when scrolling reaches a given percentage of container height.
  3. requestCallbackOnScroll– Once the conditions we set are met, callscrollCallbackTo make an API request.

There is also an optional input condition immediateCallback which, if set to true, we would use DEFAULT_SCROLL_POSITION as the starting data for the stream, which would trigger the scrollCallback without requiring the user to scroll the page. This will call the API once to get the initial data displayed on the page. All functions mentioned above are the same as those in my last article, which explains the usage of various RxJS Observable operators in detail.

Next add the infinite scroll directive to the AppComponent. Below is the full code for 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()); }}Copy the code

GetStories – Calls hackerNewsService and processes the returned data.

Notice from constructorthis.scrollCallbackwriting

this.scrollCallback = this.getStories.bind(this);
Copy the code

We assign the this.getStories function to scrollCallback and bind its context to this. This will ensure that when the callback function in infinite scroll instruction executes, its context is AppComponent not InfiniteScrollerDirective. For more information on the use of.bind, see here.

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

For HTML to be simple, UL acted as a container for the appInfiniteScroller directive, along with the parameters scrollPercent, immediateCallback, and scrollCallback. Each LI represents a news item and only the headline of the item is displayed.

Set the base style for the container.

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

The following example is an infinite scroll load using an Angular directive. Note the scroll bar on the right.

Example: online ashwin – sureshkumar. Making. IO/presents – inf…

I can’t upload a GIF here. Here’s the link to the GIF: giphy.com/gifs/xTiN0F… .

If you enjoyed this article, please share, comment and go to 💚.