Build native applications using Angular and Electron

Build native applications  using Angular and Electron

Build a Native Desktop App with Angular and Electron. Build native applications for Windows, Linux, and Mac using Angular and Electron

Electron is a JavaScript framework that allows you to build cross-platform apps by converting a web app into a native app for the supported platforms. This provides a convenient way for web developers to write software for the platform that they are not familiar with by using web technologies. By using Node.js, it supports some native functionality like file manipulation and hardware interaction. Electron has been getting more popular over time, and now it is used for companies like Slack and Microsoft to develop their popular apps.

Angular is a popular framework for building web apps, and it has a powerful CLI that makes the building process seamless. However, it does not support building an app into an Electron without some changes. There is an Angular Electron boilerplate repository that combines the 2, making it easy for you to get started.

In this story, we will build an Electron app based on Angular which gets news headlines from the New York Times API, located at https://developer.nytimes.com/. To use it, you have to register for a free API key.

The API supports CORS, so front-end apps from domains outside of nytimes.com can access their APIs. This means that we can build an Angular app with it. To build our app, you have to go to the website and register for an API key.

To start building the app, we install the Angular CLI by running npm i -g @angular/cli. After that, instead of running ng new to create the project, we check out the angular-electron repo located at https://github.com/maximegris/angular-electron.git and use the latest version on the master branch.

Next, copy the code into your own project folder. Now we can start building the app. To start, we rename the app name to new-york-times. The renaming has to be done in multiple files: electron-builer.json, package.json, angular.json.

In electron-builder.json, we replace the existing code with the following:

{
  "productName": "new-york-times",
  "directories": {
    "output": "release/"
  },
    "files": [
        "**/*",
        "!**/*.ts",
        "!*.code-workspace",
        "!LICENSE.md",
        "!package.json",
        "!package-lock.json",
        "!src/",
        "!e2e/",
        "!hooks/",
        "!angular.json",
        "!_config.yml",
        "!karma.conf.js",
        "!tsconfig.json",
        "!tslint.json"
    ],
  "win": {
    "icon": "dist",
    "target": [
      "portable"
    ]
  },
  "mac": {
    "icon": "dist",
    "target": [
      "dmg"
    ]
  },
  "linux": {
    "icon": "dist",
    "target": [
      "AppImage"
    ]
  }
}

Note the productName’s value is different from the original.

Then in package.json, we replace the existing code with:

{
  "name": "new-york-times",
  "version": "1.0.0",
  "description": "Angular 8 with Electron (Typescript + SASS + Hot Reload)",
  "homepage": "https://github.com/maximegris/angular-electron",
  "author": {
    "name": "Maxime GRIS",
    "email": "[email protected]"
  },
  "keywords": [
    "angular",
    "angular 8",
    "electron",
    "typescript",
    "sass"
  ],
  "main": "main.js",
  "private": true,
  "scripts": {
    "postinstall": "npm run postinstall:electron && electron-builder install-app-deps",
    "postinstall:web": "node postinstall-web",
    "postinstall:electron": "node postinstall",
    "ng": "ng",
    "start": "npm run postinstall:electron && npm-run-all -p ng:serve electron:serve",
    "build": "npm run postinstall:electron && npm run electron:serve-tsc && ng build --base-href ./",
    "build:dev": "npm run build -- -c dev",
    "build:prod": "npm run build -- -c production",
    "ng:serve": "ng serve",
    "ng:serve:web": "npm run postinstall:web && ng serve -o",
    "electron:serve-tsc": "tsc -p tsconfig-serve.json",
    "electron:serve": "wait-on http-get://localhost:4200/ && npm run electron:serve-tsc && electron . --serve",
    "electron:local": "npm run build:prod && electron .",
    "electron:linux": "npm run build:prod && electron-builder build --linux",
    "electron:windows": "npm run build:prod && electron-builder build --windows",
    "electron:mac": "npm run build:prod && electron-builder build --mac",
    "test": "npm run postinstall:web && ng test",
    "e2e": "npm run build:prod && mocha --timeout 300000 --require ts-node/register e2e/**/*.spec.ts",
    "version": "conventional-changelog -i CHANGELOG.md -s -r 0 && git add CHANGELOG.md",
    "lint": "ng lint"
  },
  "devDependencies": {
    "@angular-devkit/build-angular": "0.802.2",
    "@angular/cli": "8.2.2",
    "@angular/common": "8.2.2",
    "@angular/compiler": "8.2.2",
    "@angular/compiler-cli": "8.2.2",
    "@angular/core": "8.2.2",
    "@angular/forms": "8.2.2",
    "@angular/language-service": "8.2.2",
    "@angular/platform-browser": "8.2.2",
    "@angular/platform-browser-dynamic": "8.2.2",
    "@angular/router": "8.2.2",
    "@ngx-translate/core": "11.0.1",
    "@ngx-translate/http-loader": "4.0.0",
    "@types/jasmine": "3.3.16",
    "@types/jasminewd2": "2.0.6",
    "@types/mocha": "5.2.7",
    "@types/node": "12.6.8",
    "chai": "4.2.0",
    "codelyzer": "5.1.0",
    "conventional-changelog-cli": "2.0.21",
    "core-js": "3.1.4",
    "electron": "6.0.2",
    "electron-builder": "21.2.0",
    "electron-reload": "1.5.0",
    "jasmine-core": "3.4.0",
    "jasmine-spec-reporter": "4.2.1",
    "karma": "4.2.0",
    "karma-chrome-launcher": "3.0.0",
    "karma-coverage-istanbul-reporter": "2.1.0",
    "karma-jasmine": "2.0.1",
    "karma-jasmine-html-reporter": "1.4.2",
    "mocha": "6.2.0",
    "npm-run-all": "4.1.5",
    "rxjs": "6.5.2",
    "spectron": "8.0.0",
    "ts-node": "8.3.0",
    "tslint": "5.18.0",
    "typescript": "3.5.3",
    "wait-on": "3.3.0",
    "webdriver-manager": "12.1.5",
    "zone.js": "0.9.1"
  },
  "engines": {
    "node": ">=10.9.0"
  },
  "dependencies": {
    "@angular/animations": "^8.2.3",
    "@angular/cdk": "^8.1.3",
    "@angular/material": "^8.1.3",
    "@angular/material-moment-adapter": "^8.1.3",
    "@ngrx/store": "^8.2.0",
    "moment": "^2.24.0"
  }
}

Note that in the build script, we have --base-href ./ at the end. This is important because the styles and script will have the wrong path if we didn’t add it. The base-href is needed to specify that we tell the Electron web view to find the scripts and style files by their relative path instead of the absolute path.

We also included all the libraries we need like Angular Material, Moment.js, NGRX store, and the scripts for building and running our app. We changed the first key value pair to “name”: “new-york-times”.

After that, we do a similar renaming in angular.json by replacing what is there with the following:

{
  "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
  "version": 1,
  "newProjectRoot": "projects",
  "projects": {
    "new-york-times": {
      "root": "",
      "sourceRoot": "src",
      "projectType": "application",
      "architect": {
        "build": {
          "builder": "@angular-devkit/build-angular:browser",
          "options": {
            "outputPath": "dist",
            "index": "src/index.html",
            "main": "src/main.ts",
            "tsConfig": "src/tsconfig.app.json",
            "polyfills": "src/polyfills.ts",
            "assets": [
              "src/assets",
              "src/favicon.ico",
              "src/favicon.png",
              "src/favicon.icns",
              "src/favicon.256x256.png",
              "src/favicon.512x512.png"
            ],
            "styles": [
              "src/styles.scss"
            ],
            "scripts": []
          },
          "configurations": {
            "dev": {
              "optimization": false,
              "outputHashing": "all",
              "sourceMap": true,
              "extractCss": true,
              "namedChunks": false,
              "aot": false,
              "extractLicenses": true,
              "vendorChunk": false,
              "buildOptimizer": false,
              "fileReplacements": [
                {
                  "replace": "src/environments/environment.ts",
                  "with": "src/environments/environment.dev.ts"
                }
              ]
            },
            "production": {
              "optimization": true,
              "outputHashing": "all",
              "sourceMap": false,
              "extractCss": true,
              "namedChunks": false,
              "aot": true,
              "extractLicenses": true,
              "vendorChunk": false,
              "buildOptimizer": true,
              "fileReplacements": [
                {
                  "replace": "src/environments/environment.ts",
                  "with": "src/environments/environment.prod.ts"
                }
              ]
            }
          }
        },
        "serve": {
          "builder": "@angular-devkit/build-angular:dev-server",
          "options": {
            "browserTarget": "new-york-times:build"
          },
          "configurations": {
            "dev": {
              "browserTarget": "new-york-times:build:dev"
            },
            "production": {
              "browserTarget": "new-york-times:build:production"
            }
          }
        },
        "extract-i18n": {
          "builder": "@angular-devkit/build-angular:extract-i18n",
          "options": {
            "browserTarget": "new-york-times:build"
          }
        },
        "test": {
          "builder": "@angular-devkit/build-angular:karma",
          "options": {
            "main": "src/test.ts",
            "polyfills": "src/polyfills-test.ts",
            "tsConfig": "src/tsconfig.spec.json",
            "karmaConfig": "src/karma.conf.js",
            "scripts": [],
            "styles": [
              "src/styles.scss"
            ],
            "assets": [
              "src/assets",
              "src/favicon.ico",
              "src/favicon.png",
              "src/favicon.icns",
              "src/favicon.256x256.png",
              "src/favicon.512x512.png"
            ]
          }
        },
        "lint": {
          "builder": "@angular-devkit/build-angular:tslint",
          "options": {
            "tsConfig": [
              "src/tsconfig.app.json",
              "src/tsconfig.spec.json"
            ],
            "exclude": [
              "**/node_modules/**"
            ]
          }
        }
      }
    },
    "new-york-times-e2e": {
      "root": "e2e",
      "projectType": "application",
      "architect": {
        "lint": {
          "builder": "@angular-devkit/build-angular:tslint",
          "options": {
            "tsConfig": [
              "e2e/tsconfig.e2e.json"
            ],
            "exclude": [
              "**/node_modules/**"
            ]
          }
        }
      }
    }
  },
  "defaultProject": "new-york-times",
  "schematics": {
    "@schematics/angular:component": {
      "prefix": "app",
      "styleext": "scss"
    },
    "@schematics/angular:directive": {
      "prefix": "app"
    }
  }
}

We renamed all instances of angular-electron with new-york-times in this file.

Next, in environment.ts, environment.prod.ts, and environment.dev.ts, we put

apikey: 'new york times api key',
apiUrl: 'https://api.nytimes.com/svc'

into the AppConfig object. Replace the New York Times API key with your own.

In index.html, we change it to:

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>New York Times</title>
  <base href="/">
  <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
  <link href="https://fonts.googleapis.com/css?family=Roboto&display=swap" rel="stylesheet">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
  <app-root></app-root>
</body>
</html>

The text between the <title> tags will be the title that will be displayed in our app.

Also, we need to install the packages listed in package.json. We do this by running npm i. Next we run ng add @ngrx/store to add the boilerplate of @ngrx/store flux store into our app.

With that completed, we can begin building the app. To start, we run the following commands:

$ ng g component homePage
$ ng g component articleSearchPage
$ ng g component articleSearchResults
$ ng g component toolBar
$ ng g class menuReducer
$ ng g class searchResultsReducer
$ ng g service nyt

