Build a Desktop App to Track GitHub Pull Requests using Angular 2 and Electron.

Marcel Nwamadi
Hackmamba
Published in
10 min readOct 7, 2017

Working on multiple projects? Constantly checking for pull requests on your Repos? Feel the need to manage this effectively? Electron allows you to write cross-platform desktop applications.

In this article, we will be using Electron and Angular 2 to build a desktop application that tracks Github Pull Requests.

Prerequisites

With a basic knowledge of HTML, CSS, JavaScript, and Angular, we are good to go. Knowledge of electron is also a plus but not compulsory.

Installation

  • node: node.js is an open source server framework which we will be using to run the app. You should have node installed in your machine.
  • angular-cli : npm install -g @angular/cli: It is best practice to make use of angular-cli as it makes it easy to create an angular application that already works, right out of the box.
  • electron: This allows us create a desktop wrapper for our app.

Creating a New Angular App

We’ll be making use of the angular-cli to create an Angular project.

Let’s install angular-cli as a global dependency:

npm install -g @angular/cli

Next, generate a new project using the ng CLI command:

ng new electron-app

Install Electron

We also need to install the Electron CLI tool globally:

npm install -g electron

This exposes some commands that help build and package Electron apps.

Creating the build files

Once that’s done, we’ll create two files that show how the Electron app is packaged. We’ll put the files into a new Electron folder located in src folder:

cd electron-app && mkdir src/electron

The files are:

  • package.json
  • electron.js

The package.json file contains the information about where to find the script to start the electron app as well as information about the electron application itself.

We will add the following content to the package.json file:

// src/electron/package.json{“name” : “angular-electron”,“version” : “0.1.0”,“main” : “electron.js”}

The second file, electron.js is the script that shows the behavior of the application while creating the main window.

Let’s add the following content to the electron.js file:

// src/electron/electron.jsconst {app, BrowserWindow} = require('electron')// Keep a global reference of the window object, if you don't, the window will// be closed automatically when the JavaScript object is garbage collected.let winfunction createWindow () {// Create the browser window.win = new BrowserWindow({width: 800, height: 600})// and load the index.html of the app.win.loadURL(`file://${__dirname}/index.html`)// Open the DevTools.win.webContents.openDevTools()// Emitted when the window is closed.win.on('closed', () => {win = null})}// This method will be called when Electron has finished// initialization and is ready to create browser windows.// Some APIs can only be used after this event occurs.app.on('ready', createWindow)// Quit when all windows are closed.app.on('window-all-closed', () => {// On macOS it is common for applications and their menu bar// to stay active until the user quits explicitly with Cmd + Qif (process.platform !== 'darwin') {app.quit()}})app.on('activate', () => {// On macOS it's common to re-create a window in the app when the// dock icon is clicked and there are no other windows open.if (win === null) {createWindow()}})
  • The variable win keeps a global reference of the window object.
  • The createWindow function creates the browser window.
  • win.loadURL()method is used to load the app’s entry point which is index.html.
  • win.webContents.openDevTools() opens the dev tools for debugging.
  • app.on(‘ready’,createWindow) method is called when Electron has finished initialization and is ready to create browser windows.
  • app.on(‘window-all-closed) method quits when all windows are closed.

Electron uses the file:// protocol instead of http://.

Therefore, we have to edit src/index.html from:

<base href=”/”>

to

<base href=”./”>

What we need is to copy the two files created into the dist folder when building the app.

Open up package.json file in the root of the application not the one in src/electron:

“scripts” {[…]“build-electron”: “ng build –base-href . && copy src\electron\* dist”,“electron”: “npm run build-electron && electron dist”},[…]

Next, use the following command to start the app:

npm run electron

Now that electron is up and running, we can go ahead and create our application.

Register a Github Application

Our task in this example is to communicate with github API and get pull requests from a GitHub

user’s repository.

To do this, we will have to create a github application. A reason for doing this is to get some important information which we will be making use of later in the example. The information we will need to get from the github application are:

  • Client ID
  • Client Secret.

Note: Doing this is important because If you do not do this, you will only get a certain amount of requests from your application and you will begin to receive an error.

Go to github.com/settings/developers and register a new OAuth application:

After registering the Github application, you will have a client ID and a client secret which will be used later in the project.

Building the Angular App

Now that we have a Github app created, let’s head back to our existing Angular project and start integration.

Navigate to the src/app folder from the terminal :

cd src/app

Here, we will be using the angular-cli to generate a new component called `GitHub component`.Run the following command in the terminal:

