Modern and Legacy JavaScript: Differential Loading with Angular 8 Tutorial

Modern and Legacy JavaScript: Differential Loading with Angular 8 Tutorial

In this JavaScript tutorial designed for Angular developers, we'll learn how Angular 8 by example makes differential loading to send separate modern and legacy JavaScript bundles to web browsers based on their capabilities thanks to the module and nomodule attributes and browserslist. Next, we'll introduce the various new features of modern JavaScript to Angular devs then finally we'll see how to include custom JavaScript code in our Angular apps.

Angular is an open-source, client-side JavaScript platform for building web and mobile apps.

Angular is written in TypeScript which is a superset language of JavaScript that gets eventually compiled to JavaScript before it can be executed by web browsers which can only understand JavaScript, CSS and HTML.

TypeScript includes all the features of JavaScript plus a plethora of its own features that take your productivity to the next level.

Before running your app in a web browser, you need to compile the TypeScript code into JavaScript by configuring the TypeScript compiler but fortunately you don't need to do any manual configurations if your are using the Angular CLI which takes care of all that.

Sending Separate Modern and Legacy Angular JavaScript Bundles to Browsers

The latest Angular 8 version makes use of a feature called differential loading that allows you to send the modern JS code (ES6+) to modern web browsers that support the latest features of JavaScript or the legacy code (ES5) to old browsers.

Note: Differential loading allows you to save about 7/20% in size for Angular apps in modern web browsers.

Modern browsers are able to recognize a module type in the HTML <script> tag and to ignore a nomodule attribute.

For example, let's take the following code:

<script src="dist/bundle1.js" type="module"></script>
<script src="dist/bundle2.js" nomodule></script>

A modern web browser will be able to load the dist/bundle1.js file but since an old legacy browser doesn't understand the module type, it will load the dist/bundle2.js file instead.

Installing Angular CLI 8

Before you can create an Angular 8 project, you must have a recent version of Node.JS and NPM installed on your machine.

Open a new command-line interface and run the following command:

$ npm install -g @angular/cli

Creating an Angular 8 Project

Next, create an Angular 8 project using the following command:

$ ng new angular-javascript-demo

You'll be prompted by the CLI if Would you like to add Angular routing? (y/N) and Which stylesheet format would you like to use?.

If you ahev an Angular 7 project, first you need to make sure to update to the latest version:

$ cd into__your_angular_project
$ ng update @angular/cli @angular/core

Navigate to your project's folder, you should find a browserslist file which has the following content:

> 0.5%
last 2 versions
Firefox ESR
not dead
not IE 9-11 # For IE 9-11 support, remove 'not'.

This file is used by the build system to adjust CSS and JavaScript output to support the specified browsers and versions.

You can find more information about its format and rule options, refer to the official docs

You can customize this file as you see fit per your use cases.

You can run the following command in your terminal to display what browsers were selected by these queries:

$ angular-javascript-demo
$ npx browserslist

This is the output for the default browserslist configuration that comes with Angular 8:

and_chr 75
and_ff 67
and_qq 1.2
and_uc 12.12
android 67
baidu 7.12
chrome 75
chrome 74
chrome 73
edge 18
edge 17
firefox 68
firefox 67
firefox 60
ie_mob 11
ios_saf 12.2-12.3
ios_saf 12.0-12.1
ios_saf 11.3-11.4
kaios 2.5
op_mini all
op_mob 46
opera 62
opera 60
safari 12.1
safari 12
samsung 9.2
samsung 8.2

Now, open the tsconfig.json file:

{
  "compileOnSave": false,
  "compilerOptions": {
    "baseUrl": "./",
    "outDir": "./dist/out-tsc",
    "sourceMap": true,
    "declaration": false,
    "downlevelIteration": true,
    "experimentalDecorators": true,
    "module": "esnext",
    "moduleResolution": "node",
    "importHelpers": true,
    "target": "es2015",
    "typeRoots": [
      "node_modules/@types"
    ],
    "lib": [
      "es2018",
      "dom"
    ]
  },
  "angularCompilerOptions": {
    "fullTemplateTypeCheck": true,
    "strictInjectionParameters": true
  }
}

Notice that the target property has the es2015 value which means that the TypeScript compiler will target ES2015/ES6.

Now, head back to your terminal and let's build the application for production using the following command:

$ ng build --prod 

This it the output of the command:

chunk {0} runtime-es2015.24b02acc1f369d9b9f37.js (runtime) 2.83 kB [entry] [rendered]
chunk {1} main-es2015.d335718ef48b35971cc1.js (main) 546 kB [initial] [rendered]
chunk {2} polyfills-es2015.fd917e7c3ed57f282ee5.js (polyfills) 64.3 kB [initial] [rendered]
chunk {3} polyfills-es5-es2015.3aa54d3e5134f5b5b842.js (polyfills-es5) 223 kB [initial] [rendered]
chunk {4} styles.6b3fe9320909874e6b1b.css (styles) 61 kB [initial] [rendered]
Date: 2019-09-20T11:57:25.514Z - Hash: 0f403b3d18149db6f693 - Time: 299070ms
Generating ES5 bundles for differential loading...
ES5 bundle generation complete.

Now, open the dist/index.html file, you'll notice that Angular CLI 8 added various <script> tags with type="module" and nomodule attributes:

<!doctype html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <title>Angulardemo</title>
  <base href="/">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" type="image/x-icon" href="favicon.ico">
  <link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500&display=swap" rel="stylesheet">
  <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
  <link rel="stylesheet" href="styles.6b3fe9320909874e6b1b.css">
</head>

<body>
  <app-root></app-root>
  <script src="polyfills-es5.3aa54d3e5134f5b5b842.js" nomodule defer></script>
  <script src="polyfills-es2015.fd917e7c3ed57f282ee5.js" type="module"></script>
  <script src="runtime-es2015.24b02acc1f369d9b9f37.js" type="module"></script>
  <script src="main-es2015.d335718ef48b35971cc1.js" type="module"></script>
  <script src="runtime-es5.24b02acc1f369d9b9f37.js" nomodule defer></script>
  <script src="main-es5.d335718ef48b35971cc1.js" nomodule defer></script>
</body>

</html>

Let's see this in a web browser. First install the serve tool to serve our app locally:

$ npm install -g serve

Next, navigate to your dist folder and serve it:

$ cd dist/angular-javascript-demo
$ serve

Go with your web browser to the http://localhost:5000 address. Go to the Network panel of DevTools, if you use a modern version of Chrome (or any modern web browser), you should see that your web browser loaded the modern ES2015 bundles of your app:

Introducing JavaScript for Angular Devs

You don't need to master every feature of TypeScript but you must know JavaScript as it's the pillar language of frontend web development. In this tutorial, I'll introduce you to the features of modern JavaScript.

ES6 has new syntax sugar and features that make JavaScript powerful and easier to use. The new ES6 lets you write awesome JavaScript code. Among the new features introduced in JavaScript by ES6 are:

Declaring Variables in JavaScript/ES6 (var vs let vs const)

JavaScript developers used the var keyword for declaring variables for a long period of time but is there anything wrong with the var keyword? Yes more than one thing!

  • Variables declared using var leaks outside their scope (you can access them outside the scope where they are declared),
  • Variables declared using var are mutable (you can change their values)
  • Variables declared with var can be re-declared

ES6 provides the const and let keywords which are designed to solve the issues caused by the var keyword.

  • const lets you declare constant variables which can't be re-assigned.
  • let lets you declare variables which can't be re-declared.

let allows you to declare variables that are limited in scope to the block, statement, or expression on which it is used. This is unlike the var keyword, which defines a variable globally, or locally to an entire function regardless of block scope.Source

const can be used to declare variables that hold elements returned by DOM query methods such as the document.querySelector() method.

const el = document.querySelector(`#my-id`)

const variables need to be initialized or otherwise a SyntaxError will be thrown. For example, the following code will throw an error Uncaught SyntaxError: Missing initializer in const declaration:

const my_variable

This is a screen shot when executing the code in the console of Chrome DevTools:

es6 tutorial

The correct syntax would be:

const my_variable = "a const variable declared";

