rizens

Angular (2+): Attribute @Directive() & Creating An Infinite Scroll Directive

By Oren Farhi on Feb 5, 2016

In the recent article, I used the new ”ng-repeat” in Angular (+2), ”NgFor, and created component that consumes other custom component. In this article, I continue to show further development for Echoes Player with Angular (+2), this time - making it more dynamic by adding infinite scroll directive as what is known in angular2 as an attribute directive.

Angular 1.x Infinite Scroll

In the current production of Echoes Player, in order to add more videos to the result while scrolling, I used ”ng-infinite-scroll”. It has a nice minimal directive api for triggering an infinite scroll - and the usage for Echoes Player is quite simple:

<div class="view-content youtube-results youtube-videos" 
	infinite-scroll="youtubeVideos.searchMore()" 
	infinite-scroll-distance="2"
	>
....
</div>

There are more attributes as an api for this directive, however, in this case - I didn’t use it.

As of this time of writing this post, I didn’t found any Angular (+2) infinite scroll directive / component, so, I figured it is a great opportunity to migrate ”ng-infinite-scroll” directive from AngularJS.x to Angular (+2) while learning how to create one.

Please note that the source code for this AngularJS.x infinite scroll directive is written with ”coffeescript”. However, the production ready code is compiled eventually to ES5.

Migration Process to Angular (+2) Infinite Scroll

After converting the source code back to javascript (using http://js2.coffee/), I started isolating the code to understand what it does and being able to migrate it to ES2015 class.

Most of the important logics is written as an AngularJS.x controller. I figured this code should be an ES2015 class. Actually, I wanted the migrate the logics of this controller to an ES2015, So it will be agnostic to any framework/library, being able to use it anywhere - the same principal applied to ponyfoo’s dragula and his other awesome components.

But first, I had to understand Angular (+2) directive concepts.

Angular (+2) Directive In a Nutshell

In Angular (+2), aside from components, there are still directives. There are built-in directive to the framework, such as: NgFor, NgIf, NgModel, NgClass and there’s an api for creating custom directives.

Essentially, a directive is something like a component.

There are 3 kinds of directives:

  1. A Component - using @Component()
  2. A Structural Directive - using @Directive() - usually changes the DOM of an element - NgIf
  3. An Attribute Directive - using @Directive() - doesn’t change the DOM, but adding behaviour

Infinite Scroll fits well to an Attribute Directive (More on that on the official documentation) - It adds a scroll event (behaviour) to an element and acts upon it.

Lets create the Angular (+2) wrapping code for this directive.

First, lets import the relevant dependencies:

import { Directive, ElementRef, Input, Output, EventEmitter } from '@angular/core';
import { Scroller } from './scroller';

The logics and migrated code of AngularJS.x is imported from the “scroller.ts” class file.

We’re going to use some of angular’s 2 core objects to define the relevant properties.

Directive Definition

To declare an attribute as a directive, similar to Component, we use the ”@Directive()” decorator, while specifying an attribute class selector (css selector):

@Directive({
  selector: '[infinite-scroll]'
})

Directive Bindings & Events

Next, we’ll define the class which will used as a controller for this directive, an input data that we’ll expect to get from the element and an event that this directive will expose.

export class InfiniteScroll {
  @Input() set infiniteScrollDistance(distance: Number) {
    this._distance = distance;
  }

  @Output() scroll = new EventEmitter();

//...
}

The ”infiniteScrollDistance” property is expected to be set from outside the directive, as an attribute api. The same goes for the ”scroll” event, which will trigger a function that is bind from outside. This means, that we’ll use this directive like so:

<div class="search-results"
    infinite-scroll
    [infiniteScrollDistance]="2"
    (scroll)="onScroll()">
</div>

Notice how each attribute in the above ”div” element is matching a different declaration in this directive code.

Referencing Directive’s Element in Angular (+2)

With AngularJS.x, the DI system allowed us to require “$element” and expect to get a reference to the directive’s DOM element:

controller: function ($element) {
	$element.on('scroll', onScroll);
}

With Angular (+2), we use the ”ElementRef” type definition. Also with the use of Typescript, we’ll attach its property reference to ”this” directive context:

constructor(private element: ElementRef) {
   // now, we can reference to: this.element
}

Hook the Scroll Event with ngOnInit to Directive’s Element

Now, we’ll us Angular (+2) hook - ”ngOnInit” - which will run when the directive is ready and will instantiate a new scroller, only once. Notice that I bind “this” context to the onScroll function reference to keep the context of this directive when the scroll event will trigger the event emitter’s property, scroll:

ngOnInit() {
    this.scroller = new Scroller(window, setInterval, this.element, this.onScroll.bind(this), this._distance, {});
  }

  private scroller: Scroller;

  private _distance: Number;

  onScroll() {
    this.scroll.next({});
  }

Migration of Scroller Logic

The “scroller.ts” is an ES2015 class (full source code). Much of this code has been copied from the source implementation of “ng-infinite-scroll” of AngularJS.x and has been adapted to follow ES2015 syntax and relevant updates to the code as much as possible.

Final Thoughts

Still, the infinite-scroll directive is not complete and there are more features to port from the original AngularJS.x version. There are also some points in the code where it is points to Angular (+2) specific ”this.$elementRef.nativeElement” in order to get the actual DOM element.

Echoes Player implementation with ng2 is open source. You can also fork Echoes ES2015 with AngularJS.x version and follow the complete conversion from ES5 to ES2015 of this project.

The Goal of echoes-ng2 is migrating the whole application code to use angular2 various features.

Hi there, I'm Oren Farhi

I'm an Experienced Software Engineer, Front End Tech Lead, focusing on Front End & Software Architecture and creating well formed applications.

Profile Avatar
© 2024, Built by Oren Farhi