Build a Music Player with Angular 2+ & Electron I : Setup & Basics Concepts

Chris Nwamba

Last year, I wrote a series on how we can make a music player using React. The app was able to run as a desktop app because we employed Electron. Soundcloud was also used as the database for a music library. The series did well and helped a lot of us to get started with React, and for that reason, I am doing it again. As a matter of fact, developers who love Angular 2 reached out and wondered if I could make the same piece for Angular.

This time, we are about to go on an adventure with Angular, building a fun music player while dancing and running the player right from our Mac, Windows or Linux environment. This is beginning to feel exciting already.

NOTE: We are using "Angular 2" and not Angular 1.x. I am trying as much as possible not to use the term "Angular 2" but just "Angular" for these reasons.

This article will be structured the same way as that of React, borrow a lot of its approach and utilize most of the techniques you see there.

The following diagram shows what our app will look at the end of this journey:

If this feels exciting, read on.

Required Knowledge

Basics of JavaScript and HTML is all you need to join the party. The new Angular can be daunting, but I will try as much as possible to explain whatever seems strange. If you think you need to see an Angular getting started guide, then Tour of Heroes is a good place to be and is always up to date.

Tools & Tools Setup

One of the most challenging aspects of creating a desktop app with Electron and Angular is setting up the environment. This section will walk you through this process, in a stepwise manner.

1. Creating a New Angular App

The Angular CLI is a utility tool that helps developers effortlessly create and Angular app as wells generate its parts (components, pipes, services, etc.). It hides the application bundling details and exposes a JSON file for configuring how the app is bundled which makes things more declarative.

First, of course, is to install this CLI tool:

npm install -g angular-cli

You can create a new app using the CLI by running the following command:

ng new scotch-music-player

An Angular app has been setup with just a single command. No Webpack a headache, nor SystemJS troubles, just a single line command.

2. Install Electron

Electron amongst all other options has become the most popular tool for building desktop apps using web technologies. As a matter of fact, Electron was used to build some of the popular tools you use as a developer including VSCode and Atom. It has become so popular that it won't be surprising to see that one or more software(s) running on your machine was built on top of this awesome tool.

We need to install Electron in the just created Angular app. That is simple:

npm install --save-dev electron

3. Configure Electron

Electron configuration and setup is achieved using a JavaScript file by listening to lifecycle events, accessing the API methods and passing in our options to this methods. At the root of Angular app, create a main.js file:

// ./main.js
const {app, BrowserWindow} = require('electron')

let win = null;

app.on('ready', function () {

  // Initialize the window to our specified dimensions
  win = new BrowserWindow({width: 1000, height: 600});

  // Specify entry point
  win.loadURL('http://localhost:4000');

  // Show dev tools
  // Remove this line before distributing
  win.webContents.openDevTools()

  // Remove window once app is closed
  win.on('closed', function () {
    win = null;
  });

});

app.on('activate', () => {
  if (win === null) {
    createWindow()
  }
})

app.on('window-all-closed', function () {
  if (process.platform != 'darwin') {
    app.quit();
  }
});

When the app is ready, we create a new BrowserWindow and configure it's dimension.

Next, we use the loadURL method to tell Electron where it should fetch its content from. http://localhost:4000 is where Angular CLI will run our Angular app at if we run ng serve.

The openDevTools method shows the developer tools for debugging purposes. This is not needed when the app is being distributed and therefore should be removed before that.

We need to tell Electron about this file by specifying it in the package.json:

{
  "name": "scotch-music-player",
  "version": "0.0.1",
  "main": "main.js",
  ...
}

Run the following command to start Angular in one terminal:

ng serve

...and the following to start Electron in another

electron .

Awesome!

4. Distribution Config

There are few points to consider while preparing the app for distribution:

  1. The loadURL should be a file path and not http path.
  2. The base URL should point to the dist folder and not /
  3. Dev tools should be hidden.

To achieve this effectively, we need to install dotenv, a package that allows you the opportunity to configure environmental variables in a .env file:

npm install --save dotenv

Create a .env file at the root of the project folder with the following values:

PACKAGE=false
HOST=http://localhost:4200

You can now update the ready event handler behavior accordingly:

// ./main.js
const {app, BrowserWindow} = require('electron')
const path = require('path');
const url = require('url');

require('dotenv').config();

let win = null;

...

app.on('ready', function () {

  // Initialize the window to our specified dimensions
  win = new BrowserWindow({width: 1000, height: 600});

  // Specify entry point
  if (process.env.PACKAGE === 'true'){
    win.loadURL(url.format({
      pathname: path.join(__dirname, 'dist/index.html'),
      protocol: 'file:',
      slashes: true
    }));
  } else {
    win.loadURL(process.env.HOST);
    win.webContents.openDevTools();
  }

  ...

});

When the PACKAGE env variable is set to true, it is expected that the app is run with content in the dist folder which is generated when ng build is executed. If PACKAGE=true is NOT the case, then we just continue what we were doing before.

We just considered the 2nd and 3rd points, how about the 1st point, on base URL?

The ng build command allows you to pass in options of base URL so you can do something like this:

ng build --base-href /$ROOT/your-project-folder/scotch-player/dist/

That's not fun to always type at the console so you can add it as a script to package.json:

"scripts": {
    "start": "ng serve",
    "build": "ng build --base-href /$ROOT/your-project-folder/scotch-player/dist/",
    "electron": "electron .",
}

You can test these distribution strategies by changing PACKAGE to true in .env as well as running the following commands in different terminal:

npm run build

and

npm run electron

5. Live Reload

Our productivity gets a boost when we do not have to stop electron and restart it to see our changes. Therefore, we need a way to ask Electron to refresh our app once a change occurs in the application.

A module called electron-reload exists for this so go ahead to install:

npm install electron-reload

In your main.js file you just need to require the module and pass in a directory to watch:

...
require('electron-reload')(__dirname);
...

Angular Components: Presentation vs Container

A very popular pattern in the JavaScript community these days, especially amongst component architecture based projects, is separating app components into two major parts -- presentation (a.k.a UI) and container. This phenomenon rose among React developers and with time became popular among other developers working on different component-based solutions. We will explore what these types are, why they even exist and when to use which.

Presentation (UI) Components

UI components as they are also called are responsible for rendering contents in the view. Contents and styles are what they specialties. They never care about how this content come about; they just know how to render it.

For the reason that UI components are ignorant of data, they should have a way to receive data from whatever is responsible for that. In Angular, this is achieved using the Input directive which we will use while building our app.

Events also are delegated up to the container component and not handled in the UI components using the Output directive. We will also see how while building the music app.

The reason why presentation components do not hold data models or handle events is, these components have high tendency to be re-used anywhere in your app and if data or events are bound directly to them, they become very difficult to re-use or move around.

The following are good examples of UI components:

  • Date picker
  • Music controls
  • Video controls
  • Image galleries
  • Navigation menus
  • Accordions
  • Autocompletes
  • ...and much more

Container Components

This component does less to no content and more data models. Container components are responsible for how data is manufactured or fetched, how data is structured, formatted and so on.

Container components are the guys responsible for sending data down to presentation component and handling events delegated to them from the presentation component.

Your app most time will have few container components depending on how large the app is. Sometimes a container component to a page or section and sometimes a container component to house the whole app if the app is really small or a just a single view app.

A very good example is the app component that comes with every Angular quickstart or starter kit.

Up Next

What we have just done is the most intimidating aspect of creating an Angular + Electron project which is setting it up. Explaining the basic types of components will explain why our app is structured the way it is while we build the app out. In the next article, we will get more awesome by creating the presentation components of our music app.

Chris Nwamba

44 posts

JavaScript Preacher. Building the web with the JS community.