Angular 2: Attribute @Directive() and Creating An Infinite Scroll Directive
How to make Angular apps more dynamic by adding an infinite scroll directive.
Join the DZone community and get the full member experience.
Join For FreeIn 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 an infinite scroll directive, what is known in Angular 2 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 angular 1.x to angular 2 while learning how to create one.
Please note that the source code for this angular 1.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 angular 1.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:
- A Component — Using @Component()
- A Structural Directive — Using @Directive() - usually changes the DOM of an element - NgIf
- 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 (behavior) 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 'angular2/core';
import { Scroller } from './scroller';
The logics and migrated code of angular 1.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 and 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 angular 1.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 angular 1.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 angular 1.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 Angular 1.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.
Published at DZone with permission of Oren Farhi. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments