Share this post on:

Motivation

A while ago, I was asked by a client to implement a feature for an additional horizontal scrolling bar located on the top of a big table. The users could then scroll even when they were not viewing the bottom of the table. After successfully implemented the required feature, I decided to create a separate library for it, written in Typescript and aimed for Angular. It’s also a good exercise to do if you want to learn how to create libraries for Angular.

Let’s see how the project structure looks like.

> ls
Mode Name
---- ----
d----- dist
d----- e2e
d----- lib
d----- node_modules
d----- src
-a---- .editorconfig
-a---- .gitignore
-a---- angular.json
-a---- package.json
-a---- package-lock.json
-a---- README.md
-a---- tsconfig.json
-a---- tslint.json
-a---- yarn.lock

This is actually how a standard Angular 6 project looks like. You can use ng new to generate a template. The only thing that I added is the lib folder, where the actual library resides. In src the application using the library is located.

You can follow the tutorial here for a more in-depth explanation of the things under the hood.

Implementation using jQuery

We’ll now implement the component which does the trick via jQuery because Angular (as far as I know) doesn’t have the possibility of resizing elements.

mindgaze-double-scroll.component.html

<div [id]="scrollTargetId" style="overflow-x: auto; overflow-y: hidden;">
  <div [id]="scrollTargetContentId" style="height: 1px;"></div>
</div>
<div [class]="className" [id]="scrollSourceId" #scrollSource style="overflow-x: auto;">
  <ng-content></ng-content>
</div>

This is the html part. Here we see two divs, inside a div with the id bound to scrollTargetId. The nested divs have the ids scrollTargetContentId and scrollSourceId  respectively. We assign an unique id for each of these elements, as we might have multiple instances of this component on one page.

The first div is actually the container for the second horizontal bar whereas the second one is the maine one which is holding the content (Notice ng-content element).

mindgaze-double-scroll.component.ts

Now to the component class. This one is a bit more complicated because the main logic is implemented here. Notice we defined the id properties which are composed with the help of a static property, incremented at each constructor call.

private ourId: number;

public get scrollTargetId(): string {
  return `scroll-target-${this.ourId}`;
}

public get scrollTargetContentId(): string {
  return `scroll-target-content-${this.ourId}`;
}

public get scrollSourceId(): string {
  return `scroll-source-${this.ourId}`;
}

Then we have the function to initialize the double scroll functionality:

initDoubleScroll(containerId1: string, contentId1: string, containerId2: string): () => void {
  const container1 = $(`#${containerId1}`)
  const container2 = $(`#${containerId2}`)
  const content1 = $(`#${contentId1}`)
  const content2 = container2.children();

  const resizeCallback = () => {
    content1.width(content2.width() || 0);

    var widthContent2 = content2.width() || 0;
    var widthContainer2 = container2.width() || 0;
    if (widthContent2 <= widthContainer2) {
      container1.hide();
    } else {
      container1.show();
    }
  }

  $(window).resize(resizeCallback)
  content2.resize(resizeCallback)

  container1.scroll(() => {
    container2.scrollLeft(container1.scrollLeft() || 0);
  });
  container2.scroll(() => {
    container1.scrollLeft(container2.scrollLeft() || 0);
  });

  return resizeCallback;
}

So this is basically the function with the heavy lifting. We notice it defines a resized callback that gets called whenever the user resizes the window or other elements affect it’s size. And we wire up the scroll events for each container so that they are in sync whenever the user scrolls.

Full file here

Using it in your application

Before using it, you need to install the package via npm aka npm install –save mindgaze-doublescroll. Then import the module and add to imports array:

import { DoubleScrollModule } from 'mindgaze-doublescroll';

@NgModule({

imports: [
  BrowserModule,
  DoubleScrollModule
],

The final step is to use it in the UI:

<mdz-double-scroll>
  <table>
    <tbody>
      <tr style="white-space: nowrap;">
        <td style="font-size:160px;">This is a very large scroll area. The text here gets scrolled horizontally with double scroll bars!</td> 
      </tr>
    </tbody>
  </table>
</mdz-double-scroll>

It should look like so 😀

Conclusions

Now it’s very easy to achieve double scrolling in Angular 6. Feel free to contact me at af.ivan@live.com for any questions. Also don’t forget to checkout the github repo and the npm registry

Cheers!

Share this post on:
Avatar afivan

Author: afivan

Enthusiast adventurer, software developer with a high sense of creativity, discipline and achievement. I like to travel, I like music and outdoor sports. Because I have a broken ligament, I prefer safer activities like running or biking. In a couple of years, my ambition is to become a good technical lead with entrepreneurial mindset. From a personal point of view, I’d like to establish my own family, so I’ll have lots of things to do, there’s never time to get bored 😂

Leave a Reply