Rest Countries API App

REST Countries API supplying list of countries on the left with the map on the right. Selected country data in panel.

Rest Countries API is a neat endpoint that provides a list of countries shown in the selection pane. Additionally, it provides capital names, flag imagery, population, native and region names. It also provides geographical data. The geographical data is then coupled with Leaflet’s mapping abilities. The endpoints’ additional data is shared with Leaflet on click.

Git Repo

Leaflet is an interactive mapping solution akin to Google Maps, but provides devs an onslaught of options. And unlike Google Maps, there’s no need for a Gmail account and API key. Only caveat is Leaflet is a library that takes some getting used to. Moreover, how it’s implemented may vary between framework versions.

The below app extracts country geographical data from the Rest Countries api. As a result, the output is sent to Leaflet. Subsequently, we’re able to quickly cycle through the countries list, locating the central point of each selection. Check out the working version here.

One challenge I did encounter was how to fit all the data in the UI, in every screen size. There’s simply so many items to display with little screen real estate. Upgrading to Angular 14 however broke most of Leaflet so responsiveness didn’t make it into the rebuild. You know how it is. When you build something awesome and it all falls apart, the inclination to rebuild is lower than the initial drive.

Building a Rest Countries API App

When I build a UI like this at work or for practice, I literally begin with pen and paper. That’s if there’s not a provided mock of course. I sketch out how every feature works then map out the data flow and business logic. In rare scenarios I’ll do everything on the fly. But I know from experience, taking the time to flesh out an offline draft pays off.

Regardless of the task actually, pre planning allows us to see things we wouldn’t normal think of. Table and mobile views for instance don’t have space for the same elements shown on desktop. Planning for accessibility is a big one. SEO and security planning. Planning for UI optimization. Planning for components, routes, and error handling all are what I consider a best practice. Planning for API design too is actually fun in my opinion. I get to see how lean I can make my code. This part actually usually comes mid project for me. But we can certainly plan for it.

Scripting the Project

I had a firm idea of what I wanted to accomplish with the REST Countries API. It was challenging to work with Leaflet initially because it’s not the most intuitive library. Even with experience in thereof, I still don’t think it’s easy.

Regardless, I’m semi happy with the result. It begins with a resize event that’s used in app.component. Most if not all is in vanilla JavaScript using filters, working with arrays and objects. Once I got the basics down, it really wasn’t too bad. The biggest challenge was squeezing everything into the UI. And things sometimes don’t work as expected in Stackblitz.

onResize($event) {
    this.screenSize = $event.target.innerWidth;
  }

  showNationCount() {
    this.nationCount.nativeElement.innerHTML =
      '( ' + this.countriesData.length + ' )';
  }

  viewFilters() {
    this.filterMenu = !this.filterMenu;
  }

  filterAmericas() {
    this.setMaster();
    this.closeFilterMenu();
    this.americasFilter = this.countriesData.filter(function (item) {
      return item.region === 'Americas';
    });
    this.countriesData = this.americasFilter;
  }

  filterEurope() {
    this.setMaster();
    this.closeFilterMenu();
    this.europeFilter = this.countriesData.filter(function (item) {
      return item.region === 'Europe';
    });
    this.countriesData = this.europeFilter;
  }

  filterAfrica() {
    this.setMaster();
    this.closeFilterMenu();
    this.africaFilter = this.countriesData.filter(function (item) {
      return item.region === 'Africa';
    });
    this.countriesData = this.africaFilter;
  }

  filterAsia() {
    this.setMaster();
    this.closeFilterMenu();
    this.asiaFilter = this.countriesData.filter(function (item) {
      return item.region === 'Asia';
    });
    this.countriesData = this.asiaFilter;
  }

  filterOceania() {
    this.setMaster();
    this.closeFilterMenu();
    this.oceaniaFilter = this.countriesData.filter(function (item) {
      return item.region === 'Oceania';
    });
    this.countriesData = this.oceaniaFilter;
  }

  filterPolar() {
    this.setMaster();
    this.closeFilterMenu();
    this.polarFilter = this.countriesData.filter(function (item) {
      return item.region === 'Polar';
    });
    this.countriesData = this.polarFilter;
  }

  closeFilterMenu() {
    this.filterMenu = false;
  }

  setMaster() {
    this.countriesData = this.masterArray;
  }

  showAll() {
    this.closeFilterMenu();
    this.countriesData = this.masterArray;
  }

  onMapReady(map: Map) {
    this.map = map;
  }

  private initializeMapOptions() {
    this.mapOptions = {
      center: latLng(38, -97),
      zoom: 4,
      layers: [
        tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
          maxZoom: 18,
          attribution: 'frontenddevelopment.tech',
        }),
      ],
    };
  }

  selectItem(i: any, e: any) {
    this.closeFilterMenu();
    this.clearString();
    var name = e.target.innerHTML;
    this.countriesData.map(function (index: any, val: any) {
      if (index.name === name) {
        i = val;
      }
    });

    this.setMarker(i);
    this.setDetails(i);
  }

  setMarker(i: any) {
    this.userInteraction = true;
    this.longitude = this.countriesData[i].latlng[0];
    this.latitude = this.countriesData[i].latlng[1];

    const marker = new Marker([this.longitude, this.latitude]).setIcon(
      icon({
        iconSize: [18, 30],
        iconAnchor: [13, 41],
        iconUrl: 'https://unpkg.com/leaflet@1.3.4/dist/images/marker-icon.png',
      })
    );

    marker.bindTooltip(this.countriesData[i].name).openTooltip();

    this.map.setView([this.longitude, this.latitude], 5);
    this.map.invalidateSize();
    this.markerHandler(marker);
  }

  markerHandler(pin: any) {
    pin.addTo(this.map).addEventListener('click', function (event) {
      this.selectedItem = event.sourceTarget._tooltip._content;
    });
  }

  setDetails(i: number) {
    this.countryName = this.countriesData[i].name.common;
    this.capitalCity = this.countriesData[i].capital;
    this.flag = this.countriesData[i].flags.png;
    this.nativeName = this.countriesData[i].nativeName;
    this.population = this.countriesData[i].population;
    this.borderingCountries = this.countriesData[i].borders;
    this.geo = this.countriesData[i].region;
  }

  formatNumber(i: number) {
    var nf = Intl.NumberFormat(),
      x = this.population,
      result = nf.format(x);
    return result;
  }

  clearString() {
    this.searchTerm = '';
    this.setMaster();
  }

  projectDescription() {
    this.projectIntro = !this.projectIntro;
  }

Building apps like this allow me to create things I’d otherwise never get the chance to. I guess that’s part of being a developer. Coding for practice and fun in my spare time (like any of us have an onslaught of thereof lol) is rather necessary. It’s part of the craft. Otherwise, you become type casted to one role. A group of technologies unable to move forward.

To learn and grow as a developer, please see additional projects I’ve built. Related to this one are a fully dynamic interface and a weather API UI.

Regardless, the ultimate goal was to create a relatively simple rendition of two APIs playing well together. Check out the REST Countries code sample here.

Be the first to comment

Leave a Reply

Your email address will not be published.


*