These commands will create the files that we need for our app. Now we can include the library modules into our main app module. To do this, we put the following into app.module.ts:

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { HomePageComponent } from './home-page/home-page.component';
import { ArticleSearchPageComponent } from './article-search-page/article-search-page.component';
import { ArticleSearchResultsComponent } from './article-search-results/article-search-results.component';
import { StoreModule } from '@ngrx/store';
import { reducers } from './reducers';
import { NytService } from './nyt.service';
import { MatSidenavModule } from '@angular/material/sidenav';
import { MatToolbarModule } from '@angular/material/toolbar';
import { MatInputModule } from '@angular/material/input';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatDatepickerModule } from '@angular/material/datepicker';
import { ToolBarComponent } from './tool-bar/tool-bar.component';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { MatButtonModule } from '@angular/material/button';
import { MatMomentDateModule } from '@angular/material-moment-adapter';
import { HttpClientModule } from '@angular/common/http';
import { MatSelectModule } from '@angular/material/select';
import { MatCardModule } from '@angular/material/card';
import { MatListModule } from '@angular/material/list';
import { MatMenuModule } from '@angular/material/menu';
import { MatIconModule } from '@angular/material/icon';
import { MatGridListModule } from '@angular/material/grid-list';
@NgModule({
  declarations: [
    AppComponent,
    HomePageComponent,
    ArticleSearchPageComponent,
    ArticleSearchResultsComponent,
    ToolBarComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    StoreModule.forRoot(reducers),
    FormsModule,
    MatSidenavModule,
    MatToolbarModule,
    MatInputModule,
    MatFormFieldModule,
    MatDatepickerModule,
    BrowserAnimationsModule,
    MatButtonModule,
    MatMomentDateModule,
    HttpClientModule,
    MatSelectModule,
    MatCardModule,
    MatListModule,
    MatMenuModule,
    MatIconModule,
    MatGridListModule
  ],
  providers: [
    NytService
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

Most of the modules in the import array are Angular Material modules. We will use them throughout the app.

Now we have to create the part of the app where we get and store data. To do this, run:

$ ng g service nyt

This is where we make our HTTP calls to the New York Times API. Now we should have a file called nyt.service.ts. In there, we put:

import { Injectable } from '@angular/core';
import { HttpClient, HttpParams } from '@angular/common/http';
import { environment } from 'src/environments/environment';
@Injectable({
  providedIn: 'root'
})
export class NytService {
constructor(
    private http: HttpClient
  ) { }
search(data) {
    let params: HttpParams = new HttpParams();
    params = params.set('api-key', environment.apikey);
    if (data.q !== undefined) {
      params = params.set('q', data.q);
    }
    if (data.begin_date !== undefined) {
      params = params.set('begin_date', data.begin_date);
    }
    if (data.end_date !== undefined) {
      params = params.set('end_date', data.end_date);
    }
    if (data.sort !== undefined) {
      params = params.set('sort', data.sort);
    }
    return this.http.get(`${environment.apiUrl}/search/v2/articlesearch.json`, { params });
  }
getArticles(section: string = 'home') {
    let params: HttpParams = new HttpParams();
    params = params.set('api-key', environment.apikey);
return this.http.get(`${environment.apiUrl}/topstories/v2/${section}.json`, { params });
  }
}

The search function takes the data we will pass in, and if it is defined, then it will be included in the GET request’s query string. The second parameter in the this.http.get function takes a variety of options, including headers and query parameters. HttpParams objects are converted to query strings when the code is executed. getArticles does similar things as the search function except with a different URL.

Then in environment.ts, we put:

export const environment = {
  production: false,
  apikey: 'your api key',
  apiUrl: 'https://api.nytimes.com/svc'
};

This makes the URL and API key referenced in the service file available.

Next, we need to add a Flux data store to persist our menu state and search results. First we have to run:

$ ng add @ngrx/store

This adds the boilerplate code to the Flux store. Then, we run:

$ ng g class menuReducer
$ ng g class searchResultsReducer

We execute this in the src\app\reducers folder, which was created after running ng add @ngrx/store to make the files for our reducers.

Then in menu-reducer.ts, we put:

export const SET_MENU_STATE = 'SET_MENU_STATE';
export function MenuReducer(state: boolean, action) {
    switch (action.type) {
        case SET_MENU_STATE:
            return action.payload;
default:
            return state;
    }
}

And in search-result-reducer.ts, we put:

export const SET_SEARCH_RESULT = 'SET_SEARCH_RESULT';
export function SearchResultReducer(state, action) {
    switch (action.type) {
        case SET_SEARCH_RESULT:
            return action.payload;
default:
            return state;
    }
}

These two pieces of code will allow the menu and search results to be stored in memory and be propagated to components that subscribe to the data.

Next in src\app\reducers\index.ts, we put:

import { SearchResultReducer } from './search-results-reducer';
import { MenuReducer } from './menu-reducer';
export const reducers = {
  searchResults: SearchResultReducer,
  menuState: MenuReducer
};

This will allow our module to access our reducers since we have StoreModule.forRoot(reducers) in app.module.ts.

Now we’ll work on our app’s toolbar. To make the toolbar, we put the following in tool-bar.component.ts:

import { Component, OnInit } from '@angular/core';
import { Store, select } from '@ngrx/store';
import { SET_MENU_STATE } from '../reducers/menu-reducer';
@Component({
  selector: 'app-tool-bar',
  templateUrl: './tool-bar.component.html',
  styleUrls: ['./tool-bar.component.scss']
})
export class ToolBarComponent implements OnInit {
  menuOpen: boolean;
constructor(
    private store: Store<any>
  ) {
    store.pipe(select('menuState'))
      .subscribe(menuOpen => {
        this.menuOpen = menuOpen;
      })
  }
ngOnInit() {
  }
toggleMenu() {
    this.store.dispatch({ type: SET_MENU_STATE, payload: !this.menuOpen });
  }
}
this.store.dispatch({ type: SET_MENU_STATE, payload: !this.menuOpen });

This sends the state of the menu to the rest of the app.

store.pipe(select('menuState'))
  .subscribe(menuOpen => {
    this.menuOpen = menuOpen;
  })

The above gets the state of the menu and is used for displaying and toggling the menu state.

In the corresponding template, tool-bar.component.html, we put:

<mat-toolbar>
    <a (click)='toggleMenu()' class="menu-button">
        <i class="material-icons">
            menu
        </i>
    </a>
    New York Times App
</mat-toolbar>

And in tool-bar.component.scss, we put:

.menu-button {
  margin-top: 6px;
  margin-right: 10px;
  cursor: pointer;
}
.mat-toolbar {
  background: #009688;
  color: white;
}

In app.component.scss, we put:

#content {
  padding: 20px;
  min-height: 100vh;
}
ul {
  list-style-type: none;
  margin: 0;
  li {
    padding: 20px 5px;
  }
}

This changes the color of the toolbar.

Then in app.component.ts, we put:

import { Component, HostListener } from '@angular/core';
import { SET_MENU_STATE } from './reducers/menu-reducer';
import { Store, select } from '@ngrx/store';
@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent {
  menuOpen: boolean;
constructor(
    private store: Store<any>,
  ) {
    store.pipe(select('menuState'))
      .subscribe(menuOpen => {
        this.menuOpen = menuOpen;
      })
  }
@HostListener('document:click', ['$event'])
  public onClick(event) {
    const isOutside = !event.target.className.includes("menu-button") &&
      !event.target.className.includes("material-icons") &&
      !event.target.className.includes("mat-drawer-inner-container")
    if (isOutside) {
      this.menuOpen = false;
      this.store.dispatch({ type: SET_MENU_STATE, payload: this.menuOpen });
    }
  }
}

This makes it so when we click outside of the left side menu, it’ll be closed.

In app.component.html, we put:

<mat-sidenav-container class="example-container">
    <mat-sidenav mode="side" [opened]='menuOpen'>
        <ul>
            <li>
                <b>
                    New York Times
                </b>
            </li>
            <li>
                <a routerLink='/'>Home</a>
            </li>
            <li>
                <a routerLink='/search'>Search</a>
            </li>
        </ul>
</mat-sidenav>
    <mat-sidenav-content>
        <app-tool-bar></app-tool-bar>
        <div id='content'>
            <router-outlet></router-outlet>
        </div>
    </mat-sidenav-content>
</mat-sidenav-container>

This displays our left side menu and routes.

In style.scss, we put:

/* You can add global styles to this file, and also import other style files */
@import "[email protected]/material/prebuilt-themes/indigo-pink.css";
body {
  font-family: "Roboto", sans-serif;
  margin: 0;
}
form {
  mat-form-field {
    width: 95vw;
    margin: 0 auto;
  }
}
.center {
  text-align: center;
}

This imports the Material Design styles and sets the width of our forms.

Then in app-routing.module.ts, we put the following so we can see the pages we made when we go the specified URLs:

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { HomePageComponent } from './home-page/home-page.component';
import { ArticleSearchPageComponent } from './article-search-page/article-search-page.component';
const routes: Routes = [
  { path: '', component: HomePageComponent },
  { path: 'search', component: ArticleSearchPageComponent }
];
@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

In index.html, we put:

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>New York Time</title>
  <base href="/">
  <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
  <link href="https://fonts.googleapis.com/css?family=Roboto&display=swap" rel="stylesheet">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
  <app-root></app-root>
</body>
</html>

This includes the Roboto font commonly used with Material Design and Material Icons.

Now we build the logic for our two pages. First we start with our home page. In home-page.component.html, we put:

import { Component, OnInit } from '@angular/core';
import { NytService } from '../nyt.service';
@Component({
  selector: 'app-home-page',
  templateUrl: './home-page.component.html',
  styleUrls: ['./home-page.component.scss']
})
export class HomePageComponent implements OnInit {
  sections: string[] =
    `arts, automobiles, books, business, fashion, food, health,
    home, insider, magazine, movies, national, nyregion, obituaries,
    opinion, politics, realestate, science, sports, sundayreview,
    technology, theater, tmagazine, travel, upshot, world`
      .replace(/ /g, '')
      .split(',');
  results: any[] = [];
  selectedSection: string = 'home';
constructor(
    private nytService: NytService
  ) { }
ngOnInit() {
    this.getArticles();
  }
getArticles() {
    this.nytService.getArticles(this.selectedSection)
      .subscribe(res => {
        this.results = (res as any).results;
      })
  }
}

We get the articles on the first load with the ngOnInit function, and then once the page is loaded, we can choose which section to load.

In home-page.component.html, we put:

<div class="center">
    <h1>{{selectedSection | titlecase }}</h1>
    <mat-menu #appMenu="matMenu">
        <button mat-menu-item *ngFor='let s of sections' (click)='selectedSection = s; getArticles()'>{{s | titlecase }}
        </button>
    </mat-menu>
<button mat-raised-button [matMenuTriggerFor]="appMenu">
        Sections
    </button>
</div>
<br>
<mat-card *ngFor='let r of results'>
    <mat-list role="list">
        <mat-list-item>
            <mat-card-title>
                {{r.title}}
            </mat-card-title>
        </mat-list-item>
    </mat-list>
    <mat-card-subtitle>
        <mat-list role="list">
            <mat-list-item>Published Date: {{r.published_date | date: 'full' }}</mat-list-item>
            <mat-list-item><a href='{{r.url}}'>Link</a></mat-list-item>
            <mat-list-item *ngIf='r.byline'>{{r.byline}}</mat-list-item>
        </mat-list>
    </mat-card-subtitle>
    <mat-card-content>
        <mat-list role="list">
            <mat-list-item>{{r.abstract}}</mat-list-item>
        </mat-list>
        <img *ngIf='r.multimedia[r.multimedia.length - 1]?.url' [src]='r.multimedia[r.multimedia.length - 1]?.url'
            [alt]='r.multimedia[r.multimedia.length - 1]?.caption' class="image">
    </mat-card-content>
</mat-card>

This is where we display the results from the New York Times API, including headline titles, pictures, publication date, and other data. | titlecase is called a pipe. It maps the object to the left of the pipe symbol by calling the function on the right.

The titlecase pipe for example, converts a string to a title-case string. Pipes can also take arguments like date: ‘full’ to set options that can used with the pipe.

The code on the top of the file is where we enable users to select the section they want to load by letting them choose the section they want to load. It’s here:

<div class="center">
    <h1>{{selectedSection | titlecase }}</h1>
    <mat-menu #appMenu="matMenu">
        <button mat-menu-item *ngFor='let s of sections' (click)='selectedSection = s; getArticles()'>{{s | titlecase }}
        </button>
    </mat-menu>
<button mat-raised-button [matMenuTriggerFor]="appMenu">
        Sections
    </button>
</div>
<br>

This block splits the string into an array of strings with those names and without the spaces:

sections: string[] =
    `arts, automobiles, books, business, fashion, food, health,
    home, insider, magazine, movies, national, nyregion, obituaries,
    opinion, politics, realestate, science, sports, sundayreview,
    technology, theater, tmagazine, travel, upshot, world`
      .replace(/ /g, '')
      .split(',');

Then in home-page.component.scss, we put:

.image {
  width: 100%;
  margin-top: 30px;
}

This styles the pictures displayed.

Next we build the page to search for articles. It has a form and a space to display the results.

In article-search.component.ts, we put:

import { Component, OnInit } from '@angular/core';
import { SearchData } from '../search-data';
import { NgForm } from '@angular/forms';
import { NytService } from '../nyt.service';
import * as moment from 'moment';
import { Store } from '@ngrx/store';
import { SET_SEARCH_RESULT } from '../reducers/search-results-reducer';
@Component({
  selector: 'app-article-search-page',
  templateUrl: './article-search-page.component.html',
  styleUrls: ['./article-search-page.component.scss']
})
export class ArticleSearchPageComponent implements OnInit {
  searchData: SearchData = <SearchData>{
    sort: 'newest'
  };
  today: Date = new Date();
constructor(
    private nytService: NytService,
    private store: Store<any>
  ) {
}
ngOnInit() {
  }
search(searchForm: NgForm) {
    if (searchForm.invalid) {
      return;
    }
    const data: any = {
      begin_date: moment(this.searchData.begin_date).format('YYYYMMDD'),
      end_date: moment(this.searchData.end_date).format('YYYYMMDD'),
      q: this.searchData.q
    }
    this.nytService.search(data)
      .subscribe(res => {
        this.store.dispatch({ type: SET_SEARCH_RESULT, payload: (res as any).response.docs });
      })
  }
}

This gets the data when we click search and propagates the results to the Flux store, which will be used to display the data at the end.

In article-search.component.html, we put:

<div class="center">
    <h1>Search</h1>
</div>
<br>
<form #searchForm='ngForm' (ngSubmit)='search(searchForm)'>
    <mat-form-field>
        <input matInput placeholder="Keyword" required #keyword='ngModel' name='keyword' [(ngModel)]='searchData.q'>
        <mat-error *ngIf="keyword.invalid && (keyword.dirty || keyword.touched)">
            <div *ngIf="keyword.errors.required">
                Keyword is required.
            </div>
        </mat-error>
    </mat-form-field>
    <br>
    <mat-form-field>
        <input matInput [matDatepicker]="startDatePicker" placeholder="Start Date" [max]="today" #startDate='ngModel'
            name='startDate' [(ngModel)]='searchData.begin_date'>
        <mat-datepicker-toggle matSuffix [for]="startDatePicker"></mat-datepicker-toggle>
        <mat-datepicker #startDatePicker></mat-datepicker>
    </mat-form-field>
    <br>
    <mat-form-field>
        <input matInput [matDatepicker]="endDatePicker" placeholder="End Date" [max]="today" #endDate='ngModel'
            name='endDate' [(ngModel)]='searchData.end_date'>
        <mat-datepicker-toggle matSuffix [for]="endDatePicker"></mat-datepicker-toggle>
        <mat-datepicker #endDatePicker></mat-datepicker>
    </mat-form-field>
    <br>
    <mat-form-field>
        <mat-label>Sort By</mat-label>
        <mat-select required [(value)]="searchData.sort">
            <mat-option value="newest">Newest</mat-option>
            <mat-option value="oldest">Oldest</mat-option>
            <mat-option value="relevance">Relevance</mat-option>
        </mat-select>
    </mat-form-field>
    <br>
    <button mat-raised-button type='submit'>Search</button>
</form>
<br>
<app-article-search-results></app-article-search-results>

This is the search form for the articles. It includes a keyword field, start and end date datepickers, and a drop-down to select the way to sort. These are all Angular Material components. <app-article-search-results></app-article-search-results> is the article search result component which we generated but have not built yet.

Note that the [( in [(ngModel)] denotes two-way data binding between the component or directive, and the current component and [ denote one-way binding from the current component to the directive or component.

Next in article-search.results.ts, we put:

import { Component, OnInit } from '@angular/core';
import { Store, select } from '@ngrx/store';
@Component({
  selector: 'app-article-search-results',
  templateUrl: './article-search-results.component.html',
  styleUrls: ['./article-search-results.component.scss']
})
export class ArticleSearchResultsComponent implements OnInit {
  searchResults: any[] = [];
constructor(
    private store: Store<any>
  ) {
    store.pipe(select('searchResults'))
      .subscribe(searchResults => {
        this.searchResults = searchResults;
      })
  }
ngOnInit() {
  }
}

This block gets the article search results stored in our Flux store and sends it to our template for displaying:

store.pipe(select('searchResults'))
      .subscribe(searchResults => {
        this.searchResults = searchResults;
      })

In article-search.results.html, we put:

<mat-card *ngFor='let s of searchResults'>
    <mat-list role="list">
        <mat-list-item>
            <mat-card-title>
                {{s.headline.main}}
            </mat-card-title>
        </mat-list-item>
    </mat-list>
    <mat-card-subtitle>
        <mat-list role="list">
            <mat-list-item>Date: {{s.pub_date | date: 'full' }}</mat-list-item>
            <mat-list-item><a href='{{s.web_url}}'>Link</a></mat-list-item>
            <mat-list-item *ngIf='s.byline.original'>{{s.byline.original}}</mat-list-item>
        </mat-list>
    </mat-card-subtitle>
    <mat-card-content>
        <div class="content">
            <p>{{s.lead_paragraph}}</p>
            <p>{{s.snippet}}</p>
        </div>
    </mat-card-content>
</mat-card>

This just displays the results from the store.

In article-search-results.component.scss, we add:

.content {
  padding: 0px 15px;
}

This adds some padding to the paragraphs.

With the logic done, we just need to replace the code in core.module.ts, with the following:

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { AppModule } from '../app.module';
@NgModule({
  declarations: [],
  imports: [
    CommonModule,
    AppModule
  ]
})
export class CoreModule { }

And in shared.module.ts, we put:

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { TranslateModule } from '@ngx-translate/core';
import { WebviewDirective } from './directives/';
import { PageNotFoundComponent } from './components';
@NgModule({
  declarations: [ WebviewDirective, PageNotFoundComponent],
  imports: [CommonModule, TranslateModule],
  exports: [TranslateModule, WebviewDirective]
})
export class SharedModule {}

to include the PageNotFoundComponent in the module so that the build will proceed.

After adding all the code, we can run npm run ng:serve:web to preview the app in a browser, and to preview it as an Electron, we run npm run electron:local .

Finally, to build the app into a Windows executable, run npm run electron:windows . The executable should be located in the root of the release folder in the project folder.

At the end, we have the following:

Node vs Angular : Comparing Two Strong JavaScript Technologies

Just from being a simple client-side scripting language, JavaScript has evolved over the years to turn out to be a powerful programming language. Here Node.js is a cross-platform runtime environment while AngularJS is one of the top JavaScript framework. Angular helps the developers to build web applications which are dynamic in nature using HTML template language and following the MVC design pattern.

An Angular Roadmap — The Past, Present, and Future of Angular

An Angular Roadmap — The Past, Present, and Future of Angular

✅Interested in being an Angular developer in 2019? ... blog post it's most likely that you've written some code in javaScript in the past.

Paleolithic JavaScript — SproutCore

In the beginning, there was SproutCore. It was the first comprehensive JavaScript framework aimed at making it easy to build desktop-quality single-page web apps. It’s not that this wasn’t possible before. When Google released Gmail, it showed the world that web apps really could replace complex desktop applications. Google even open-sourced the Closure toolkit — a set of libraries and an optimizing compiler that it used to build Gmail.

The problem was that Google’s Closure tools weren’t very developer-friendly. They relied heavily on Java, which alienated web developers who were used to working with JavaScript, PHP, Ruby, and Python. Gmail was a great demonstration of what was possible, but developing similar applications still felt out of reach for many.

Some courageous developers managed to string together amazing single page apps using a combination of jQuery, duct tape, and hope. While these apps looked amazing to end-users, for the developers working on them, the apps quickly turned into hulking piles of technical debt that made the dev team dread heading to work in the morning.

As a result, a few enterprising developers began to work on frameworks that would bring Gmail-like apps within easy reach of web developers everywhere. SproutCore was the first of these frameworks to take off. It came with a complete set of widgets that made it possible to build complex web applications without even touching HTML or CSS.

This ended up being great for former desktop developers who had been dragged kicking and screaming onto the web. Several more frameworks popped up with similar goals; GWT and Cappuccino were the most prominent. These frameworks even avoided JavaScript by transpiling other languages into JS. Again, this was great for desktop developers. But it left passionate web developers out in the cold and made them feel as though their hard-won HTML, CSS, and JavaScript skills weren’t valuable.

This left an opening for a framework that truly embraced the web, instead of trying to plaster over it and pretend it was something else. A couple of early frameworks (Backbone and Knockout) appeared, and achieved a moderate amount of success. Ember also showed up around this time. It took SproutCore, stripped it down to its bones, and tried to rebuild it into something truly web-friendly. Ember wanted to be the Six Million Dollar Man of the JavaScript world: rebuilt better, stronger, and faster.

None of these frameworks rocketed to popularity. The world was waiting for something better. In 2010, that something better appeared — it was named Angular.

The Golden Age of Angular

Even before Angular version 1.0 had been released, Angular took the front-end development world by storm. Finally, we had an easy-to-use JavaScript framework that treated HTML as a first-class citizen. Developers and designers could now work together to build amazing single-page applications. This came as a relief to designers, who had been annoyed and offended because older frameworks had treated HTML and CSS as tools for barbarians, tools that no civilized developer should have to touch.

The first thing that seemed magical to developers trying Angular for the first time was two-way data binding. Prior to this, most developers had only seen this kind of data binding in desktop frameworks like WPF and Windows Forms. It was great to be able to bind forms and other UI elements to JavaScript model objects. While two-way data binding could cause performance problems when overused, teams that used it judiciously found that Angular enabled them to create complex front-end applications much more quickly than ever before.

Angular proved to be popular for more than just easy binding of data to HTML elements. Angular directives provided an easy way to create reusable HTML + CSS components. Although other JavaScript frameworks provided this before Angular, Angular was the first one that became extremely popular. Reusable components had long been in-use in server-side frameworks. ASP.NET user controls and partial templates in Rails and Django are but a few examples.

Finally, Angular made dependency injection popular in the front-end development world. Dependency injection had long been popular in enterprise applications, which is perhaps why it hadn’t caught on in the JavaScript world. Front-end developers have long been averse to what they see as needlessly complex enterprise software design patterns. This concern isn’t without merit. Have you ever, in the course of writing an application, said to yourself “What I really need here is a “SimpleBeanFactoryAwareAspectInstanceFactory?”

Dependency injection, though, has proven its worth. And Angular made dependency injection easy to use for an audience that hadn’t used it much in the past. Need an HTTP client? Or perhaps you’d like to do some animation? No problem. Angular had built-in services for those. Just ask for them, and Angular would inject them into your components. No need to instantiate anything yourself.

Or perhaps you wanted to use the browser’s window or location objects without making it impossible to unit test your components outside of a browser? Angular had you covered there too, with its built-in $window and $location services. At runtime, you’d get the browser objects you were expecting. And when running unit tests server-side in Node.js, you could pass mock services into your components to ensure they behaved as expected in various scenarios.

If all of this wasn’t enough, Angular also made it simple to register and inject your own services. For developers who were used to binding all their data to the DOM and hoping for the best, this was awesome. If you were writing a new front-end app that called for APIs that would cost your company a lot of money if overused, you’d probably prefer to be able to write tests ahead of time to ensure that your application doesn’t try to do something like calling the Twilio API 800 million times.

So you’d create a Twilio service that gets injected at runtime. At testing time, you’d create a mock service that records the cost of every API call your application is trying to make. You’d write tests that cover common usage scenarios and ensure that these scenarios don’t result in your company receiving a 7-figure bill. Overall, most developers found that Angular directives combined with dependency injection made it possible to write modular, testable front-end applications using tried-and-true software engineering techniques. Many development teams decided that this resulted in a happy state of affairs, and decided to go all-in on Angular.

The Angular Decline? The Rise of React

While things were mostly great in the world of Angular, it wasn’t all sunshine and lollipops. Developers were starting to run into severe performance problems when they tried to bind too many model objects to too many DOM elements. Some applications slowed to a crawl. Direct calls to $digest and other black-magic sorcery started becoming necessary to achieve acceptable performance. Around the same time, a new challenger appeared: React. At first, React didn’t seem to pose too large a danger to Angular. After all, these React weirdos had gone to the trouble of inventing JSX, which looked a lot like a way to mix markup into your code. Hadn’t we gone to a lot of trouble to invent templating languages for the explicit reason of avoiding mixing markup and code?

As it turned out, React’s approach was pretty popular in the front-end development community. It didn’t rocket to popularity, however. Angular was still dominant, and it looked like it would remain that way. Until that is, Angular’s popularity was given a good kick in the teeth from an unexpected source: the Angular team itself.

The Introduction of Angular 2

Angular 2 was first announced at the ng-europe conference in 2014. The Angular team’s plans came as a shock to the Angular community, to say the least. Reaction from Angular developers was swift and negative… and not without reason. Angular 2 would be getting rid of many familiar concepts from Angular 1, introducing a new, incompatible templating language (and oh, by the way) would also be programmed using an entirely new language.

AngularJS

Although both Angular 1 and Angular 2 were called ‘Angular,’ in reality, they were very different frameworks with a few things in common. To help prevent confusion, the Angular team started referring to the old version of Angular as ‘AngularJS’, and the new version as simply ‘Angular.’ This makes intuitive sense since AngularJS was written in JavaScript, and Angular was not. To keep the distinction between the frameworks clear, I’ll be referring to Angular 1 as AngularJS from this point forward.

As a result of all of this, AngularJS developers lost faith in the framework’s future. They threatened to move to a new framework on future projects, and that is precisely what many of them did. React was the biggest beneficiary of the mass exodus from AngularJS.

Although React didn’t do as much as AngularJS, in a way that was positive. If you’re using a view library that doesn’t try to include everything plus the kitchen sink, it’s a lot more difficult for the developers of that library to pull the rug out from under you in the future. In the beginning, using React was a bit of a pain compared to AngularJS. You had to cobble together a patchwork of libraries just to cover the functionality the AngularJS provided out of the box.

Many teams saw this as a good way to reduce risk, because it was unlikely that the developers of all of those libraries would decide to make backward incompatible breaking changes at the same time, which is essentially what Angular had done.

The Emergence of Vue

To compound AngularJS’ woes, another framework named Vue showed up at about the same time the drama over Angular 2 was occurring. Vue was inspired by AngularJS but aimed to simplify it and get rid of what Vue’s creator saw as unnecessary complexity (so Vue felt very familiar to existing AngularJS developers). Vue provided a safe haven for many AngularJS developers who didn’t want to move over to React.

This doesn’t mean that AngularJS developers were not waiting patiently for Angular 2 to appear. But it’s clear that there was a mass exodus from AngularJS to React and Vue due to the uncertainty caused by the plans for Angular 2.

Rising From the Ashes with Angular 2

Eventually, Angular 2 was released. As expected, it did away with many familiar concepts from AngularJS but kept a few of the best pieces like services and dependency injection. Fortunately for the sanity of developers, Angular uses plain TypeScript and not a fork as originally planned.

To make things more confusing, the Angular team maintained a fork of the new framework that used the Dart programming language instead of TypeScript. Initially, the TypeScript and Dart versions were developed in sync, generated from a single code base. Eventually, the TS and Dart versions of Angular decided to go their separate ways, and Angular Dart now exists on its own.

Even with this confusion, Angular’s popularity began to increase again after the Angular 2 release. It happened slowly. As often occurs in software development, trends shifted. Developers realized that a big, batteries-included framework might actually be useful. After all, when your application grows large enough, you end up actually needing all of those batteries.

Enterprise developers, in particular, began moving back to Angular. This makes sense. Usually, when you start an enterprise web app, you know it is going to be complex. There’s no point in starting with a tiny MVP when you know from the beginning all 87 things your application is going to be expected to do.

Where’s Angular 3?

Although Angular 2 wasn’t perfect, many developers of complex web applications began to realize that the new-and-improved Angular was a good fit for their needs. Fortunately for them, Angular had some exciting improvements in store. More importantly, the Angular team demonstrated that it could consistently publish new versions of the framework with few breaking changes between versions

In a move that seemed odd at the time, the Angular team decided to skip version 3 entirely and move to version 4. This was done for good reason: the team working on Angular’s router package had already pushed ahead and released version 3, while the remainder of Angular was still at version 2.3. They decided to keep all Angular package versions in sync moving forward, and bumping everything up to version 4 for the next release was the easiest way to achieve this.

Angular 4

Angular 4 had some significant changes, including added ahead of time compilation, which resulted in small production JavaScript bundles and shorter application load time. Support for server-side rendering was added, which was a boost for teams that wanted to render their app ahead of time to improve initial load performance. Many other improvements were added throughout the framework, but upgrading apps from Angular 2 to 4 was quick and painless in most cases.

Angular 4.3 and Angular 5

Angular 4.3 added a new HTTP client that was easier to use than the old HTTP service. In Angular 5, the old HTTP service was deprecated and would be dropped in the next release. In spite of this inconvenience, there was relatively little grumbling because the upgrade in most cases was straightforward. Angular 5 also added better internationalization support and further build optimizations.

Angular 6 and 7

Angular 6 and 7 were disappointing to some developers. There were no large changes, but there were many small quality of life improvements, especially to the Angular CLI tooling. The decreasing number of visible changes isn’t an indication that the Angular team has stopped innovating. Instead, it shows that the framework is mature, so the development team is now free to do more work behind the scenes, fixing bugs and improving performance.

The stability of the framework since the release of Angular 2 has drawn some old-school AngularJS developers back into the Angular world. It has also attracted the attention of enterprise development teams. When you’re building enterprise apps that may live for decades it’s ideal to use a framework that gets new releases on a predictable schedule but doesn’t change too rapidly. A developer who had only used Angular 2 could be up and running and contributing to an Angular 7 app within minutes.

The Future of Angular

Angular 8 and Angular Ivy

And that brings us to today. As we’ve seen, Angular has come a long way. It has gone from loved by web developers to being reviled to being admired, although it isn’t yet loved again like it was in its early days.

On the horizon, we have Angular 8. A ton of work has been done in Angular 8 to make it easy to use with the Bazel build system, which is absolutely amazing news for all 3 developers who are using it outside of Google. More excitingly, though, the Angular team is hard at work on a new rendered called Angular Ivy. It’s intended to be a drop-in replacement for the current rendered. For the most part, current apps won’t need to make any changes to use Angular Ivy.

If Angular Ivy is a drop-in replacement, why should developers care? Two important reasons: speed, and bundle size — two very important concerns. For a few years, it seemed like web developers had gone a bit crazy. Teams were shipping JavaScript bundles that were 5MB, 10MB, or even larger, and thinking that there was no problem with this. After all, the applications worked perfectly on the developers’ i7-powered MacBook Pros so they should work fine for everyone, right?

Unfortunately, in the real world, not everyone is running the latest and greatest hardware. Hundreds of millions of people access the internet solely on older Android phones with slightly more processing power than a potato, through internet connections only a little faster than dial-up. For these users, a huge JavaScript bundle takes forever to load, and even longer for their device to parse and run. Even in less extreme cases, there are countless users around the world who aren’t using the latest and greatest hardware. For them, massive JavaScript apps are usable (but painful).

Angular Ivy Expectations

The Angular Ivy renderer will help in several ways:

  1. It is being written with an eye on efficiency, so it will accomplish the same tasks while executing far fewer CPU instructions. This will improve both the battery life and the sanity of users with less-than-powerful devices.
  2. The renderer will be written in a much more modular fashion that the current renderer. This will make it much more amenable to tree-shaking and dead code elimination. As a result, your production JavaScript bundle will include only the code that is needed to run your application, instead of bundling together everything plus the kitchen sink as often happens with the current rendered.
  3. In addition to the bundle-size reduction and improved rendering speed, Angular Ivy has another few quality-of-life enhancements for Angular developers. Rebuild times are significantly faster. So if you’re running your app in development mode and waiting for your changes to appear, you’re now going to be spending a lot less time waiting.
  4. Template-type checking is improved, which means you’ll catch more errors at compile time instead of at runtime. Runtime template bugs are annoying, because they either bite you during testing, or they bite your users when they’re trying to use your app.
  5. The Angular Ivy template compiler will generate code that is human readable, which the current View Engine compiler doesn’t do. This will come in handy when trying to track down tough template bugs.

The net result: smaller apps, faster apps, happier developers, and happier users.

Angular’s Past, Present, and Future

If you’ve been using Angular from its early days all the way until now, then congratulations! While there have been plenty of rough patches, we’ve ended up with a fast, modern framework that is fun to use.

If you were an AngularJS developer but moved on to React, Vue, or something else, I encourage you to give Angular another look. It’s worth your time, even if you decide to stick with what you’re using now.

And if you’ve never used Angular at all, why not give it a shot?

We’ve just been on a whirlwind tour through Angular’s past, present, and future. Without a doubt, it has been quite a ride. Regardless of your Angular background, I hope you’ve enjoyed the tour!

30s ad

Angular 2 Firebase - Build a Web App with Typescript

Angular 2 Demystified

Master Angular 2 - The No Nonsense Course

Complete Angular 7 - Ultimate Guide - with Real World App

A Quick Guide to Angular 7 in 4 Hours