ng generate component github

This automatically generates a folder named github, containing github.component.ts, github.component.css and github.component.html files.

We need to add the github component selector by including the <app-github`> selector in app.component.html:

<div class="container"><app-github></app-github></div>

CREATE SERVICE

Let’s create a service to fetch data from the Github API which will be accessible and used by the component. The service will be used to make data calls for our component. This service will make a `get` request to with the Github Pull-Request API endpoint.

Run the following command to generate a service:

ng generate service github

This creates GitHub.service.ts and GitHub.service.spec.ts files. Testing is not in the scope of this article, so we are only going to make use of GitHub.service.ts.

Create a Github Service

Open github.service.ts file and update with the following code block:

import { Injectable } from '@angular/core';import {Http, Headers} from '@angular/http';import 'rxjs/add/operator/map';@Injectable()export class GithubService {constructor() {}}

We had to import Injectable, Http, Headers, rxjs/add/operator/map.

  • The @Injectable() operator applies the Angular Injectable function. The @Injectable() decorator tells TypeScript to emit metadata about the service.
  • Http will be used to perform http requests using XMLHttpRequest as the default backend.
  • Headers returns a new Headers instance from the given DOMString of Response Headers.
  • Reactive Extensions for JavaScript (RxJS) is a reactive streams library that allows you to work with asynchronous data streams
  • The map operator transforms the response emitted by Observable.

Add local properties to store the credentials we received from github in the service we created:

import { Injectable } from '@angular/core';import {Http, Headers} from '@angular/http';import 'rxjs/add/operator/map';@Injectable()export class GithubService {private username = '<github username>';private client_id ='<private client_id>';private client_secret='<private client_secret>';constructor() {}}

Now, within the constructor, we have to inject and resolve the Http Module:

constructor(private _http:Http) {}

github.service.ts should look like this:

import { Injectable } from '@angular/core';import {Http, Headers} from '@angular/http';import 'rxjs/add/operator/map';@Injectable()export class GithubService {private username = '<github username>';private client_id ='<private client_id>';private client_secret='<private client_secret>';constructor(private _http:Http) {}}

The GithubService has to be imported in app.module.ts. It also has to be included in the providers array of app.module.ts. This is to tell the github.component.ts how to create an instance of this service when we need it.

providers: [GithubService],app.module.ts file should look like this:import { BrowserModule } from '@angular/platform-browser';import { NgModule } from '@angular/core';import {HttpModule} from '@angular/http';import {FormsModule} from '@angular/forms';import {GithubService} from './github.service';import { AppComponent } from './app.component';import { GithubComponent } from './github/github.component';@NgModule({declarations: [AppComponent,GithubComponent],imports: [BrowserModule,HttpModule,FormsModule],providers: [GithubService],bootstrap: [AppComponent]})export class AppModule { }

Import the github.service.ts in github.component.ts and inject GithubService in the github.component.ts constructor. Now, the service can be used within the component.

import { Component, OnInit } from '@angular/core';import {GithubService} from '../github.service';@Component({selector: 'app-github',templateUrl: './github.component.html',styleUrls: ['./github.component.css']})export class GithubComponent implements OnInit {constructor(private _githubService:GithubService) {}}

Get the GitHub Pull Request API

In the GithubService class, create a function, getPulls(). In this function, we will be making a get request to https://api.github.com/repos/<REPONAME>/pulls and use the `map()` operator to transform the output:

getpulls(){return this._http.get('https://api.github.com/repos/'+this.reponame+'/pulls').map(res => res.json());}

res => res.json() implies that we are returning json content.

Subscribe to the Observable

In the github.component.ts, make a call the getpulls method, subscribe to the observable returned:

this._githubService.getpulls().subscribe(pulls =>{this.pulls= pulls;});

Just above the constructor, add pulls as a property with type any.

pulls: any;

The github.component.ts should look like this:

import { Component, OnInit } from '@angular/core';import {GithubService} from '../github.service';@Component({selector: 'app-github',templateUrl: './github.component.html',styleUrls: ['./github.component.css'],})export class GithubComponent implements OnInit {pulls: any;constructor(private _githubService:GithubService) {}this._githubService.getpulls().subscribe(pulls =>{console.log(pulls);this.pulls= pulls;});ngOnInit() {}}

Back to github.service.ts, we shall create another function, updateReponame():

//reponame is passed in and given type of StringupdateReponame(reponame: String){this.reponame = reponame;}

Just above the constructor, add reponame as a property with type any.

reponame: any;

This will be the final structure of github.service.ts file:

import { Injectable } from '@angular/core';import {Http, Headers} from '@angular/http';import 'rxjs/add/operator/map';@Injectable()export class GithubService {private username = '1990marcel';private client_id ='6faad5ce6694d0213a59';private client_secret='b9df2d050a104d5c11bee854a972d4654a1fc217';reponame: any;constructor(private _http:Http) {console.log('service init');}getpulls(){return this._http.get('https://api.github.com/repos/'+this.reponame+'/pulls').map(res => res.json());}updateReponame(reponame: string){this.reponame = reponame;}}

In github.component.ts, we’ll call the updateReponame() function, and in it, we will pass this.username as a parameter.

this._githubService.updateReponame(this.username);

Above the github.component.ts’s constructor, add username as a property with type any.

username: any;

Still in github.component.ts, we’ll create a function, search(), which we will make use of in github.component.html.

The search()function will now be holding our service functions:

this._githubService.getpulls().subscribe(pulls =>{console.log(pulls);this.pulls= pulls;});

and

this._githubService.updateReponame(this.username);

This will be the final structure of github.component.ts file:

import { Component, OnInit } from '@angular/core';import {GithubService} from '../github.service';@Component({selector: 'app-github',templateUrl: './github.component.html',styleUrls: ['./github.component.css'],providers:[GithubService]})export class GithubComponent implements OnInit {username: any;pulls: any;constructor(private _githubService:GithubService) {}search(){this._githubService.updateReponame(this.username);this._githubService.getpulls().subscribe(pulls =>{this.pulls= pulls;});}ngOnInit() {}}

Display Pull Requests

This will be achieved by inputting (username/repo-name) into a form, which returns the pull requests after being submitted.

Bootstrap is used for our user interface. Insert the CDN link from getbootstrap.com , within the <head> tag of index.html

<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/css/bootstrap.min.css" integrity="sha384-rwoIResjU2yc3z8GV/NPeZWAv56rSmLldC3R/AZzGRnGxQQKnKkoFVhFQhNUwEyJ" crossorigin="anonymous">

We want to display the following from the pull requests we will be fetching:

  • Title
  • Author
  • TIME UPDATED.

Let’s create a navbar to display github image and a text description:

<nav class="navbar navbar-dark fixed-top bg-dark"><!--We are getting the github image from https://github.com/favicon.ico--><img src="https://github.com/favicon.ico" width="48"><h2 class="text">Github Pull</h2></nav>

Style github.component.css as shown below:

.text{color: white;}.jump{margin-top: 10px;margin-left: 20%;}.push{margin-top:8em;}

Create a form to receive input

Create a form with a submit function search()

<div class="jumbotrun push"><div class="col-md-12"><form (submit) ="search()" class="jump"><div class="form-group"><label for="Repo">Enter repo (username/repo-name)</label><input type="text" class="form-control"placeholder="username/repo-name"style="width:300px;display:inline-block;margin-right: 25px; margin-left: 25px;"name="username" [(ngModel)] = "username"/><input type="submit" value="Fetch" class="btn btn-info" /></div></form></div></div>

In github.component.html, we have a very basic form, having a submit event which is set to call the function search(). The search() function was earlier set in github.component.ts to get the github API request from github.service.ts.

We make use of angular’s two-way binding using [(ngModel)] which is the Angular syntax to bind the username property to the textbox.

Note: Be sure to import FormsModule in app.module.ts and add it to the @NgModule metadata’s imports array.

To display the pull requests, we shall make use of:

  • Angular’s *ngFor directive which iterates over the pulls array and renders an instance of this template for each pull in that array
  • Angular’s interpolation binding syntax, {{}}
<div *ngFor="let pull of pulls"><div class="card card-inverse card-primary mb-3 text-center"><div class="card-block"><blockquote class="card-blockquote"><h2>TITLE: {{pull.title}}</h2><p>AUTHOR: {{pull.author_association}}</p><footer>TIME UPDATED: {{pull.updated_at}}</footer></blockquote></div></div></div>

Run the app one more time and expect the following output:

npm run electron

Enter username/repo-name:

Viola! Our app works perfectly well! You can play with the code HERE.

Conclusion

We’ve built our app based on GitHub’s platform, making a request to GitHub’s API and wrapping it with Electron. Electron is a native web view which enables you to wrap your web application into a desktop application. Feel free to create more awesome desktop apps with it and see if you have any limitations!

This article was originally posted on scotch

--

--