I think that you’ve heard this Atwood’s law.

“Any application that can be written in JavaScript, will eventually be written in JavaScript.”

— Jeff Atwood, Author, Entrepreneur, Cofounder of StackOverflow

You can write hybrid or native mobile apps with JavaScript (Ionic Framework), and now you can even create desktop apps (Windows, Mac, Linux) with JavaScript (Electron).

Basically, my mind was exploded with the ability of JavaScript. I will let it sink in for a moment.

If you want to check the code and try the app, you can just jump to my github repository.

Now, I will show you how I create a simple to-do app with Electron 4 and Angular 7.

Features:

Create a new task

Drag and drop tasks

Delete finished tasks

Apply Angular Material for styling

Start with a boilerplate: Angular-Electron

If you don’t want to use Angular, you can check out other templates from Electron Forge such as React, Vue, Vanilla…

After you clone the boilerplate, you need to install packages.

npm install

After that, for drag and drop and styling feature, we also need to install some extra packages.

npm i -D @angular/cdk @angular/material @angular/animations hammerjs

Then you import necessary modules to app.module.ts

// app.module.ts

import { DragDropModule } from ' import { BrowserAnimationsModule } from ' @angular/platform-browser /animations';import { DragDropModule } from ' @angular/cdk /drag-drop';

import {MatInputModule} from '

import {MatIconModule} from '

import {MatListModule} from ' import {MatGridListModule} from ' @angular/material /grid-list';import {MatInputModule} from ' @angular/material /input';import {MatIconModule} from ' @angular/material /icon';import {MatListModule} from ' @angular/material /list'; ...

...

imports: [

BrowserAnimationsModule,

MatGridListModule,

MatInputModule,

MatIconModule,

MatListModule,

DragDropModule

...

],

}) @NgModule ({...imports: [BrowserAnimationsModule,MatGridListModule,MatInputModule,MatIconModule,MatListModule,DragDropModule...],})

We can include a prebuilt theme to style.scss

// style.scss // Angular Material

@import "~@angular/material/prebuilt-themes/indigo-pink.css";

Some components reply on HammerJS for gestures, we’ll put it on src/main.ts

// src/main.ts import 'hammerjs';

We also need to add Material Icons to index.html

// index.html <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">

2. Create todo component

You can delete or rename the home component from the boilerplate.

todo component

// todo.component.ts import { Component, OnInit } from ' @angular/core ';

CdkDragDrop,

moveItemInArray,

transferArrayItem

} from ' import {CdkDragDrop,moveItemInArray,transferArrayItem} from ' @angular/cdk /drag-drop'; export interface Todo {

title: string;

date: string;

}

selector: 'app-todo',

templateUrl: './todo.component.html',

styleUrls: ['./todo.component.scss']

}) @Component ({selector: 'app-todo',templateUrl: './todo.component.html',styleUrls: ['./todo.component.scss']}) export class TodoComponent implements OnInit {

value = ''; public todo: Todo[] = [

{ title: 'Wake up', date: new Date().toString() },

{ title: 'Shopping', date: new Date().toString() },

]; public done: Todo[] = [

{ title: 'Write a blog', date: new Date().toString() },

{ title: 'Study Electron', date: new Date().toString() }

]; arr = []; constructor() {} ngOnInit() {} onSubmit() {

this.todo.push({ title: this.value, date: new Date().toString() });

this.value = '';

} clearInput(event: MouseEvent) {

if ((<HTMLElement>event.target).nodeName === 'MAT-ICON') {

this.value = '';

}

} deleteTask(index: number) {

this.done.splice(index, 1);

} onAreaListControlChanged(index: number) {

setTimeout(() => {

const task = this.todo.splice(index, 1);

this.done.unshift(task[0]);

}, 1000);

} drop(event: CdkDragDrop<string[]>, type: string) {

if (event.previousContainer !== event.container) {

transferArrayItem(event.previousContainer.data, event.container.data,

event.previousIndex, event.currentIndex);

} else {

if (type === 'todo') {

moveItemInArray(this.todo, event.previousIndex, event.currentIndex)

} else {

moveItemInArray(this.done, event.previousIndex, event.currentIndex);

}

}

}

}

For the Html template

// todo.component.html <div class="container">

<h1>Todo List!</h1>

<form class="example-form">

<mat-form-field class="example-form-field">

<input matInput type="text" placeholder="Add new task"

[(ngModel)]="value"

[ngModelOptions]="{standalone: true}"

(keyup.enter)="onSubmit()">

<button mat-button *ngIf="value" matSuffix mat-icon-button aria-label="Clear" (click)="clearInput($event)">

<mat-icon>close</mat-icon>

</button>

</mat-form-field>

</form> <p *ngIf="todo.length < 1">You're rock! There are no more tasks.</p>

<div class="list">

<h3 *ngIf="todo.length > 0">Ongoing List</h3>

<mat-selection-list cdkDropList

[cdkDropListData]="todo"

[cdkDropListConnectedTo]="secondList"

#firstList="cdkDropList"

(cdkDropListDropped)="drop($event, 'todo')">

<mat-list-option *ngFor="let task of todo; let i = index" cdkDrag (click)="onAreaListControlChanged(i)">

{{ task.title }}

<mat-divider></mat-divider>

</mat-list-option>

</mat-selection-list>

<h3 *ngIf="done.length > 0">Done</h3>

<mat-list cdkDropList

[cdkDropListData]="done"

[cdkDropListConnectedTo]="firstList"

#secondList="cdkDropList"

(cdkDropListDropped)="drop($event, 'done')">

<mat-list-item role="listitem" *ngFor="let task of done; let i = index" cdkDrag>

<mat-icon matListIcon (click)="deleteTask(i)">delete_forever</mat-icon>

<s style="color: grey;">{{ task.title }}</s>

<mat-divider></mat-divider>

</mat-list-item>

</mat-list>

</div>

</div>

Put some styles to the template

// todo.component.scss .container{

margin: 0 20px;

} .example-form-field {

width: 100%;

button {

border: none;

}

}

Edit the routing module

// app-routing.module.ts

import { NgModule } from '

import { Routes, RouterModule } from ' import { TodoComponent } from './components/todo/todo.component';import { NgModule } from ' @angular/core ';import { Routes, RouterModule } from ' @angular/router '; const routes: Routes = [

{

path: '',

component: TodoComponent

}

];

imports: [RouterModule.forRoot(routes, {useHash: true})],

exports: [RouterModule]

})

export class AppRoutingModule { } @NgModule ({imports: [RouterModule.forRoot(routes, {useHash: true})],exports: [RouterModule]})export class AppRoutingModule { }

Then add TodoComponent to app.module.ts

// app.module.ts import { TodoComponent } from './components/todo/todo.component';

declarations: [

AppComponent,

TodoComponent,

WebviewDirective

]

...

}) @NgModule ({declarations: [AppComponent,WebviewDirective...})

After having your todo component ready, you can try to test the app by running:

npm start

I will open a new window with inspection, so you can work and modify your app. For production, you can build your cross-platform desktop apps through these commands:

npm run electron:linux

npm run electron:windows

npm run electron:mac

From now, you can start playing around with this simple todo app. You can save data locally or through a cloud (Firebase…) if you want.

This is just a simple desktop app project only for demonstrating the ability of Electron. If you want to improve it, feel free to create an issue or PR.