Red Green Repeat Adventures of a Spec Driven Junkie

Learning Angular: Boot Up Process

I want to solidify key concepts in Learning Angular. This time, understanding Angular’s boot up process.

I will go through the process I took in learning how Angular boots up by digging through the files of a fresh Angular project.

By the end, you will understand how Angular boots up, the files involved, their interactions, and configurations.

This article will take about twelve minutes to read, probably less as there’s code included for the sake of completeness.

Andrea Briosco - The Rothschild Lamp source and more information

Introduction

Understanding how a system starts helps me learn the system better. If I can’t debug issues I have at the early stages (like installing a previous version), debugging the issue later when it comes up just becomes harder as there’s more places to look (or there’s places I don’t even know about!)

I will go over how Angular works its magic when starting up a new application.

Following Along

I am only using a blank application generated using: ng new <project name> and modifying the contents of: app/src/app.component.html to be: test 6 app

If you want to follow along, do the following steps with Angular installed at the command prompt:

  1. Run: ng new test6
  2. Run: echo "test 6 app" > test6/app/src/app.component.html

I use find and ag a lot in this article. find is a standard command in most UNIX systems. ag is grep but on steroids, you can learn more and install it for your system here

One file: index.html

If you’re a web programmer, you will know the significance of index.html, the default page that a web-server sends when a file is not specified.

This file also exists in the project test6 project folder in: src/index.html

Let’s take a look to see what it’s contents are:

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Test6</title>
  <base href="/">

  <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 is a plain HTML page, there is not even a script tag to import.

Notice that there is a <app-root> element. That is not a standard HTML element.

Let’s start the application using ng serve and have the browser load up the page and see what the browser displays:

Test 6 in Browser

The index.html does not show test 6 app anywhere in the file above, yet, the browser is showing that in the body. Let’s take a look at the source:

<html lang="en"><head>
   <meta charset="utf-8">
   <title>Test6</title>
   <base href="/">

   <meta name="viewport" content="width=device-width, initial-scale=1">
   <link rel="icon" type="image/x-icon" href="favicon.ico">
 <script data-dapp-detection="">
 (function() {
   let alreadyInsertedMetaTag = false

   function __insertDappDetected() {
	 if (!alreadyInsertedMetaTag) {
	   const meta = document.createElement('meta')
	   meta.name = 'dapp-detected'
	   document.head.appendChild(meta)
	   alreadyInsertedMetaTag = true
	 }
   }

   if (window.hasOwnProperty('web3')) {
	 // Note a closure can't be used for this var because some sites like
	 // www.wnyc.org do a second script execution via eval for some reason.
	 window.__disableDappDetectionInsertion = true
	 // Likely oldWeb3 is undefined and it has a property only because
	 // we defined it. Some sites like wnyc.org are evaling all scripts
	 // that exist again, so this is protection against multiple calls.
	 if (window.web3 === undefined) {
	   return
	 }
	 if (!window.web3.currentProvider ||
		 !window.web3.currentProvider.isMetaMask) {
	   __insertDappDetected()
	 }
   } else {
	 var oldWeb3 = window.web3
	 Object.defineProperty(window, 'web3', {
	   configurable: true,
	   set: function (val) {
		 if (!window.__disableDappDetectionInsertion)
		   __insertDappDetected()
		 oldWeb3 = val
	   },
	   get: function () {
		 if (!window.__disableDappDetectionInsertion)
		   __insertDappDetected()
		 return oldWeb3
	   }
	 })
   }
 })()</script><style type="text/css">/* You can add global styles to this file, and also import other style files */
 </style><style></style></head>
 <body>
   <app-root _nghost-c0="" ng-version="6.1.10">test 6 app
 </app-root>
	   <script type="text/javascript" src="runtime.js"></script><script type="text/javascript" src="polyfills.js"></script><script type="text/javascript"styles.js"></script><script type="text/javascript" src="vendor.js"></script><script type="text/javascript" src="main.js"></script>
</body></html>

When the Angular app booted up, it transformed:

<app-root></app-root>

to:

<app-root _nghost-c0="" ng-version="6.1.10">test 6 app</app-root>

How did this happen?? The only place where the text: test 6 app appears is in the app.component.html file, yet the index.html does not have any JavaScript voodoo to import this file.

Where’s the Magic ?

We know Angular is a JavaScript framework and it’s front-end based. Where’s the magic happening??

Let’s see if there’s a JavaScript or Typescript file that will help explain what’s going on.

Let’s start with Typescript files, using find to list out all the files ending with ts:

vagrant@ubuntu-bionic:~/test6$ find . -name *.ts | grep -v node_modules
./e2e/src/app.po.ts
./e2e/src/app.e2e-spec.ts
./src/main.ts
./src/app/app.module.ts
./src/app/app.component.ts
./src/app/app.component.spec.ts
./src/polyfills.ts
./src/environments/environment.prod.ts
./src/environments/environment.ts
./src/test.ts

Note: need -v node_modules or else find returns all *.ts files in node_modules/

The Typescript files are in these folders:

  • e2e/ folder is for end to end testing.
  • src/environments/ folder is for environment configurations (i.e. production vs. development).
  • src/app/ folder is for our code, definitely a possibility, especially that’s where app.component.html is.
  • src/ folder contains three Typescript files:
    • main.ts
    • polyfills.ts
    • test.ts

Let’s check out the src/ Typescript files:

test.ts

The contents of test.ts contain:

// This file is required by karma.conf.js and loads recursively all the .spec and framework files

import 'zone.js/dist/zone-testing';
import { getTestBed } from '@angular/core/testing';
import {
  BrowserDynamicTestingModule,
  platformBrowserDynamicTesting
} from '@angular/platform-browser-dynamic/testing';

declare const require: any;

// First, initialize the Angular testing environment.
getTestBed().initTestEnvironment(
  BrowserDynamicTestingModule,
  platformBrowserDynamicTesting()
);
// Then we find all the tests.
const context = require.context('./', true, /\.spec\.ts$/);
// And load the modules.
context.keys().map(context);

Hmm - doesn’t look like this file is even used when starting the application. Let’s move on.

polyfill.ts

The contents of the polyfill.ts file contain:

/**
 * This file includes polyfills needed by Angular and is loaded before the app.
 * You can add your own extra polyfills to this file.
 *
 * This file is divided into 2 sections:
 *   1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
 *   2. Application imports. Files imported after ZoneJS that should be loaded before your main
 *      file.
 *
 * The current setup is for so-called "evergreen" browsers; the last versions of browsers that
 * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
 * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
 *
 * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html
 */

/***************************************************************************************************
 * BROWSER POLYFILLS
 */

/** IE9, IE10 and IE11 requires all of the following polyfills. **/
// import 'core-js/es6/symbol';
// import 'core-js/es6/object';
// import 'core-js/es6/function';
// import 'core-js/es6/parse-int';
// import 'core-js/es6/parse-float';
// import 'core-js/es6/number';
// import 'core-js/es6/math';
// import 'core-js/es6/string';
// import 'core-js/es6/date';
// import 'core-js/es6/array';
// import 'core-js/es6/regexp';
// import 'core-js/es6/map';
// import 'core-js/es6/weak-map';
// import 'core-js/es6/set';

/** IE10 and IE11 requires the following for NgClass support on SVG elements */
// import 'classlist.js';  // Run `npm install --save classlist.js`.

/** IE10 and IE11 requires the following for the Reflect API. */
// import 'core-js/es6/reflect';


/** Evergreen browsers require these. **/
// Used for reflect-metadata in JIT. If you use AOT (and only Angular decorators), you can remove.
import 'core-js/es7/reflect';


/**
 * Web Animations `@angular/platform-browser/animations`
 * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.
 * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).
 **/
// import 'web-animations-js';  // Run `npm install --save web-animations-js`.

/**
 * By default, zone.js will patch all possible macroTask and DomEvents
 * user can disable parts of macroTask/DomEvents patch by setting following flags
 */

 // (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
 // (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
 // (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames

 /*
 * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
 * with the following flag, it will bypass `zone.js` patch for IE/Edge
 */
// (window as any).__Zone_enable_cross_context_check = true;

/***************************************************************************************************
 * Zone JS is required by default for Angular itself.
 */
import 'zone.js/dist/zone';  // Included with Angular CLI.



/***************************************************************************************************
 * APPLICATION IMPORTS
 */

OK, this definitely looks irrelevant to getting “test 6 app” into the index.html file. This file had code to handle other browsers, like Internet Explorer. (Remember using that?! :-) )

Next!

main.ts

The contents of the last file in src/, main.ts, contain:

import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

import { AppModule } from './app/app.module';
import { environment } from './environments/environment';

if (environment.production) {
  enableProdMode();
}

platformBrowserDynamic().bootstrapModule(AppModule)
  .catch(err => console.error(err));

Oh - this looks interesting! I will group related items together and go through them:

Environment

These lines process which environment the application is running as:

import { enableProdMode } from '@angular/core';
import { environment } from './environments/environment';
if (environment.production) {
  enableProdMode();
}

Is the app running in production mode? Not relevant now since this is a brand new app.

AppModule

This is the first reference to the src/app/ folder.

import { AppModule } from './app/app.module';

Definitely relevant to the app.component.html file that contains test 6 app.

Platform Browser Dynamic

This is interesting, the import, platform-browser-dynamic from the @angular file is happening with:

import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

platformBrowserDynamic().bootstrapModule(AppModule)
  .catch(err => console.error(err));

That function calls the bootstrapModule, passing in the AppModule, also defined in this file.

🤔

Platform Browser Dynamic

I start looking for platformBrowserDynamic definition in @angular/platform-browser-dynamic:

vagrant@ubuntu-bionic:~/test6$ ag platformBrowserDynamic
src/main.ts
2:import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
11:platformBrowserDynamic().bootstrapModule(AppModule)

src/test.ts
7:  platformBrowserDynamicTesting
15:  platformBrowserDynamicTesting()

Well, that is not useful as those are the files I just looked at.

Let’s go back to find and see what it turns up:

vagrant@ubuntu-bionic:~/test6$ find . -name platform-browser-dynamic*
./node_modules/@angular/platform-browser-dynamic
./node_modules/@angular/platform-browser-dynamic/bundles/platform-browser-dynamic-testing.umd.js
./node_modules/@angular/platform-browser-dynamic/bundles/platform-browser-dynamic-testing.umd.js.map
./node_modules/@angular/platform-browser-dynamic/bundles/platform-browser-dynamic-testing.umd.min.js.map
./node_modules/@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js.map
./node_modules/@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.min.js
./node_modules/@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js
./node_modules/@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.min.js.map
./node_modules/@angular/platform-browser-dynamic/bundles/platform-browser-dynamic-testing.umd.min.js
./node_modules/@angular/platform-browser-dynamic/fesm2015/platform-browser-dynamic.js.map
./node_modules/@angular/platform-browser-dynamic/fesm2015/platform-browser-dynamic.js
./node_modules/@angular/platform-browser-dynamic/platform-browser-dynamic.d.ts
./node_modules/@angular/platform-browser-dynamic/platform-browser-dynamic.metadata.json
./node_modules/@angular/platform-browser-dynamic/esm5/src/platform-browser-dynamic.js
./node_modules/@angular/platform-browser-dynamic/esm5/platform-browser-dynamic.js
./node_modules/@angular/platform-browser-dynamic/src/platform-browser-dynamic.d.ts
./node_modules/@angular/platform-browser-dynamic/fesm5/platform-browser-dynamic.js.map
./node_modules/@angular/platform-browser-dynamic/fesm5/platform-browser-dynamic.js
./node_modules/@angular/platform-browser-dynamic/esm2015/platform-browser-dynamic.externs.js
./node_modules/@angular/platform-browser-dynamic/esm2015/src/platform-browser-dynamic.js
./node_modules/@angular/platform-browser-dynamic/esm2015/platform-browser-dynamic.js

Now we’re talking!

Let’s check out a specific package file:

vagrant@ubuntu-bionic:~/test6$ less node_modules/@angular/platform-browser-dynamic/esm2015/index.js

Digging around that file and others in the node_modules/@angular/platform-browser-dynamic, there’s files, most are “dynamic”, it’s contents contain:

/**
 * @fileoverview added by tsickle
 * @suppress {checkTypes,extraRequire,uselessCode} checked by tsc
 */
/**
 * @license
 * Copyright Google Inc. All Rights Reserved.
 *
 * Use of this source code is governed by an MIT-style license that can be
 * found in the LICENSE file at https://angular.io/license
 */
// This file is not used to build this module. It is only used during editing
// by the TypeScript language service and during build for verification. `ngc`
// replaces this file with production index.ts when it rewrites private symbol
// names.
export { VERSION, JitCompilerFactory, RESOURCE_CACHE_PROVIDER, platformBrowserDynamic, ɵCompilerImpl, ɵplatformCoreDynamic, ɵINTERNAL_BROWSER_DYNAMIC_PLATFORM_PROVIDERS, ɵResourceLoaderImpl } from './public_api';

//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi9wYWNrYWdlcy9wbGF0Zm9ybS1icm93c2VyLWR5bmFtaWMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7Ozs7O0FBYUEscU1BQWMsY0FBYyxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBAbGljZW5zZVxuICogQ29weXJpZ2h0IEdvb2dsZSBJbmMuIEFsbCBSaWdodHMgUmVzZXJ2ZWQuXG4gKlxuICogVXNlIG9mIHRoaXMgc291cmNlIGNvZGUgaXMgZ292ZXJuZWQgYnkgYW4gTUlULXN0eWxlIGxpY2Vuc2UgdGhhdCBjYW4gYmVcbiAqIGZvdW5kIGluIHRoZSBMSUNFTlNFIGZpbGUgYXQgaHR0cHM6Ly9hbmd1bGFyLmlvL2xpY2Vuc2VcbiAqL1xuXG4vLyBUaGlzIGZpbGUgaXMgbm90IHVzZWQgdG8gYnVpbGQgdGhpcyBtb2R1bGUuIEl0IGlzIG9ubHkgdXNlZCBkdXJpbmcgZWRpdGluZ1xuLy8gYnkgdGhlIFR5cGVTY3JpcHQgbGFuZ3VhZ2Ugc2VydmljZSBhbmQgZHVyaW5nIGJ1aWxkIGZvciB2ZXJpZmljYXRpb24uIGBuZ2NgXG4vLyByZXBsYWNlcyB0aGlzIGZpbGUgd2l0aCBwcm9kdWN0aW9uIGluZGV4LnRzIHdoZW4gaXQgcmV3cml0ZXMgcHJpdmF0ZSBzeW1ib2xcbi8vIG5hbWVzLlxuXG5leHBvcnQgKiBmcm9tICcuL3B1YmxpY19hcGknO1xuIl19

That leads me to think: platform-browser-dynamic is probably importing the bootstrapModule from another file! Let’s find that definition.

Bootstrap Module

Let’s search for the definition of the bootstrapModule function using ag again:

vagrant@ubuntu-bionic:~/test6$ ag bootstrapModule node_modules/@angular
...(output snipped!)
node_modules/@angular/core/esm2015/src/application_ref.js
229:     * let moduleRef = platformBrowser().bootstrapModuleFactory(MyModuleNgFactory);
238:    bootstrapModuleFactory(moduleFactory, options) {
282:     * let moduleRef = platformBrowser().bootstrapModule(MyModule);
290:    bootstrapModule(moduleType, compilerOptions = []) {
298:            .then((moduleFactory) => this.bootstrapModuleFactory(moduleFactory, options));

The application_ref.js file looks like it defines the bootstrapModule! Let’s look in that file to see if there’s a better definition:

from: node_modules/@angular/core/esm2015/src/application_ref.js

   /**
	 * Bootstrap a new component at the root level of the application.
	 *
	 * \@usageNotes
	 * ### Bootstrap process
	 *
	 * When bootstrapping a new root component into an application, Angular mounts the
	 * specified application component onto DOM elements identified by the componentType's
	 * selector and kicks off automatic change detection to finish initializing the component.
	 *
	 * Optionally, a component can be mounted onto a DOM element that does not match the
	 * componentType's selector.
	 *
	 * ### Example
	 * {\@example core/ts/platform/platform.ts region='longform'}
	 * @template C
	 * @param {?} componentOrFactory
	 * @param {?=} rootSelectorOrNode
	 * @return {?}
	 */
	bootstrap(componentOrFactory, rootSelectorOrNode) {
		if (!this._initStatus.done) {
			throw new Error('Cannot bootstrap as there are still asynchronous initializers running. Bootstrap components in the `ngDoBootstrap` method of the root module.');
		}

Looks like this is the boot up process for Angular, since it requires to caller to pass a specific component to it: AppModule, which is in the project as: src/app.module.ts.

AppModule

Let’s take a look at the file that defines: “AppModule”, src/app.module.ts:

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppComponent } from './app.component';

@NgModule({
  declarations: [
	AppComponent
  ],
  imports: [
	  BrowserModule,
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

The line:

import { AppComponent } from './app.component';

Is importing the AppComponent. Let’s check that file.

AppComponent

The file: src/app.component.ts defines the AppComponent. The file contains:

import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
}

That contains:

selector: 'app-root',
templateUrl: './app.component.html',

That holds the key to the index.html file transforming: <app-root> to have the contents of the app.component.html file!

  • selector defines which element to transform
  • templateUrl defines what file to insert into the selector

That was easy!

The Angular Connection

Wait, how does Angular know to use main.ts ? The index.html never defined any script tags! What’s the connection???

Going back to ag and digging for any instances of main.ts:

vagrant@ubuntu-bionic:~/test6$ ag main.ts
angular.json
18:            "main": "src/main.ts",

The relevant section of angular.json:

"architect": {
   "build": {
     "builder": "@angular-devkit/build-angular:browser",
     "options": {
       "outputPath": "dist/test6",
       "index": "src/index.html",
       "main": "src/main.ts",
       "polyfills": "src/polyfills.ts",
       "tsConfig": "src/tsconfig.app.json",
       "assets": [
         "src/favicon.ico",
         "src/assets"
       ],
       "styles": [
         "node_modules/bootstrap/dist/css/bootstrap.min.css",
         "src/styles.css"
       ],
       "scripts": []
     },

This configures all the relevant files of Angular, the main file, the polyfill file, even the index.html file!

The Angular Boot Up Process:

(or how:

<app-root></app-root>

becomes:

<app-root _nghost-c0="" ng-version="6.1.10">{{ app.component.html contents }}
</app-root>

)

  File Action Item
1 angular.json defines main file to be main.ts
2 angular.json defines index file to be index.html
3 src/index.html calls <app-root>
4 src/main.ts imports platform-browser-dynamic function
5 node_modules/@angular/platform-browser-dynamic defines platform-browser-dynamic function
6 node_modules/@angular/core/esm2015/src/application_ref.js defines bootstrap function
7 src/main.ts calls platformBrowserDynamic.bootstrapModule(AppModule)
8 src/main.ts imports AppModule
9 src/app.module.ts imports AppComponent
10 src/app.component.ts defines <app-root>
11 src/app.component.ts calls templateUrl
12 src/app.component.html defines contents for <app-root>

That’s the Magic of Booting Up Angular, in twelve steps!

Conclusion

A simple question: “how does angular boot up?!” took more than looking at twelve steps in the process and eight different files. Going through this now, I understand what files are necessary and how those files interact with each other in Angular’s boot up process.

This has been enlightening to me and I look forward in the next steps in learning Angular.