Infinite Gallery Viewer with Typescript
Tags: typescript, api, javascript
Published: 2020-10-23 13:24:47

In our previous series release [Build a quote generator with typescript](https://kodeinstincts.hashnode.dev/build-a-quote-generator-with-typescript-1), we converted a regular `Javascript` code to `Typescript`, and it was fun. In today's release, we'd be converting an Infinite Gallery viewer built with `Javascript` to `Typescript`.

> Disclaimer: This article is part of `ZTM` [Andre Negeoi's](https://udemy.com/course/javascript-web-projects-to-build-your-portfolio-resume/) Portfolio enhancement course

The Javascript codebase is on [GitHub](https://github.com/phavor/HacksPanel/tree/main/inScroll). You should obtain the site structure and stylesheet from there. We'd focus on the `Typescript` conversion here.

### Setup Typescript
If you need help setting up your Typescript environment for this exercise, see the setup from our last series [here](https://kodeinstincts.hashnode.dev/build-a-quote-generator-with-typescript-1)

### Getting an Unsplash API key
Head on to [Unsplash](https://unsplash.com/join) and register, next click on `api/developers` tab from the profile menu, then click on `Your apps`. Follow the guide to create a new app and get your API_KEY.

### Writing the App.ts
Let's start with our variables and interface definitions:

```
const imageContainer = document.getElementById('image-container');
const loader = document.getElementById('loader');

let photosArray: GalleryData[];
let ready = false;
let imagesLoaded = 0;
let totalImages = 0;
let count = 5;
const API_KEY = <your_api_key>;
let API_URL = `https://api.unsplash.com/photos/random/?client_id=${API_KEY}&count=${count}
`;

interface GalleryData {
  alt_description: string;
  urls: {
    regular: string;
  };
  links: {
    html: string;
  }
}

interface Attribute {
  src?: string;
  alt?: string;
  href?: string;
  title?: string;
  target?: string;
}
```

### The functions
Our first function would be the `imageLoaded` function. 

```
// Check if all images were loaded
function imageLoaded() {
  imagesLoaded++;

  if (imagesLoaded === totalImages) {
    ready = true;
    loader!.hidden = true;
    count = 30;
    API_URL = `https://api.unsplash.com/photos/random/?client_id=${API_KEY}&count=${count}
`;
  }
}
```

The special conversion highlight here is `loader!.hidden = true`; When we defined the `loader` element initially with this `const loader = document.getElementById('loader');`, we could either have the `HTMLElement` or `null`. To tell typescript that we are sure that the element exists on the page, we use the `!` before the element name. That overrides typescript's strict null check.

**Pro-Tip**: Use only when you are double sure that the element exists on the page and will not be removed throughout the lifetime of your code.

#### setAttribute Helper function
This function will help us stay `DRY`. Typescript warns when we do not type our parameters. Our function would need
- A HTMLElement to set the attributes against, this will be the first parameter.
- An object that contains the attributes we want to set and their values.

```
// Helper function to set attributes on DOM elements
function setAttributes(element: HTMLElement, attributes: Attribute) {
  for (const [key, value] of Object.entries(attributes as Record<string, string>)) {
    element.setAttribute(key, value);
  }
}
```

> Pro Tip: To use `Object.entries`, ensure you add `"ES2017"` or later to your `tsconfig.json`'s lib.

#### displayPhotos function
The `displayPhotos` function generates the DOM elements and renders it to the screen.

```
// Display photos
const displayPhotos = () => {
  totalImages = photosArray.length;
  imagesLoaded = 0;

  photosArray.forEach(photo => {
    // Create <a> linking to unsplash
    const item = document.createElement('a');
    setAttributes(item, {
      href: photo.links.html,
      target: '_blank'
    })

    // Create <img> for photo
    const img = document.createElement('img');
    setAttributes(img, {
      src: photo.urls.regular,
      alt: photo.alt_description,
      title: photo.alt_description
    });

    // Event Listerner, check when each is finished loading
    img.addEventListener('load', imageLoaded)

    // Put <img> inside <a>, then put both in the imageContainer;
    item.appendChild(img)
    imageContainer!.appendChild(item);
  });
}
```

Finally, we will add the `getPhotos` function and attach our `scroll` event to the windows object.

```
// Get photos from Unsplash API
const getPhotos = async () => {
  try {
    const response = await fetch(API_URL);
    photosArray = await response.json()

    displayPhotos();
  } catch (error) {

  }
}

// Hook up the scroll event
window.addEventListener('scroll', () => {
  if (
    window.innerHeight + window.scrollY >=
    document.body.offsetHeight - 1000 &&
    ready
  ) {
    ready = false;
    getPhotos();
  }
})

// On load
getPhotos();
```

Now run the `tsc --build tsconfig.json` to build your `.ts` files into the required `.js` equivalence.

This was super exciting for me, I hope you enjoy it.