javascript es6 tutorial

Now, let's try to change the value of my_variable:

my_variable = "new value";

The code will fail with the error Uncaught TypeError: Assignment to constant variable.

es6 tutorial

Now let's use let to declare some variables:

let my_variable2;
let my_variable3= "another variable declaration";

We declare my_variable2 (non initialized) and my_variable2 (initialized with a string value) variables.

javascript es6 tutorial

The value of my_variable2 is undefined.

Unlike the var keyword, the let keyword doesn't allow you to re-declare variables within the same function or block scope. The following code will fail with the error Uncaught SyntaxError: Identifier 'my_variable2' has already been declared:

let my_variable2; 

es6 tutorial

Both const and let keywords are block-scoped meaning the variables are only available inside the block where they are declared. Let's consider this example:

  let x = 1;
  if (true) {
    let x = 2;
    console.log(x);  
  }
  console.log(x);  

We declare the x twice, the first declaration is outside the if{} block and the second declaration is within the if{} block. The second declaration produces a new variable available inside the block but not outside so the first console.log(x) instruction will print 2 while the second console.log(x) will print 1.

es6 tutorial

We now know that var is function scope, and now we know that let and const are block scope, which means any time you’ve got a set of curly brackets you have block scope.

Now, we need to know you can only declare a variable inside of its scope once.

You can update a let variable, and we’ll take a look more at let and const but you cannot redeclare it twice in the same scope.

The important thing here is that these two winner variables are actually two separate variables. They have the same name, but they are both scoped differently:

  • let winner = false outside of the if loop is scoped to the window.
  • let winner = true inside the if loop is scoped to the block.

JS Modules

Almost every language has a concept of modules — a way to include functionality declared in one file within another. Typically, a developer creates an encapsulated library of code responsible for handling related tasks. That library can be referenced by applications or other modules. <!DOCTYPE html> <html> <head> <title>ES6 module example</title> <script src="app/utils.js" type="module"></script> <script src="app/fallback.js" nomodule></script> </head> <body> </body> </html>

You can add module to the type attribute of a script element <script type="module">. The browser will then treat either inline or external script elements as an ES6 module.

In utils.js file.

  function hello()  {  
    return  "Hello";  
  }
  function world()  {
    return  "World";
  }
  // Basic export
  export  { hello, world };

In main.js file.

  import  {hello, world}  from  '/app/utils.js';
  hello();
  world();

Arrow Functions

Arrow functions were introduced with ES6 as a new syntax for writing JavaScript functions. They save developers time and simplify function scope. Surveys show they’re the most popular ES6 feature:

es6 arrow functions Source

Most major modern browsers support arrow functions.

are a more concise syntax for writing function expressions. They utilize a new token, =>, that looks like a fat arrow. Arrow functions are anonymous and change the way this binds in functions.

// ES5
var multiplyES5 = function(x, y) {
  return x * y;
};

// ES6
const multiplyES6 = (x, y) => { return x * y };

Curly brackets aren’t required if only one expression is present. The preceding example could also be written as:

const multiplyES6 = (x, y) => x * y;

Also, you can use Arrow function with map, filter, and reduce built-in functions.

Now, that we have seen the important features of modern JavaScript, let's see how we can how to use JavaScript code in your Angular 8 projects which are based on TypeScript.

Including Custom JavaScript in Angular 8 Project

Now, let's learn about how to include custom JavaScript code in our Angular 8 application.

Go ahead and create a new JavaScript file in the src/ folder of your project:

$ cd src
$ touch custom.js

Next write the following code:

const helloAngular = () => {
    console.log("Hello Angular from JavaScript!");
};

Next, open the angular.json file and add the file to the scripts array:

            "scripts": [
              "src/custom.js"
            ]

That's it, you can now use vanilla JavaScript in your project.

Conclusion

In this tutorial we learned how Angular 8 makes use of differential loading to send separate modern and legacy JavaScript bundles to web browsers based on their capabilities thanks to the module and nomodule attributes and browserslist. Next, we introduced the various new features of modern JavaScript to Angular devs then finally we've seen how to include custom JavaScript code in our Angular apps.


  • Date: