Angular Source Code Tour
Angular Source Code Tour
com
DRAFT
Preface............................................................................................................iii
1:Zone.js.........................................................................................................6
2:Overview Of The Angular Main Project.............................................................24
3:Tsc-Wrapped................................................................................................28
4:@Angular/Facade.........................................................................................33
5:@Angular/Core............................................................................................40
6:@Angular/Common......................................................................................85
7:@Angular/Platform-Browser...........................................................................96
8:@Angular/Platform-Browser-Dynamic............................................................120
9:@Angular/Platform-WebWorker.....................................................................124
10:@Angular/Platform-WebWorker-Dynamic......................................................151
11:@Angular/Platform-Server.........................................................................152
12:@Angular/HTTP........................................................................................158
13:@Angular/Forms.......................................................................................166
14:@Angular/Router......................................................................................176
15:@Angular/Compiler-CLI.............................................................................186
16:@Angular/Compiler...................................................................................193
17:Tsickle.....................................................................................................199
18:TS-API-Guardian.......................................................................................204
Preface
Flex-Layout In-Memory-Web-API
Router
Main
Reflect-
Zone.js RxJS Tsickle
Metadata
Util Projects
TSLint TS-Node
Language
Projects
TypeScript
iv Preface
All these projects are dependent on the TypeScript package (even the TypeScript
compiler is itself written in TypeScript). The dependency on other language projects
varies. None of the projects are dependent on Visual Studio Code. These projects only
need a code editor that can work with the TypeScript tsc compiler – and there are
many (see middle of main page at http://typescriptlang.org). Visual Studio Code is
one such editor, that is particularly good, open source, freely available for macOS,
Linux and Windows, and it too is written in TypeScript. But you may choose to use any
code editor to investigate the source trees of these projects.
Viewing Markdown Files
In addition to source in TypeScript, all these projects also contain documentation files
in markdown format (*.md). Markdown is a domain specific language (DSL) for
represent HTML documents. It is easier / quicker to manually author compared to
HTML. When transformed to HTML it provides professional documentation. One
question you might have is how to view them (as HTML, rather that as .md text). One
easy way to see them as html is just to view the .md files on Github, e.g.:
● https://github.com/angular/angular
Alternatively, on your own machine, most text editors have either direct markdown
support or it is available through extensions. When examining a source tree with .md
files, it is often useful when your code editor can also open markdown files and
transform them to HTML when requested. StackOverflow covers this issue here:
● http://stackoverflow.com/questions/9843609/view-markdown-files-offline
For example, Visual Studio Code supports Markdown natively and if you open a .md
file and select CTRL+SHIFT+V, you get to see nice HTML:
● https://code.visualstudio.com/docs/languages/markdown
Finally, if you want to learn markdown, try here:
● http://www.markdowntutorial.com
deciding how / what to optimize (for example, when application developers really
understand how Angular’s NgZone works and is intertwined with Angular’s change
detection, then they can place CPU-intensive but non-UI code in a different zone
within the same thread, and this can result in much better performance).
Productivity - Familiarity with the source is important for maximum productivity with
any framework and is one part of a developer accumulating a broader understanding
of the substrate upon which their applications execute.
Good practices – Studying large codebases that are well tested and subjected to
detailed code reviews is a great way for “regular” developers to pick up good coding
habits and use in their own application source trees.
Target Audience
This guided tour of the Angular source tree is aimed at intermediate to advanced
application developers who wish to develop a deeper understanding of how Angular
actually works. To really appreciate the functioning of Angular, an application
developer should read the source. Yes, Angular come with developer documentation at
https://angular.io/docs/ts/latest (which is mostly quite good) but it is best studied in
conjunction with the source code.
Overview
The Zone.js project provides multiple asynchronous execution contexts running within
a single JavaScript thread (i.e. within the main browser UI thread or within a single
webworker). Zones are a way of sub-dividing a single thread using the JavaScript
event loop (a single zone does not cross thread boundaries). A nice way to think about
zones is that they sub-divide the stack within a JavaScript thread into multiple mini-
stacks, and sub-divide the JavaScript event loop into multiple mini event loops.
When your app loads Zone.js, it monkey-patches certain asynchronous calls (e.g.
setTimer, addEventListener), to implement zone functionality. Zone.js adds
wrappers to the callbacks the application supplies, and when a timeout occurs or an
event is detected, it runs the wrapper first, and then the application callback. Chunks
of executing application code form tasks and each task executes in the context of a
zone.
Zones are arranged in a hierarchy and provide useful features in areas such as error
handling, performance measuring and executing configured work items upon entering
and leaving a zone (all of which might be of great interest to implementors of change
detection in a modern web framework!).
Zone.js is mostly transparent to application code. Zone.js runs in the background and
for the most part “just works”. Application code can make zone calls if needed and
become more actively involved in zone management. Angular uses Zone.js and
Angular application code usually runs inside a zone (although advanced application
developers can take certain steps to move some code outside of the Angular zone –
using the NgZone class).
Using external libraries can occasionally cause problems with zones – so if you detect
strange errors do verify it is not a zone-related problem. A specific example is with
Stripe payments API (a regular JavaScript library) and Angular – the problem and the
simple solution are explained here:
● http://stackoverflow.com/questions/36258252/stripe-json-circular-reference
Project Information
The homepage and root of the source tree for Zone.js is at:
● https://github.com/angular/zone.js
Below we assume you have got the Zone.js source tree downloaded locally under a
directory we will call <ZONE> and any pathnames we use will be relative to that.
Zone.js is written in TypeScript. It has no package dependencies (its package.json has
this entry: "dependencies": {}), though it has many devDependencies. It is quite a
small source tree, whose size (uncompressed) is less that 700KB.
This primer document will be of interest to developers learning Zone.js:
● https://docs.google.com/document/d/1F5Ug0jcrm031vhSMJEOgp1l-Is-
Vf0UCNDY-LsQtAIY/edit
Overview 7
Loading Zone.js
To use Zone.js in your applications, you need to load it. Your package.json file will
need:
"dependencies": {
..
"zone.js": "<version>"
},
You should load Zone.js just after loading core.js (if using that), and before everything
else. For example, in an Angular application based on the QuickStart layout, your
index.html file will contain:
<script src="node_modules/core-js/client/shim.min.js"></script>
<script src="node_modules/zone.js/dist/zone.js"></script>
<script src="node_modules/reflect-metadata/Reflect.js"></script>
<script src="node_modules/systemjs/dist/system.src.js"></script>
If using an Angular application generated via Angular CLI (as most production apps
will be), Angular CLI will generate a file called <project-name>/src/polyfills.ts and it
will contain:
// This file includes polyfills needed by Angular and is loaded before
// the app. You can add your own extra polyfills to this file.
import 'core-js/es6/symbol';
..
import 'zone.js/dist/zone';
Angular CLI also generates a main.ts file, and its first line is:
import './polyfills.ts';
If writing your application in TypeScript (recommended), you also need to get access
to the ambient declarations. These define the Zone.js API and are supplied in:
● <ZONE>/dist/zone.js.d.ts
(This file is particularly well documented and well worth some careful study by those
learning Zone.js). Unlike declarations for most other libraries, zone.js.d.ts does not
use import or export at all (those constructs do not appear even once in that file).
That means application code wishing to use zones cannot simply import its .d.ts file,
as is normally the case. Instead, the ///reference construct needs to be used. This
includes the referenced file at the site of the ///reference in the containing file. The
benefit of this approach is that the containing file itself does not have to (but may)
use import, and thus may be a script, rather than having to be a module. The use of
zones is not forcing the application to use modules (however, most larger applications,
including all Angular applications - will). How this works is best examined with an
example, so lets look at how Angular includes zone.d.ts. Angular contains a file,
types.d.ts under its modules directory:
● <ANGULAR-MASTER>/modules/types.d.ts
and it has the following contents:
// This file contains all ambient imports needed to compile the modules/
source code
8 1:Zone.js
When building each Angular component, the tsconfig.json file, located in:
● <ANGULAR-MASTER>/modules/@angular/<package>
contains:
"files": [
"index.ts",
"../../../node_modules/zone.js/dist/zone.js.d.ts",
"../../system.d.ts"
],
Use Within Angular
When writing Angular applications, all your application code runs within a zone, unless
you take specific steps to ensure some of it does not. Also, most of the Angular
framework code itself runs in a zone. When beginning Angular application
development, you can get by simply ignoring zones, since they are set up correctly by
default for you and applications do not have to do anything in particular to take
advantage of them.
Zones are how Angular initiates change detection – when the zone’s mini-stack is
empty, change detection occurs. Also, zones are how Angular configures global
exception handlers. When an error occurs in a task, its zone’s configured error handler
is called. A default implementation is provided and applications can supply a custom
implementation via dependency injection. For details, see here:
● https://angular.io/docs/ts/latest/api/core/index/ErrorHandler-class.html
On that page note the code sample about setting up your own error handler:
class MyErrorHandler implements ErrorHandler {
handleError(error) {
// do something with the exception
}
}
@NgModule({
providers: [{provide: ErrorHandler, useClass: MyErrorHandler}]
})
class MyModule {}
Overview 9
private _bootstrapModuleFactoryWithZone<M>(
moduleFactory: NgModuleFactory<M>, ngZone: NgZone): Promise<NgModuleRef<M>>{
// Note: We need to create the NgZone _before_ we instantiate the module,
// as instantiating the module creates some providers eagerly.
// So we create a mini parent injector that just contains the new NgZone
// and pass that as parent to the NgModuleFactory.
1 if (!ngZone) ngZone = new NgZone({enableLongStackTrace: isDevMode()});
// Attention: Don't use ApplicationRef.run here,
// as we want to be sure that all possible constructor calls are
// inside `ngZone.run`!
return ngZone.run(() => {
const ngZoneInjector =
ReflectiveInjector.resolveAndCreate([{provide: NgZone,
useValue: ngZone}], this.injector);
const moduleRef =
<NgModuleInjector<M>>moduleFactory.create(ngZoneInjector);
2 const exceptionHandler: ErrorHandler =
moduleRef.injector.get(ErrorHandler, null);
if (!exceptionHandler) {
throw new Error(
'No ErrorHandler. Is platform module (BrowserModule) included?');
}
moduleRef.onDestroy(() =>
ListWrapper.remove(this._modules, moduleRef));
3 ngZone.onError.subscribe(
{next: (error: any) => { exceptionHandler.handleError(error); }});
return _callAndReportToErrorHandler(exceptionHandler, () => {
const initStatus: ApplicationInitStatus =
10 1:Zone.js
moduleRef.injector.get(ApplicationInitStatus);
return initStatus.donePromise.then(() => {
4 this._moduleDoBootstrap(moduleRef);
return moduleRef;
});
});
At 1 we see a new NgZone being created and its run() method being called, at 2 we
see an error handler implementation being requested from dependency injection (a
default implementation will be returned unless the application supplies a custom one)
and at 3, we see that error handler being used to configure error handling for the
newly created NgZone. Finally at 4, we see the call to the actual bootstrapping.
So in summary, as Angular application developers, we should clearly learn about
zones, since that is the execution context within which our application code will run.
Zone.js API
Zone.js exposes an API for applications to use in the <ZONE>/dist/zone.js.d.ts file:
The two main types it offers are for tasks and zones, along with some helper types. A
zone is a (usually named) asynchronous execution context; a task is a block of
functionality (may also be named). Tasks run in the context of a zone. Zone.js also
supplies a const value, also called Zone, of type ZoneType:
interface ZoneType {
current: Zone;
currentTask: Task; }
declare const Zone: ZoneType;
Recall that TypeScript has distinct declaration spaces for values and types, so the Zone
value is distinct from the Zone type. For further details, see the TypeScript Language
Specification – Section 2.3 – Declarations:
● https://github.com/Microsoft/TypeScript/blob/master/doc/spec.md#2.3
Apart from being used to define the Zone value, ZoneType is not used further.
When your application code wishes to find out the current zone it simply uses
Zone.current, and when it wants to discover the current task within that zone, it
uses Zone.currentTask. If you need to figure out whether Zone.js is available to your
application (it will be for Angular applications), then just make sure Zone is not
undefined. If we examine:
● <ANGULAR-MASTER>/modules/@angular/core/src/zone/ng_zone.ts
– we see that is exactly what Angular’s NgZone.ts does:
constructor({enableLongStackTrace = false}) {
if (typeof Zone == 'undefined') {
throw new Error('Angular requires Zone.js polyfill.');
}
Zone.js API 11
Zone.js API
TaskType Task
type
data
TaskData currentTask
ZoneType Zone
current
ZoneSpec
parent
ZoneDelegate
Zone
HasTaskState
Types Values
Two simple helper types used to define tasks are TaskType and TaskData. TaskType is
just a human-friendly string to associate with a task. It is usually set to one of the
three task types as noted in the comment:
// Task type: `microTask`, `macroTask`, `eventTask`.
declare type TaskType = string;
TaskData contains a boolean (is this task periodic, i.e. is to be repeated) and two
numbers - delay before executing this task and a handler id from setTimout.
interface TaskData {
/**
* A periodic [MacroTask] is such which get
* automatically rescheduled after it is executed.
*/
isPeriodic?: boolean;
/**
* Delay in milliseconds when the Task will run.
*/
12 1:Zone.js
delay?: number;
/**
* identifier returned by the native setTimeout.
*/
handleId?: number;
}
There are three helper types used to define Zone. HasTaskState just contains
booleans for each of the task types and a string:
declare type HasTaskState = {
microTask: boolean;
macroTask: boolean;
eventTask: boolean;
change: TaskType;
};
ZoneDelegate is used when one zone wishes to delegate to another how certain
operations should be performed. So for forcking (creating new tasks), scheduling,
intercepting, invoking and error handling, the delegate may be called upon to carry
out the action.
interface ZoneDelegate {
zone: Zone;
fork(targetZone: Zone, zoneSpec: ZoneSpec): Zone;
intercept(targetZone: Zone, callback: Function, source: string):
Function;
invoke(targetZone: Zone, callback: Function, applyThis: any,
applyArgs: any[], source: string): any;
handleError(targetZone: Zone, error: any): boolean;
scheduleTask(targetZone: Zone, task: Task): Task;
invokeTask(targetZone: Zone, task: Task, applyThis: any,
applyArgs: any): any;
cancelTask(targetZone: Zone, task: Task): any;
hasTask(targetZone: Zone, isEmpty: HasTaskState): void;
}
Zone.js API 13
ZoneSpec is an interface that allows implementations to state what should have when
certain actions are needed. It uses ZoneDelegate and the current zone:
interface ZoneSpec {
name: string;
properties?: { [key: string]: any; };
onFork?: (parentZoneDelegate: ZoneDelegate, currentZone: Zone,
targetZone: Zone, zoneSpec: ZoneSpec) => Zone;
onIntercept?: (parentZoneDelegate: ZoneDelegate, currentZone: Zone,
targetZone: Zone, delegate: Function, source: string) => Function;
onInvoke?: (parentZoneDelegate: ZoneDelegate, currentZone: Zone,
targetZone: Zone, delegate: Function, applyThis: any,
applyArgs: any[], source: string) => any;
onHandleError?: (parentZoneDelegate: ZoneDelegate, currentZone: Zone,
targetZone: Zone, error: any) => boolean;
onScheduleTask?: (parentZoneDelegate: ZoneDelegate, currentZone: Zone,
targetZone: Zone, task: Task) => Task;
onInvokeTask?: (parentZoneDelegate: ZoneDelegate, currentZone: Zone,
targetZone: Zone, task: Task, applyThis: any, applyArgs: any)=>any;
onCancelTask?: (parentZoneDelegate: ZoneDelegate, currentZone: Zone,
targetZone: Zone, task: Task) => any;
onHasTask?: (delegate: ZoneDelegate, current: Zone, target: Zone,
hasTaskState: HasTaskState) => void;
}
Often when a zone needs to perform an action, it uses the supplied ZoneSpec. Do you
want to record a long stack trace, keep track of tasks, work with WTF (discussed later)
or run async test well? For each a these a different ZoneSpec is supplied, and each
offers different features and comes with different processing costs. Zone.js supplies
one implementation of the Zone interface, and multiple implementations of the
ZoneSpec interface (in <ZONE>/lib/zone-spec). Application code with specialist needs
could create a custom ZoneSpec.
An application can build up a hierarchy of zones and sometimes a zone needs to make
a call into another zone further up the hierarchy, and for this a ZoneDelegate is used.
There were significant changes to the Zone.js API between v.0.5 and v.0.6, and much
of README.md describes the old v0.5 with the old API, so perhaps is best ignored. It
contains one important comment:
# NEW Zone.js POST-v0.6.0
See the new API [here](./dist/zone.js.d.ts).
that use Zone.js. From the DEVELOPER.md document we see the contents of dist is
auto-generated (we need to explore how).
The root directory contains these JSON files:
● tsconfig.json
● typings.json
● package.json
tsconfig.json is:
"compilerOptions": {
"module": "commonjs",
"target": "es5",
"noImplicitAny": false,
"outDir": "build",
"inlineSourceMap": false,
"inlineSources": false,
"declaration": true,
"noEmitOnError": false,
"stripInternal": true
},
"exclude": [
"node_modules",
"typings/main",
"typings/main.d.ts",
"build",
"dist"
]
The typings.json file contains ambient dependencies for Jasmine (unit testing), es6-
promise and node.
The package.json file contains metadata (including main and browser, which provide
alternative entry points depending on whether this package 1 is loaded into a server
[node] or a 2 browser app):
{
"name": "zone.js",
"version": "0.6.17",
"description": "Zones for JavaScript",
1 "main": "dist/zone-node.js",
2 "browser": "dist/zone.js",
"typings": "dist/zone.js.d.ts",
it has no dependencies:
"dependencies": {},
It has many devDependencies, including those related to gulp (task runner), jasmine
(unit testing) and karma (unit test runner), and then these:
"devDependencies": {
...
"es6-promise": "^3.0.2",
"nodejs-websocket": "^1.2.0",
"ts-loader": "^0.6.0",
"typescript": "^1.8.0",
"typings": "^0.7.12",
"webpack": "^1.12.2"
}
}
Webpack is quite a popular bundler and ts-loader is a TypeScript loader for webpack.
Details on both projects can be found here:
https://webpack.github.io/
https://github.com/TypeStrong/ts-loader
The root directory also contains the MIT license in a file called LICENSE, along with
the same within a comment in a file called LICENSE.wrapped.
It contains the following files concerning unit testing and continuous integration
testing:
● .travis.yml
● karma.conf.js
● karma-sauce.conf.js
● sause.conf.js
It contains this file concerning bundling:
● webpack.config.js
This has the following content:
module.exports = {
entry: './test/source_map_test.ts',
output: {
path: __dirname + '/build',
filename: 'source_map_test_webpack.js'
},
devtool: 'inline-source-map',
module: {
loaders: [
{test: /\.ts/, loaders: ['ts-loader'], exclude: /node_modules/}
]
},
resolve: {
extensions: ["", ".js", ".ts"]
}
}
● .gitignore
It contains this task runner configuration:
● gulpfile.js
It supplies a gulp task called “test/node” to run tests against the node version of
Zone.js, and a gulp task “compile” which runs the TypeScript tsc compiler in a child
process. It supplies many gulp tasks to build individual components:
[1] The ambient declarations
'build/zone.js.d.ts',
[3] different ZoneSpecs (so they can be loaded individually, whichever (if any) are
required):
'build/long-stack-trace-zone.js',
'build/long-stack-trace-zone.min.js',
'build/proxy-zone.js',
'build/proxy-zone.min.js',
'build/task-tracking.js',
'build/task-tracking.min.js',
'build/wtf.js',
'build/wtf.min.js',
'build/async-test.js',
'build/fake-async-test.js',
'build/sync-test.js'
The remaining files in the dist directory are builds of different zone specs, which for
specialist reasons you may wish to include. These are:
● async-test.js
● fake-async-test.js
● long-stack-trace-zone.js / long-stack-trace-zone.min.js
● proxy.js / proxy.min.js
● task-tracking.js / task-tracking.min.js
● wtf.js / wtf.min.js
We will look in detail at what each of these does later when examining the
<ZONE>/lib/zone-spec source directory.
example
The example directory contains a range of simple examples showing how to use
Zone.js.
scripts
The script directory contains three scripts:
● grab-blink-idl.sh
● sauce/sauce_connect_setup.sh
● sauce/sauce_connect_block.sh
Source
The main source for zone.js is in:
● <ZONE>/lib
It contains five directories:
● common
● browser
● server
● jasmine
● zone-spec
along with one source file:
● zone.ts
It is best to think of them arranged as follows:
Source 19
Zone.js Components
Common
zone.ts ZoneSpec
core engine monkey patching (different for each environment) processing rules
Internally, it implements a ZoneAwarePromise class and swaps out the global promise
for it.
class ZoneAwarePromise<R> implements Promise<R> { .. }
global.Promise = ZoneAwarePromise;
The constructor just records the supplied parameters and sets up invoke:
constructor(type: TaskType, zone: Zone, source: string,
callback: Function, options: TaskData,
scheduleFn: (task: Task) => void, cancelFn:(task: Task) => void){
this.type = type;
this.zone = zone;
this.source = source;
this.data = options;
this.scheduleFn = scheduleFn;
this.cancelFn = cancelFn;
this.callback = callback;
const self = this;
The interesting activity in here is setting up the invoke function. It increments the
_numberOfNestedTaskFrames counter, calls zone.runTask(), and in a finally block,
checks if _numberOfNestedTaskFrames is 1, and if so, calls the standalone function
drainMicroTaskQueue(), and then decrements _numberOfNestedTaskFrames.
this.invoke = function () {
_numberOfNestedTaskFrames++;
try {
return zone.runTask(self, this, <any>arguments);
} finally {
if (_numberOfNestedTaskFrames == 1) {
drainMicroTaskQueue();
}
Source 21
_numberOfNestedTaskFrames--;
}
};
}
The ZoneDelegate methods for the eight scenarios just forward the calls to the
selected ZoneSpec (pr parent delegate) and does some house keeping. For example,
the invoke method checks if _invokeZS is defined, and if so, calls its onInvoke,
otherwise it calls the supplied callback directly:
invoke(targetZone: Zone, callback: Function, applyThis: any,
applyArgs: any[], source: string): any {
return this._invokeZS
? this._invokeZS.onInvoke(this._invokeDlgt, this.zone, targetZone,
Source 23
The scheduleTask method is a bit different, in that it first 1 tries to use the
_scheduleTaskZS (if defined), otherwise 2 tries to use the supplied task’s scheduleFn
(if defined), otherwise 3 if a microtask calls scheduleMicroTask(), otherwise 4 it is
an error:
scheduleTask(targetZone: Zone, task: Task): Task {
try {
1 if (this._scheduleTaskZS) {
return this._scheduleTaskZS.onScheduleTask(
this._scheduleTaskDlgt, this.zone, targetZone, task);
2 } else if (task.scheduleFn) {
task.scheduleFn(task)
3 } else if (task.type == 'microTask') {
scheduleMicroTask(<MicroTask>task);
} else {
4 throw new Error('Task is missing scheduleFn.');
}
return task;
} finally {
if (targetZone == this.zone) {
this._updateTaskCount(task.type, 1);
}
}
}
The fork method is where new zones get created. If _forkZS is defined, it is used,
otherwise a new zone is created with the supplied targetZone and zoneSpec:
fork(targetZone: Zone, zoneSpec: ZoneSpec): AmbientZone {
return this._forkZS
? this._forkZS.onFork(
this._forkDlgt, this.zone, targetZone, zoneSpec)
: new Zone(targetZone, zoneSpec);
}
The internal variable _currentZone is initialized to the root zone( _currentTask to null):
let _currentZone: Zone = new Zone(null, null);
let _currentTask: Task = null;
2: Overview Of The Angular Main Project
Keeping Up to Date
The Angular project is evolving rapidly and it is important for application developers
using it to keep up to date with progress.
A good place to start is with the notes from the Angular Weekly Meeting:
● http://g.co/ng/weekly-notes
When updates to the codebase are released, the latest changes are recorded in the
change Log (CHANGELOG.md) in the root folder and app developers should review the
contents of this file from time to time.
For general news on Angular, keep an eye on:
● https://angular.io/news.html
The next version will be Angular 4, expected in March, 2017. During the development
of Angular, the router went through a number of significant iterations and had its own
independent version number – which currently stands at version 3 (within Angular).
To bring versioning of Angular and its router back in sync, the next version of Angular
will be set to 4 and not 3). Angular 4 is expected to contain smaller evolutionary
improvements (certainly nothing on the scale of change between AngularJS and
Angular). When Angular 4 arrives, expect more use of just “Angular” to describe the
framework.
Angular 2
includes
NPM packages
( in root directory)
executes
calls published by
npm publish
publish-packages.sh
uploads to
downloaded by
writes
refers to located in
Overview
Tsc-wrapped is a wrapper for the TypeScript tsc compiler that allows extensions to be
added. The Angular ahead-of-time (AOT) compiler, Compiler-CLI, calls tsc-wrapped,
which in turn calls tsickle (which we will examine in a later chapter).
Tsc-wrapped is located in:
● <ANGULAR-MASTER>/tools/@angular/tsc-wrapped
and is the only entry there under tools/@angular. In contrast, there are many projects
under modules/@angular, where most of the Angular source lives.
The `.d.ts` format does not preserve information about the Decorators applied
to symbols. Some tools, such as Angular template compiler, need access to
statically analyzable information about Decorators, so this library allows
programs to produce a `foo.metadata.json` to accompany a `foo.d.ts` file, and
preserves the information that was lost in the declaration emit.
Source Tree Layout 31
The index.ts file exports various types from the src sub-directory:
export {DecoratorDownlevelCompilerHost, MetadataWriterHost}
from './src/compiler_host';
export {CodegenExtension, main} from './src/main';
export {default as AngularCompilerOptions} from './src/options';
export * from './src/cli_options';
export * from './src/collector';
export * from './src/schema';
CliOptions
CliOptions
I18nExtractionCliOptions NgcCliOptions
Different options are supplied for the internationalization functionality (i18n) and the
compilation functionality (ngc). The constructors in each case initialize the properties:
32 3:Tsc-Wrapped
The TypeScript tsc compiler uses a supplied compiler host instance to interact with the
world outside the compiler (e.g. loading source files from the file system).
Tsc-wrapped uses custom compiler hosts to implement varying functionality.
The compiler_host.ts source file contains four (one abstract) such implementations:
The src directory 33
// to be reused by metadataWriter
programForJsEmit = createProgram(downlevelHost);
check(downlevelHost.diagnostics);
preprocessHost = downlevelHost;
if (diagnostics) console.timeEnd('NG downlevel');
}
if (ngOptions.annotateForClosureCompiler) {
if (diagnostics) console.time('NG JSDoc');
const tsickleHost =
3 new TsickleCompilerHost(preprocessHost,programForJsEmit,ngOptions);
programForJsEmit = createProgram(tsickleHost);
check(tsickleHost.diagnostics);
if (diagnostics) console.timeEnd('NG JSDoc');
}
We see the different compiler hosts (1, 2, 3) being used to implement varying
functionality.
4: @Angular/Facade
Overview
Facade is a collection of wrapper and utility-style types that give more control over
access to low-level functionality. Unlike all the other @angular packages, it has no
index.ts in its root directory. Most of its functionality is used internally by the other
packages and is imported directly by them.
Unlike all the directories in modules/@angular, Facade is not a package. It does not
have a package.json file. It you look up package.json of the application source tree
generated by Angular CLI, Facade is not listed among the dependencies, unlike say,
@angular/common or @angular/core, hence when you run npm install to download
all needed packages, and later look in your application’s node_modules directory,
there is no @angular/facade in there.
Each of the @angular packages needs to contain a symbolic link in its src directory to
the facade directory. For Windows, you need to call the create-symlinks.sh script
(in <ANGULAR2>/scripts) to create these links – refer to our earlier discussion of this
issue when we explored the scripts directory.
@Angular/Facade API
A single Facade type, EventEmitter, is exported, and this is done via core’s index.ts,
● <ANGULAR2>/modules/@angular/core/index.ts
with this line:
export {EventEmitter} from './src/facade/async';
An event emitter is an RxJS observable for a stream of events and is widely used
within Angular.
Source
The files in facade’s src sub-directory:
● <ANGULAR2>/modules/@angular/facade/src
are as follows:
● async.ts
● browser.ts
● collection.ts
● errors.ts
● intl.ts
● lang.ts
36 4:@Angular/Facade
WrappedError extends BaseError and in its constructor takes in an error to wrap and
stores this in a field called _originalError. The one change WrappedError makes to
the BaseError API is to re-implement get stack. It either returns the recorded stack
of _originalError (if an instance of Error) or otherwise _nativeError:
export class WrappedError extends BaseError {
originalError: any;
_nativeError: Error;
constructor(message: string, error: any) {
super(
`${message} caused by: ${error instanceof Error ? error.message: error }`);
this.originalError = error;
}
get stack() {
return (
(this.originalError instanceof Error
? this.originalError
: this._nativeError)
as any).stack;
}
}
The lang.ts file supplies a range of small language-related utilitiy functions, often only
one or a few lines long. Here is a small selection:
export function hasConstructor(value: Object, type: any): boolean {
return value.constructor === type;
Source 37
It also adds wrappers for primitives, with useful extra functionality. For example,
StringWrapper has this method to remove characters on the left of a string:
static stripLeft(s: string, charVal: string): string {
if (s && s.length) {
var pos = 0;
for (var i = 0; i < s.length; i++) {
if (s[i] != charVal) break;
pos++;
}
s = s.substring(pos);
}
return s;
}
A single class, EventEmitter, is defined in the async.ts file. It is widely used in the
Angular packages, including: by directives, components, NgZone, location, async pipe,
metadata, forms, http, the message bus for platform browser’s webworker
communication and more. In other words, it is used everywhere within Angular, and
shows the importance of RxJS to Angular development.
EventEmitter extends RxJS’s Subject:
export class EventEmitter<T> extends Subject<T> {...}
When client code wishes this event emitter to emit a value, it calls the emit()
method, which forwards the value to the base class observable’s next() method:
emit(value?: T) { super.next(value); }
It declares three local variables, and after being set in this method, the last line
passes them to the base class observerable’s subscribe() method, and its result is
returned as the result of this method:
let schedulerFn: any;
let errorFn = (err: any): any => null;
let completeFn = (): any => null;
…
return super.subscribe(schedulerFn, errorFn, completeFn);
The generatorOrNext parameter should either be an object with a next, error and
complete methods, or a function that can be called directly, with a value parameter.
The body of EventEmitter.subscribe has an if / else statement that checks
generatorOrNext. (When we slightly reformat the layout of the code to better line up
the conditional statements), the if side looks like:
if (generatorOrNext && typeof generatorOrNext === 'object') {
schedulerFn =
this.__isAsync
? (value: any) => {setTimeout(() => generatorOrNext.next(value));}
: (value: any) => { generatorOrNext.next(value); };
if (generatorOrNext.error) {
errorFn =
this.__isAsync
? (err) => { setTimeout(() => generatorOrNext.error(err)); }
: (err) => { generatorOrNext.error(err); };
}
if (generatorOrNext.complete) {
completeFn =
this.__isAsync
? () => { setTimeout(() => generatorOrNext.complete()); }
: () => { generatorOrNext.complete(); };
}
}
if (complete) {
completeFn =
this.__isAsync
? () => { setTimeout(() => complete()); }
: () => { complete(); };
}
The schdulerFn, errorFn and completeFn set to handlers that call the passed in
function parameters.
How EventEmitter.subscribe() works when __isAsync is true is best described
with the following diagram:
…
VM’s implementation of setTimeout
SetTimeout()
When timeout expires,
...
add an event to event queue
(there may be events ahead of it)
z
x Event 0 Event 1
Only when stack is
empty, remove next
Stack Event Queue
event from queue
The collection.ts file contains wrappers for collection types, such as maps and lists.
Note the methods of these wrappers are static, so the actual collection data needs to
be passed in a parameter (usually the first).
For example, the ListWrapper class exposes these static methods:
export class ListWrapper {
static createFixedSize(size: number): any[] { }
static createGrowableSize(size: number): any[] { }
static clone<T>(array: T[]): T[] { }
40 4:@Angular/Facade
The intl.ts file contains small helper classes and functions for internationalization. It
deals with regional-specific data such as dates, time format and number formatting.
An example class is NumberFormatter, which has a single static method, format, that
returns an international number format via a call to the standard Intl.NumberFormat
interface in lib.es6.d.ts:
export class NumberFormatter {
static format(
num: number, locale: string, style: NumberFormatStyle,
{minimumIntegerDigits, minimumFractionDigits,
maximumFractionDigits, currency, currencyAsSymbol = false}: {
minimumIntegerDigits?: number,
minimumFractionDigits?: number,
Source 41
maximumFractionDigits?: number,
currency?: string,
currencyAsSymbol?: boolean
} = {}): string {
let options: Intl.NumberFormatOptions = {
minimumIntegerDigits,
minimumFractionDigits,
maximumFractionDigits,
style: NumberFormatStyle[style].toLowerCase()
};
if (style == NumberFormatStyle.Currency) {
options.currency = currency;
options.currencyDisplay = currencyAsSymbol ? 'symbol' : 'code';
}
return new Intl.NumberFormat(locale, options).format(num);
}
}
Test Directory
The files in facade’s test sub-directory:
● <ANGULAR2>/modules/@angular/facade/test
are as follows:
● async_spec.ts
● collection-spec.ts
● lang_spec.ts
these contain jasmine unit tests for EventEmitter, collections and lang.
5: @Angular/Core
Overview
Core is the foundational package upon which all other packages are based. It supplies
a wide range of functionaliy, in areas such as metadata, the template linker, the Ng
module system, application initialization, dependency injection, i18n, animation, WTF,
and foundational types such as NgZone, Sanitizer and SecurityContext.
@Angular/Core API
The index.ts file in Core’s root directory just exports src/core/ts:
● <ANGULAR-MASTER>/modules/@angular/core/src/core.ts
which is where Core’s API is exported:
export * from './metadata';
export * from './version';
export * from './util';
export * from './di';
export {createPlatform, assertPlatform, destroyPlatform, getPlatform,
PlatformRef, ApplicationRef, enableProdMode, isDevMode,
createPlatformFactory, NgProbeToken} from './application_ref';
export {APP_ID, PACKAGE_ROOT_URL, PLATFORM_INITIALIZER,
APP_BOOTSTRAP_LISTENER} from './application_tokens';
export {APP_INITIALIZER, ApplicationInitStatus} from './application_init';
export * from './zone';
export * from './render';
export * from './linker';
export {DebugElement, DebugNode, asNativeElements, getDebugNode}
from './debug/debug_node';
export {GetTestability, Testability, TestabilityRegistry,
setTestabilityGetter} from './testability/testability';
export * from './change_detection';
export * from './platform_core_providers';
export {TRANSLATIONS, TRANSLATIONS_FORMAT, LOCALE_ID} from './i18n/tokens';
export {ApplicationModule} from './application_module';
export {wtfCreateScope, wtfLeave, wtfStartTimeRange,
wtfEndTimeRange, WtfScopeFn} from './profile/profile';
export {Type} from './type';
export {EventEmitter} from './facade/async';
export {ErrorHandler} from './error_handler';
export * from './core_private_export';
export * from './animation/metadata';
export {AnimationTransitionEvent}
from './animation/animation_transition_event';
export {AnimationPlayer} from './animation/animation_player';
export {Sanitizer, SecurityContext} from './security';
The source tree for the Core package contains these directories:
● src
● test (unit tests in Jasmine)
● testing (test tooling)
and these files:
● index.ts
● package.json
● rollup.config.js
● rollup-testing.config.js
● tsconfig-build.json
● tsconfig-testing.json
Source
core/src
The core/src directory directly contains many files, which we will group into three
categories. Firstly, a number of files just export types from equivalently named sub-
directories. Files that fall into this category include:
● change_detection.ts
● core.ts
● core_private_export.ts
● di.ts
● metadata.ts
● linker.ts
● render.ts
● zone.ts
For example, the renderer.ts file is a one-liner that just exports from renderer/api.ts:
export {RenderComponentType, Renderer, RootRenderer} from './render/api';
opaque token that Angular itself and application code can use to register supplied
functions that will be executed when a platform is initialized. An example usage within
Angular is in @angular/platform-browser (src/browser.ts) where it is used to have
initDomAdapter function called upon platform initialization (note use of multi –
which means multiple such initializer functions can be registered):
export const INTERNAL_BROWSER_PLATFORM_PROVIDERS: Provider[] = [
{provide: PLATFORM_INITIALIZER, useValue: initDomAdapter, multi: true},
{provide: PlatformLocation, useClass: BrowserPlatformLocation}
];
application_init.ts defines one opaque token and one injectable service. The opaque
token is:
export const APP_INITIALIZER: any =
new OpaqueToken('Application Initializer');
Providers are supplied to Angular’s dependency injection system. Note the default
locale is set to “en-US” - globalized applications may wish to override this for
international markets. The IterableDiffers and KeyValueDiffers provides related to
change detection. ViewUtils is defined in the src/linker sub-directory and contains
utility-style code related to rendering.
Often in the Angular source tree we see an abstract class being defined, along with a
concrete implementation (same name with an additional ‘_’ at the end). We see this in
ApplicationModule, in these two lines (ApplicationRef_ derives from the abstract
ApplicationRef):
ApplicationRef_,
{provide: ApplicationRef, useExisting: ApplicationRef_},
So if client code asks DI for ApplicationRef_, an instance of that will be returned;; and
if client code asks for an instance that implements the base ApplicationRef, then the
existi instance of ApplicationRef_ will also be returned. It is dones line this so
application code can provide custom implmentations of some of these classes (we
would normally not recommend providing a custom implementation of ApplicationRef,
but for other types it can be useful).
The types in application_ref.ts plays a pivotal role in how the entire Angular
infrastructure works. Application developers wishing to learn how Angular really works
are strongly encouraged to carefully study the code in application_ref.ts. Let’s start
our examination by looking at the createPlatformFactory() function:
export function createPlatformFactory(
1 parentPlaformFactory:(extraProviders?: Provider[]) => PlatformRef,
2 name: string,
3 providers: Provider[] = [])
4 : (extraProviders?: Provider[]) => PlatformRef {
const marker = new OpaqueToken(`Platform: ${name}`);
5 return (extraProviders: Provider[] = []) => {
if (!getPlatform()) {
if (parentPlaformFactory) {
parentPlaformFactory(
providers.concat(extraProviders).concat(
Source 47
A platform represent the hosting environment within which one or more applications
execute. Different platforms are supported (e.g. browser UI, webworker, server and
you can create your own). For a web page, it is how application code interacts with
the page (e.g. sets URL). PlatformRef represents the platform, and we see its two
main features are supplying the root injector and module bootstrapping. The other
members are to do with destroying resources when no longer needed.
One implementation of PlatformRef is supplied, called PlatformRef_. It manages the
root injector passed in to the constructor, an array of NgModuleRefs and an array of
destroy listeners. In the constructor, it takes in an injector. Note that calling the
platform’s destroy() method will result in all applications that use that platform
having their destroy() methods called.
@Injectable()
export class PlatformRef_ extends PlatformRef {
48 5:@Angular/Core
destroy() {
if (this._destroyed) {
throw new Error('The platform has already been destroyed!');
}
this._modules.slice().forEach(module => module.destroy());
this._destroyListeners.forEach(listener => listener());
this._destroyed = true;
}
private _bootstrapModuleWithZone<M>(
moduleType: Type<M>,
compilerOptions: CompilerOptions|CompilerOptions[] = [],
ngZone: NgZone,
componentFactoryCallback?: any): Promise<NgModuleRef<M>> {
Platform Bootstrapping
using offline compiler
using runtime compiler (compiler-cli)
Main.ts calls:
app code
Main.ts calls:
PlatformBrowserDynamic() PlatformBrowser()
.bootstrapModule(AppModule); .bootstrapModuleFactory(AppModule);
bootstrapModule
@angular/core/src/angular_ref.ts
calls
_bootstrapModuleWithZone
(calls compiler here)
bootstrapModuleFactory
calls
calls
_bootstrapModuleFactoryWithZone
calls
_moduleDoBootstrap
50 5:@Angular/Core
private _bootstrapModuleFactoryWithZone<M>(
moduleFactory: NgModuleFactory<M>, ngZone: NgZone):Promise<NgModuleRef<M>>{
if (!ngZone)
ngZone = new NgZone({enableLongStackTrace: isDevMode()});
return ngZone.run(() => {
const ngZoneInjector =
ReflectiveInjector.resolveAndCreate(
[{provide: NgZone, useValue: ngZone}], this.injector);
const moduleRef =
<NgModuleInjector<M>>moduleFactory.create(ngZoneInjector);
const exceptionHandler: ErrorHandler =
moduleRef.injector.get(ErrorHandler, null);
if (!exceptionHandler) {
throw new Error(
'No ErrorHandler. Is platform module (BrowserModule) included?');
}
moduleRef.onDestroy(()
=> ListWrapper.remove(this._modules, moduleRef));
ngZone.onError.subscribe(
{next: (error: any) => { exceptionHandler.handleError(error); }});
return _callAndReportToErrorHandler(exceptionHandler,
() => {
const initStatus: ApplicationInitStatus =
moduleRef.injector.get(ApplicationInitStatus);
return initStatus.donePromise.then(() => {
1 this._moduleDoBootstrap(moduleRef);
return moduleRef;
});
});
});
}
Only a single platform may be active at any one time. _platform is defined as:
let _platform: PlatformRef;;
The assertPlatform() function ensures two things, and if either false, throws a
BaseException. Firstly it ensures that a platform exists, and secondly that its injector
has a provider for the token specified as a parameter.
export function assertPlatform(requiredToken: any): PlatformRef {
const platform = getPlatform();
if (!platform) {
throw new Error('No platform exists!');
}
if (!platform.injector.get(requiredToken, null)) {
throw new Error(
'A platform with a different configuration has been created.
Please destroy it first.');
}
return platform;
}
52 5:@Angular/Core
The run mode specifies whether the platform is is production mode or developer
mode. By default, it is in developer mode:
let _devMode: boolean = true;
let _runModeLocked: boolean = false;
To determine which mode is active, call isDevMode(). This always returns the same
value. In other words, whatever mode is active when this is first call, that is the mode
that is always active.
export function isDevMode(): boolean {
_runModeLocked = true;
return _devMode;
}
Its has getters for component types and components. It main method is bootstrap(),
which is a generic method with a C type parameter - which attaches the component to
DOM elements and sets up the application for execution. Note that bootstrap’s
parameter is a union type, it represents either a ComponentFactory or a Type, both of
which take C as a type parameter.
Attached views are those that are not attached to a view container and are subject to
dirty checking. Such views can be attached and detached, and a count returned.
One implementation of ApplicationRef is supplied, called ApplicationRef_. This is
marked as Injectable(). It maintains the following fields:
Source 53
this._zone.onMicrotaskEmpty.subscribe(
{next: () => { this._zone.run(() => { this.tick(); }); }});
}
Its bootstrap() implementation passes some code to the run function (to run in the
zone) and this code calls componentFactory.create() to create the component and
then _loadComponent().
bootstrap<C>(
componentOrFactory: ComponentFactory<C>|Type<C>): ComponentRef<C> {
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.');
}
let componentFactory: ComponentFactory<C>;
if (componentOrFactory instanceof ComponentFactory) {
componentFactory = componentOrFactory;
} else {
componentFactory = this._componentFactoryResolver
.resolveComponentFactory(componentOrFactory);
}
this._rootComponentTypes.push(componentFactory.componentType);
const compRef = componentFactory.create(
this._injector, [], componentFactory.selector);
compRef.onDestroy(() => { this._unloadComponent(compRef); });
const testability = compRef.injector.get(Testability, null);
if (testability) {
compRef.injector.get(TestabilityRegistry)
.registerApplication(compRef.location.nativeElement, testability);
}
this._loadComponent(compRef);
if (isDevMode()) {
this._console.log(
`Angular is running in the development mode. Call
enableProdMode() to enable the production mode.`);
54 5:@Angular/Core
}
return compRef;
}
forward refs are placeholders used to faciliate out-of-sequence type declarations. The
forward_ref.ts file defines an interface and two functions:
export interface ForwardRefFn { (): any; }
core/src/metadata
Think of metadata as little nuggets of information we would like to attach to other
things. The core/src/metadata.ts file exports a variety of types from the
core/src/metadata sub-directory.
The source files in core/src/metadata are:
● di.ts
● directives.ts
● lifecycle_hooks.ts
● ng_module.ts
● view.ts
The di.ts file defines a range of metadata classes derived from DependencyMetadata.
First is defines AttributeMetadata (uses attribute name) and QueryMetadata (uses
selector) that derive directly from DependencyMetadata; then it defines
ContentChidrenMetadata, ContentChildMetadata and ViewQueryMetadata – all
three of which derive from QueryMetadata, and then it defines
ViewChildrenMetadata and ViewChildMetadata, both of which derive from
ViewQueryMetadata.
The view.ts file defines an enum, a var and a class. The ViewEncapsulation enum is
defined as:
export enum ViewEncapsulation {
Emulated,
Native,
None
}
These represent how template and style encapsulation should work. None means don’t
use encapsulation, Native means use what the renderer offers (specifically the
Shadow DOM) and Emulated is best explained by the comment:
/**
* Emulate `Native` scoping of styles by adding an attribute containing
* surrogate id to the Host Element and pre-processing the style rules
* provided via ViewMetadata#styles or ViewMetadata#stylesUrls, and adding
* the new Host Element attribute to all selectors.
*
* This is the default option.
*/
styleUrls: string[];
styles: string[];
directives: Array<Type|any[]>;
pipes: Array<Type|any[]>;
encapsulation: ViewEncapsulation;
animations: AnimationEntryMetadata[];
interpolation: [string, string];
constructor(..){} // just initialize above
The directives.ts file exports classes related to directive metadata. They include:
● DirectiveMetadata (derives from InjectableMetadata)
● ComponentMetadata (derives from DirectiveMetadata)
● PipeMetadata (derives from InjectableMetadata)
● ImputMetadata
● OutputMetadata
● HostBindingMetadata
● HostListenerMetadata
The lifecycle_hooks.ts file defines an enum, an interface, a var and some simple
abstract classes. The enum is:
export enum LifecycleHooks {
OnInit,
OnDestroy,
DoCheck,
OnChanges,
AfterContentInit,
AfterContentChecked,
AfterViewInit,
AfterViewChecked
}
The abstract classes define the method signatures for handlers that application
component interest in the lifecycle hooks must implement:
export abstract class OnChanges {
abstract ngOnChanges(changes: SimpleChanges): void; }
export abstract class OnInit { abstract ngOnInit(): void; }
export abstract class DoCheck { abstract ngDoCheck(): void; }
Source 59
core/src/profile
The profiel directory has two files:
● profile.ts
● wtf_init.ts
● wtf_impl.ts
WTF is the Web Tracing Framework:
● http://google.github.io/tracing-framework/
“The Web Tracing Framework is a collection of libraries, tools, and scripts
aimed at web developers trying to write large, performance-sensitive
Javascript applications. It's designed to be used in conjunction with the
built-in development tools of browsers but goes far beyond what they
usually support at the cost of some user setup time.”
from: http://google.github.io/tracing-framework/overview.html
wtf_init.ts has this noop:
export function wtfInit() {}
core/src/reflection
The reflection directory has these files:
● platform_reflection_capabilities.ts
● reflection.ts
Source 61
● reflection_capabilities.ts
● reflector.ts
● reflector_reader.ts
● types.ts
The reflector_reader.ts file defines the RelectorRader interface:
export abstract class ReflectorReader {
abstract parameters(typeOrFunc: /*Type*/ any): any[][];
abstract annotations(typeOrFunc: /*Type*/ any): any[];
abstract propMetadata(typeOrFunc: /*Type*/ any): {[key: string]: any[]};
abstract importUri(typeOrFunc: /*Type*/ any): string;
}
Layering for angular applications involves your application code talking to the Angular
framework, which is layered into an application layer and a renderer layer, with a
renderer API in between. The core/src/render/api.ts file defines this thin API and
nothing else. The API consists of four classes - RenderDebugInfo,
RenderComponentType, Renderer and RootRenderer. Implementation of this API is
not part of core (though core/src/debug/debug_renderer.ts provides a debugging
wrapper for it). Instead, the various platform modules need to provide the actual
implementations for different scenarios.
Angular 2
Renderer API (api.ts)
Renderer Implementations
(various – from platform-* modules)
A notable characteristic of the Renderer API is that, even though it is defined in terms
of elements, it does not list anywhere what those elements are. Elements are
identified in terms of string names, but what are valid names is not part of the
renderer. Instead, there is an element schema registry defined in the template
compiler (@angular/compiler/src/schema) and we will examine it further when looking
at the compiler.
Now we can move on to looking at the renderer API. RenderDebugInfo is used to
provide debugging context information and is defined as:
export abstract class RenderDebugInfo {
get injector(): Injector { return unimplemented(); }
get component(): any { return unimplemented(); }
get providerTokens(): any[] { return unimplemented(); }
get references(): {[key: string]: any} { return unimplemented(); }
get context(): any { return unimplemented(); }
get source(): string { return unimplemented(); }
}
The RootRenderer class is used to register a provider with dependency injection and
is defined as:
export abstract class RootRenderer {
abstract renderComponent(componentType: RenderComponentType): Renderer;
}
The result of this is that different root renderers can be supplied via dependency
injection for differing scenarios, and client code using the renderer API can use a
suitable implementation. If that is how the RootRenderer gets into dependency
injection system, then of course the next question is, how does it get out?
Surprisingly, there is no call to injector.get(RootRenderer) in the codebase.
Instead, the ViewUtils class (in @angular/core/src/linker/view-utils.ts) is also
registered with dependency injection and it takes a RootRenderer as a constructor
parameter. ViewUtils is marked as @injectable, so when a request is made to the
injector for a ViewUtils, then when it is constructed (in
@angular/core/src/linker/component_factory.ts) it is supplied with a RootRenderer
automatically by the injector:
export class ComponentFactory<C> {
...
create(
injector: Injector, projectableNodes: any[][] = null,
rootSelectorOrNode: string|any = null): ComponentRef<C> {
var vu: ViewUtils = injector.get(ViewUtils);
if (isBlank(projectableNodes)) {
projectableNodes = [];
}
// Note: Host views don't need a declarationAppElement!
var hostView = this._viewFactory(vu, injector, null);
var hostElement = hostView.create(
EMPTY_CONTEXT, projectableNodes, rootSelectorOrNode);
return new ComponentRef_<C>(hostElement, this._componentType);
}
}
When we examine code generated by the Angular template compiler, it defines a class
derived from AppView and has many calls to the renderer. Here is a sample of Angular
generated code from https://github.com/thelgevold/angular2-offline-compiler:
const parentRenderNode:any =
this.renderer.createViewRoot(this.declarationAppElement.nativeElement);
this._el_0 = this.renderer.createElement(parentRenderNode,'h1',null);
this._text_1 = this.renderer.createText(this._el_0,'',null);
this._text_2 = this.renderer.createText(parentRenderNode,'\n\n',null);
this._el_3=this.renderer.createElement(parentRenderNode,'treeview',null);
Source 65
We’ll talk more about the actual use of the rendering API when examining view-
related code in core/src/linker.
Now we’ll move on to the principal class in the Renderer API, Renderer, which is
abstract and declares the following methods:
Here only the interface is being defined – for actual implementation, refer to the
various platform renderers in the different platform modules. The renderer is a simple
66 5:@Angular/Core
abstraction, quite suitable for a variety of rendering engines. Let’s look at four
methods:
abstract createElement(
parentElement: any, name: string, debugInfo?: RenderDebugInfo): any;
abstract setElementProperty(
renderElement: any, propertyName: string, propertyValue: any): void;
abstract listen(
renderElement: any, name: string, callback: Function): Function;
abstract invokeElementMethod(
renderElement: any, methodName: string, args?: any[]): void;
The elements, their properties and their methods are identified by name (strings). The
list of arguments to invokeElementMethod() is of type any, the propertyValue for
setElementProperty() is any. The parentElement for createElement() is any. The
listen() function attaches a callback to an event associated with an element.
core/src/debug
This directory contains two files:
● debug_node.ts
● debug_renderer.ts
The debug_node.ts file implements EventListener, DebugNode and DebugElement
classes along with some helper functions. EventListener stores a name and a
function, to be called after events are detected:
export class EventListener {
constructor(public name: string, public callback: Function){}; }
The debug node at attached as a child to the parent DebugNode. The nativeNode to
which this DebugNode refers to is recorded. A private field, _debugInfo records the
RenderDebugInfo (defined in core/src/render/api.ts) to be stored in this DebugNode.
The DebugElement class extends DebugNode and supplies a debugging representation
of an element.
export class DebugElement extends DebugNode {
name: string;
Source 67
Errored,
Destroyed,
}
core/src/change_detection/differs
● default_iterable_differ.ts
● default_keyvalue_differ.ts
● iterable_differs.ts
● keyvalue_differs.ts
TBD
70 5:@Angular/Core
core/src/zone
This directory contains these files:
● ng_zone.ts
● ng_zone_impl.dart
● ng_zone_impl.ts
The small zone.js library (https://github.com/angular/zone.js) provides a way of
managing execution context when using asynchronous tasks. Angular has a
dependency on it, and we see its use in core/src/zone.
The ng_zone_impl.ts file implements the NgzoneImpl class. It has inner and outer
Zone fields:
private outer: Zone;
private inner: Zone;
It’s constructor is when the zones are configured. It starts by setting both outer and
inner zones to the current zone.
this.outer = this.inner = Zone.current;
Regardless of the result of above, it always forks once more, passing parameters:
● name: ‘angular’
● properties: <any>{'isAngularZone': true},
● handlers for: onInvokeTask, onInvoke, onHasTask, onHandleError
The ng_zone.ts file defines the NgZone class. This provides simple implementations of
the following properties:
get onUnstable(): EventEmitter<any> { return this._onUnstable; }
get onMicrotaskEmpty(): EventEmitter<any> { return this._onMicrotaskEmpty; }
get onStable(): EventEmitter<any> { return this._onStable; }
get onError(): EventEmitter<any> { return this._onErrorEvents; }
get isStable(): boolean { return this._isStable; }
get hasPendingMicrotasks(): boolean { return this._hasPendingMicrotasks; }
get hasPendingMacrotasks(): boolean { return this._hasPendingMacrotasks; }
Source 71
findTestabilityInTree(registry: TestabilityRegistry,
elem: any, findInAncestors: boolean): Testability;
}
Which is set
export function setTestabilityGetter(getter: GetTestability): void {
_testabilityGetter = getter;
}
Now let’s look at how testabillity is tied in with the rest of the framework. Recall that
PLATFORM_COMMON_PROVIDERS (core/src/platform_common_providers.ts) is defined as:
export const PLATFORM_COMMON_PROVIDERS: Array<any|Type|Provider|any[]> = [
..
TestabilityRegistry,
..
];
The Compiler class is the base class for compilers, and it is these derived classes
where the actual template compilation occurs.
export class Compiler {
get injector(): Injector {
throw new BaseException(`Runtime compiler is not loaded.
Tried to read the
injector.`);
}
compileComponentAsync<T>(component: ConcreteType<T>)
: Promise<ComponentFactory<T>> {
throw new BaseException(
`Runtime compiler is not loaded. Tried to compile
${stringify(component)}`);
}
compileComponentSync<T>(component: ConcreteType<T>): ComponentFactory<T> {
Source 75
We will examine that class in detail when covering the compiler module. That is the
actual compiler that our code uses. The same file has:
export const RUNTIME_COMPILER_FACTORY = new _RuntimeCompilerFactory();
When covering the platform-browser-dynamic module, we will see this being used to
define BROWSER_DYNAMIC_COMPILER_FACTORY:
export const BROWSER_DYNAMIC_COMPILER_FACTORY =
RUNTIME_COMPILER_FACTORY.withDefaults(
{providers: [{provide: XHR, useClass: XHRImpl}]});
];
<application code>
bootstrap (platform-browser-dynamic)
bootstrapModule (platform-browser-dynamic)
platformBrowserDynamic (platform-browser-dynamic)
INTERNAL_BROWSER_DYNAMIC_PLATFORM_PROVIDERS
(platform-browser-dynamic)
BROWSER_DYNAMIC_COMPILER_FACTORY (platform-browser-dynamic)
RUNTIME_COMPILER_FACTORY (compiler)
The exceptions.ts file defines exceptions used by the template compilation process:
export class ExpressionChangedAfterItHasBeenCheckedException
extends BaseException {
constructor(oldValue: any, currValue: any, context: any) {
let msg =
`Expression has changed after it was checked. Previous value:
'${oldValue}'. Current value: '${currValue}'.`;
if (oldValue === UNINITIALIZED) {
msg += ` It seems like the view has been created after its parent ` +
` and its children have been dirty checked.` +
` Has it been created in a change detection hook ?`;
}
super(msg);
Source 77
}
}
The view_ref.ts file defines the ViewRef and EmbeddedViewRef abstract classes, and
defines the ViewRef_ concrete class.
ViewRef is defined as:
export abstract class ViewRef {
get destroyed(): boolean { return <boolean>unimplemented(); }
this._originalMode = this._view.cdMode;
}
destroy() { this._view.destroy(); }
}
The view_container_ref.ts file defines the abstract ViewContainerRef class and the
concrete ViewContainerRef_ class. ViewContainerRef is a container for views. It
defines four getters – element (for the anchor element of the container), injector,
parentInjector and length (number of views attached to container), that in the default
implementation all throw unimplemented exceptions. It defines two important create
methods – createEmbeddedView and createComponent, which create the two variants
of views supported. Finally, it has a few helper methods – clear, get, insert, indexOf,
remove and detach – which work on the views within the container.
export abstract class ViewContainerRef {
/** @internal */
_createComponentInContainerScope: WtfScopeFn =
wtfCreateScope('ViewContainerRef#createComponent()');
createComponent<C>(componentFactory: ComponentFactory<C>,
index: number = -1, injector: Injector = null,
projectableNodes: any[][] = null): ComponentRef<C> {
var s = this._createComponentInContainerScope();
var contextInjector =
isPresent(injector) ? injector : this._element.parentInjector;
var componentRef = componentFactory.create(
contextInjector, projectableNodes);
this.insert(componentRef.hostView, index);
return wtfLeave(s, componentRef);
}
80 5:@Angular/Core
The abstract class is AppView. When the Angular template compiler is generating code
for your templates, it creates an outer AppView with ViewType set to ViewType.HOST,
and an inner AppView with ViewType set to VieType.COMPONENT.
AppView
ViewRef
AppElement
ViewType (HOST, COMPONENT, EMBEDDED)
ViewUtils
VieContainerRef
Source 81
The template_ref.ts file defines the TemplateRef abstract class and the
TemplateRef_ concrete class.
export abstract class TemplateRef<C> {
get elementRef(): ElementRef { return null; }
abstract createEmbeddedView(context: C): EmbeddedViewRef<C>;
Source 83
constructor(
public index: number,
public parentIndex: number,
public parentView: AppView<any>,
public nativeElement: any) {}
over the array (factories) and for each item, adds an entry to the map (_factories)
that maps the factory’s componentType to the factory.
In its resolveComponentFactory() method, CodegenComponentFactoryResolver
looks up the map for a matching factory, and if present, returns it, otherwise returns
the result of a call to the parent’s resolveComponentFactory.
The app_module_factory_loader.ts file defines the AppModuleFactoryLoader abstract
class as:
export abstract class AppModuleFactoryLoader {
abstract load(path: string): Promise<AppModuleFactory<any>>;
}
return (<any>global)
.System.import(module)
.then((module: any) => module[exportName])
.then((type: any) => checkNotEmpty(type, module, exportName))
.then((type: any) => this._compiler.compileAppModuleAsync(type));
}
return (<any>global)
.System.import(module + FACTORY_MODULE_SUFFIX)
.then((module: any) => module[exportName + FACTORY_CLASS_SUFFIX])
.then((factory: any) => checkNotEmpty(factory, module, exportName));
}
}
}
6: @Angular/Common
Overview
The Common package builds on top of the Core package and adds some shared
functionality in areas such as directives, location and pipes.
@Angular/Common API
The exported API of the @Angular/Common package can be represented as:
PipeTransform
Core
Common Type Exports
LowerCasePipe TitleCasePipe
NgLocalization
JsonPipe SlicePipe
CommonModule
I18nPluralPipe PercentPipe
Used in CommonModule’s
NgModule decorator
CurrencyPipe I18nSelectPipe
NgLocaleLocalization
DatePipe
COMMON_PIPES
Location Platform
COMMON_ Strategy Location
DIRECTIVES
Location
ChangeEvent
PathLocation
Const Export
Location
Common
Strategy
BASE_APP_HREF ChangeListener
HashLocation Location
Strategy
@Angular/Common API 89
Note that AsyncPipe, unlike all the other pipes, does not implement the
PipeTransform interface. However, it does implement the transform() method.
PipeTransform is defined in:
● <ANGULAR-MASTER>/modules/@angular/core/src/change_detection/pipe_transform.ts
as:
export
interface PipeTransform { transform(value: any, ...args: any[]): any; }
NgClass NgFor
Common Type Exports
NgIf NgPlural
NgPluralCase NgStyle
NgSwitch NgSwitchCase
NgSwitchDefault NgTemplateOutlet
"@angular/core": ["../../../dist/packages-dist/core"]
},
"rootDir": ".",
"sourceMap": true,
"inlineSources": true,
"target": "es5",
"skipLibCheck": true,
"lib": [ "es2015", "dom" ]
},
"files": [
"index.ts",
"../../../node_modules/zone.js/dist/zone.js.d.ts"
],
"angularCompilerOptions": {
"annotateForClosureCompiler": true,
"strictMetadataEmit": true
}
}
Source
common/src
The common/src directory has these source files:
● common.ts
● common_module.ts
● localization.ts
● version.ts
The common_module.ts file defines the CommonModule type, which contains common
declarations, exports and providers:
@NgModule({
declarations: [COMMON_DIRECTIVES, COMMON_PIPES],
exports: [COMMON_DIRECTIVES, COMMON_PIPES],
providers: [
{provide: NgLocalization, useClass: NgLocaleLocalization},
],
})
export class CommonModule {}
The localization.ts file provides localization functionality, mainly in the area of plurals.
It defines the NgLocalization abstract class, the NgLocaleLocalization concrete
class and the getPluralCategory() function.
export abstract class NgLocalization {
abstract getPluralCategory(value: any): string;
}
As we have just seen with CommonModule, this is what its NgModule decorator uses as
the provider for NgLocalization. Its implementation of getPluralCategory() uses
CLDR-based code, generated by this script:
● <ANGULAR-MASTER>/script/cldr/gen_plural_rules.js
Cldr
Cldr stands for Unicode’s Common Locale Data Repository:
● http://http://cldr.unicode.org/
and is described as:
“The Unicode CLDR provides key building blocks for software to support
the world's languages, with the largest and most extensive standard
repository of locale data available.”
The cldr npm package:
● https://www.npmjs.com/package/cldr
is described as:
“A module that allows you to extract a bunch of locale-specific information from the
Unicode CLDR (Common Localization Data Repository), including:
● Date, time, and date-time formats
● Date interval formats
● Number formats, symbols, and digits for all number systems
● Exemplar and ellipsis characters
● Day names, month names, quarter names, era names, and cyclic names
● Patterns for rendering lists of items
● Display names for languages, time zones, territories, scripts and currencies
● Plural rule functions (converted to JavaScript functions)
● Rule-based number formatting functions (converted to JavaScript functions)”
The gen_plural_rules.js script at:
● <ANGULAR-MASTER>/scripts/cldr/cldr.js
Source 93
uses the cldr npm package to general plural information and places it in:
<ANGULAR-MASTER>/modules/@angular/common/src/localization.ts
common/src/directives
This directory has the following source files:
● index.ts
● ng_class.ts
● ng_for.ts
● ng_if.ts
● ng_plural.ts
● ng_style.ts
● ng_switch.ts
● ng_template_outlet.ts
The indexs.ts file exports the directive types, along with a definition for
COMMON_DIRECTIVES, which is:
export const COMMON_DIRECTIVES: Provider[] = [
NgClass,
NgFor,
NgIf,
NgTemplateOutlet,
NgStyle,
NgSwitch,
NgSwitchCase,
NgSwitchDefault,
NgPlural,
NgPluralCase,
];
constructor(
private _viewContainer: ViewContainerRef,
templateRef: TemplateRef<NgIfContext>) {
this._thenTemplateRef = templateRef;
}
we observe that the NgIf class does not derive from any other class. It is made into a
directive by using the Directive decorator.
94 6:@Angular/Common
Then we see it has four setters defined as input properties, for the variations of if:
@Input()
set ngIf(condition: any) {
this._context.$implicit = condition;
this._updateView();
}
@Input()
set ngIfThen(templateRef: TemplateRef<NgIfContext>) {
this._thenTemplateRef = templateRef;
this._thenViewRef = null; // clear previous view if any.
this._updateView();
}
@Input()
set ngIfElse(templateRef: TemplateRef<NgIfContext>) {
this._elseTemplateRef = templateRef;
this._elseViewRef = null; // clear previous view if any.
this._updateView();
}
Finally it has an internal method, _updateView, where the view is changed as needed:
private _updateView() {
if (this._context.$implicit) {
if (!this._thenViewRef) {
this._viewContainer.clear();
this._elseViewRef = null;
if (this._thenTemplateRef) {
this._thenViewRef =
this._viewContainer.createEmbeddedView(
this._thenTemplateRef, this._context);
}
}
} else {
if (!this._elseViewRef) {
this._viewContainer.clear();
this._thenViewRef = null;
if (this._elseTemplateRef) {
this._elseViewRef =
this._viewContainer.createEmbeddedView(
this._elseTemplateRef, this._context);
}
}
}
}
}
The first thing to note about NgFor’s implementation is the class implements DoCheck
and OnChanges lifecycle.
@Directive({selector: '[ngFor][ngForOf]'})
export class NgFor implements DoCheck, OnChanges { }
Hence we would expect NgFor to provide ngDoCheck and ngOnChanges methods and it
does. ngDoCheck() calls _applyChanges, where for each change operation a call to
viewContainer.createEmbeddedView() is made.
common/src/location
This directory contains these source files:
● hash_location_strategy.ts
● location.ts
● location_strategy.ts
● path_location_strategy.ts
● platform_location.ts
The location_strategy.ts file defines the LocationStrategy class and the
APP_BASE_HREF opaque token.
export abstract class LocationStrategy {
abstract path(includeHash?: boolean): string;
abstract prepareExternalUrl(internal: string): string;
abstract pushState(
state: any, title: string, url: string, queryParams: string): void;
abstract replaceState(
state: any, title: string, url: string, queryParams: string): void;
abstract forward(): void;
abstract back(): void;
abstract onPopState(fn: LocationChangeListener): void;
abstract getBaseHref(): string;
}
96 6:@Angular/Common
The Location class does not extend any other class, and its constructor only takes a
LocationStrategy as as parameter:
@Injectable()
export class Location {
/** @internal */
_subject: EventEmitter<any> = new EventEmitter();
/** @internal */
_baseHref: string;
/** @internal */
_platformStrategy: LocationStrategy;
constructor(platformStrategy: LocationStrategy) {
this._platformStrategy = platformStrategy;
const browserBaseHref = this._platformStrategy.getBaseHref();
this._baseHref =
Location.stripTrailingSlash(_stripIndexHtml(browserBaseHref));
this._platformStrategy.onPopState((ev) => {
this._subject.emit({
Source 97
'url': this.path(true),
'pop': true,
'type': ev.type,
});
});
}
One useful method is subscribe(), which allows your application code to be informed
of popState events:
subscribe(
onNext: (value: any) => void,
onThrow: (exception: any) => void = null,
onReturn: () => void = null): Object {
return this._subject.subscribe(
{next: onNext, error: onThrow, complete: onReturn});
}
common/src/pipes
This sub-directory contains these source files:
● async_pipe.ts
● case_conversion_pipes.ts
● date_pipe.ts
● i18n_plural_pipe.ts
● i18n_select_pipe.ts
● invalid_pipe_argument_error.ts
● json_pipe.ts
● number_pipe.ts
● slice_pipe.ts
All the pipe classes are marked with the Pipe decorator. Apart from AsyncPipe, all the
other pipes implement the PipeTransform interface (and even AsyncPipe implements
the transform method). As an example, slice_pipe.ts has the following:
@Pipe({name: 'slice', pure: false})
export class SlicePipe implements PipeTransform {
transform(value: any, start: number, end?: number): any {
if (isBlank(value)) return value;
if (!this.supports(value)) {
throw new InvalidPipeArgumentError(SlicePipe, value);
}
return value.slice(start, end);
}
private supports(obj: any): boolean {
return typeof obj === 'string' || Array.isArray(obj); }
}
COMMON_PIPES (in index.ts) lists the defined pipes and will often be used when
creating components.
export const COMMON_PIPES = [
AsyncPipe,
UpperCasePipe,
LowerCasePipe,
JsonPipe,
98 6:@Angular/Common
SlicePipe,
DecimalPipe,
PercentPipe,
TitleCasePipe,
CurrencyPipe,
DatePipe,
I18nPluralPipe,
I18nSelectPipe,
];
Overview
A platform module is how an application interacts with its hosting environment. Duties
of a platform include rendering (deciding what is displayed and how), multitasking
(webworkers), security sanitization of URLs/html/style (to detect dangerous
constructs) and location management (the URL displayed in the browser).
We have seen how the Core module provides a rendering API, but it includes no
implementations of renderers and no mention of the DOM. All other parts of Angular
that need to have content rendered talk to this rendering API and rely on an
implementation to actually deal with the content to be “displayed” (and what
“displayed” means varies depending on the platform). You will find an implementation
of renderer in the platform modules – the main one is DomRenderer. Note that this
renders to a DOM adapter (and multiple of those exist), but Core and all the features
sitting on top of Core only know about the rendering API, not the DOM.
Angular supplies five platform modules: platform-browser (runs in the browser’s main
UI thread and uses the offline template compiler), platform-browser-dynamic (runs in
the browser’s main UI thread and uses the runtime template compiler), platform-
webworker (runs in a webworker and uses the offline template compiler), platform-
webworker-dynamic (runs in a webworker and uses the runtime template compiler)
and platform-server (runs in the server and can uses either the offline or runtime
template compiler). Shared functionality relating to platforms is in platform-browser
and imported by the other platform modules. So platform-browser is a much bigger
module compared to the other platform modules.
In this chapter we will explore platform-browser and we will cover the others in the
subsequent chapters.
Platform-browser is how application code can interact with the browser when running
in the main UI thread and assuming the offline template compiler has been used to
pre-generate a module factory. For production use, platform-browser is likely to be
100 7:@Angular/Platform-Browser
the platform of choice, as it results in the fastest display (no in-browser template
compilation needed) and smallest download size (the Angular template compiler does
not have to be downloaded to the browser).
There is exactly one platform instance per thread (main browser UI thread or
webworker). Multiple applications may run in the same thread, and they interact with
the same platform instance.
Platform
<HEAD> bootstrapModule()
<title ..>
</HEAD>
bootstrapModule()
<BODY> App 1
<app1 />
App 2 bootstrapModule()
<p>content</p>
<app2 />
<h1>a title</h1> App 3
<app3 />
</BODY>
index.html code
Platform-Browser API
The platform-browser API can be sub-divided into four functional areas:
browser-related, DOM, secruity and webworkers. The DOM related API can be
represented as:
Platform-Browser API 101
EventManager By AnimationDriver
Class Exports
NgProbeToken HammerGestureConfig
HAMMER_GESTURE_CONFIG
Opaque
Exports
Token
DOCUMENT EVENT_MANAGER_PLUGINS
Hammer is for touch input. Event Manager handles the flow of events. NgProbeToken
is used for debugging and is exposed in the browser’s developer tools.
Two important classes - DomRenderer and DomAdapter - are not publically exported.
Instead, they are registered with the dependency injector and that is how they are
made available to application code (we will shortly examine both in detail).
The browser-related API can be represented as:
platformBrowser [disable|enable]DebugTools
Sanitizer
Interface
SafeHtml SafeResourceUrl
Exports
Class
DomSanitizer
Source
platform-browser/src
This directory contains the following file:
● browser.ts
The browser.ts file defines provider lists and the NgModule-decorated BrowserModule,
all important in the bootstrapping of an Angular application.
Sanitization providers mitigate XSS risks:
/**
* @security Replacing built-in sanitization providers exposes the
* application to XSS risks. Attacker-controlled data introduced by an
* unsanitized provider could expose your application to XSS risks. For more
* detail, see the [Security Guide](http://g.co/ng/security).
*/
export const BROWSER_SANITIZATION_PROVIDERS: Array<any> = [
{provide: Sanitizer, useExisting: DomSanitizer},
{provide: DomSanitizer, useClass: DomSanitizerImpl},
104 7:@Angular/Platform-Browser
];
Platform-browser/src/dom
We will sub-divide the code in the DOM sub-directory into four categories –
adapter/renderer, animation, debug and events.
We have seen how the Core package defines a rendering API and all other parts of the
Angular framework and applicaton code uses it to have content rendered. But Core
has no implementation. Now it is time to see an implementation, based on the DOM.
That is the role of these files:
● dom_tokens.ts
● shared_styles_host.ts
● util.ts
● dom_adapter.ts
● dom_renderer.ts
dom_tokens.ts declares an opaque token:
/**
* A DI Token representing the main rendering context.
* In a browser this is the DOM Document.
*
* Note: Document might not be available in the Application Context when
* Application and Rendering Contexts are not the same (e.g. when running
* the application into a WebWorker).
*/
export const DOCUMENT: OpaqueToken = new OpaqueToken('DocumentToken');
The two main files involved in delivering the DomRenderer are dom_adapter.ts and
dom_renderer.ts. A DomAdapter is a class that represents an API very close to the
HTML DOM that every web developer is familiar with. A DOM renderer is an
implementation of Core’s Rendering API in terms of a DOM adapter.
The benefit that a DomAdapter brings (compared to hard-coding calls to the actual
DOM inside a browser), is that multiple implementations of a DomAdapter can be
supplied, including in scenarios where the real DOM is not available (e.g. server-side,
or inside webworkers).
The following diagram shows how Core’s Renderer API, renderer implementations and
DOM adapters are related. For many applications, the entire application will run in the
106 7:@Angular/Platform-Browser
Angular 2 Rendering
RootRenderer Renderer
Core
{ implements } { implements }
{ implements }
DebugDomRootRenderer
{ implements }
{uses }
WebWorker
WebWorker
RootRenderer
Renderer
renderComponent()
Platform-Browser
{ returns }
DomRootRenderer
GenericBrowser
DomAdapter { sets }
WorkerDomAdapter
Browser
DomAdapter
{ calls } setRootDomAdapter() setDOM()
Platform
-Server
Parse5DomAdapter
MessageBasedRenderer
108 7:@Angular/Platform-Browser
The DomAdapter class is a very long list of methods similar to what you would find a
normal DOM API – here is a sampling of what it contains:
/**
* Provides DOM operations in an environment-agnostic way.
*
* @security Tread carefully! Interacting with the DOM directly
* is dangerous and can introduce XSS risks.
*/
export abstract class DomAdapter {
abstract createEvent(eventType: string): any;
abstract getInnerHTML(el: any /** TODO #9100 */): string;
abstract getOuterHTML(el: any /** TODO #9100 */): string;
abstract insertBefore(el: any, node: any): any;
abstract insertAllBefore(el: any , nodes: any ): any;
abstract insertAfter(el: any, node: any): any;
abstract setInnerHTML(el: any , value: any): any;
abstract getText(el: any): string;
abstract setText(el: any, value: string): any;
abstract getValue(el: any): string;
abstract setValue(el: any, value: string): any;
abstract createElement(tagName: any, doc?: any): HTMLElement;
abstract createElementNS(ns: string, tagName: string, doc?: any): Elemen
abstract createShadowRoot(el: any): any;
abstract getShadowRoot(el: any): any;
abstract hasAttribute(element: any, attribute: string): boolean;
abstract getAttribute(element: any, attribute: string): string;
abstract setAttribute(element: any, name: string, value: string): any;
abstract getTitle(): string;
abstract setTitle(newTitle: string): any;
abstract elementMatches(n: any, selector: string): boolean;
}
The dom_renderer.ts file defines the DOM renderer that relies on getDOM() to supply
a Dom Adapter. It supplies these classes – DomRootRenderer, DomRootRenderer_ and
DomRenderer. We saw earlier how DomRootRenderer_ is used in the NgModule
declaration:
@NgModule({
providers: [
..
{provide: DomRootRenderer, useClass: DomRootRenderer_},
{provide: RootRenderer, useExisting: DomRootRenderer},
.. })
export class BrowserModule { .. }
constructor(
public document: any,
public eventManager: EventManager,
Source 109
The big class is DomRenderer, which implements Renderer, mostly in terms of calls to
a DomAdapter. As an example, let’s look at its createElement() method – we see
how it uses getDOM() to implement the functionality:
createElement(
parent: Element,
name: string,
debugInfo: RenderDebugInfo): Node {
var nsAndName = splitNamespace(name);
var el = isPresent(nsAndName[0]) ?
getDOM().createElementNS(
(NAMESPACE_URIS as any)[nsAndName[0]], nsAndName[1]) :
getDOM().createElement(nsAndName[1]);
if (isPresent(this._contentAttr)) {
getDOM().setAttribute(el, this._contentAttr, '');
}
if (isPresent(parent)) {
getDOM().appendChild(parent, el);
}
return el;
}
getDOM().appendChild(nodesParent,
getDOM().createStyleElement(this._styles[i]));
}
} else {
if (isPresent(this._hostAttr)) {
getDOM().setAttribute(hostElement, this._hostAttr, '');
}
nodesParent = hostElement;
}
return nodesParent;
}
dom_animate_player.ts contains:
export interface DomAnimatePlayer {
cancel(): void;
play(): void;
pause(): void;
finish(): void;
onfinish: Function;
position: number;
currentTime: number;
}
constructor(
@Inject(EVENT_MANAGER_PLUGINS) plugins: EventManagerPlugin[],
112 7:@Angular/Platform-Browser
addEventListener(element: HTMLElement,
eventName: string, handler: Function): Function {
var plugin = this._findPluginFor(eventName);
return plugin.addEventListener(element, eventName, handler);
}
addGlobalEventListener(target: string,
eventName: string, handler: Function): Function {
var plugin = this._findPluginFor(eventName);
return plugin.addGlobalEventListener(target, eventName, handler);
}
/** @internal */
_findPluginFor(eventName: string): EventManagerPlugin {
var plugins = this._plugins;
for (var i = 0; i < plugins.length; i++) {
var plugin = plugins[i];
if (plugin.supports(eventName)) {
return plugin;
}
}
throw new Error(`No event manager plugin found for event ${eventName}`);
}
}
Touch events via the hammer project are supported via the hammer_common.ts and
hammer_gesture.ts files. The list of supported touch events are:
var _eventNames = {
// pan
'pan': true,
'panstart': true,
'panmove': true,
'panend': true,
'pancancel': true,
'panleft': true,
'panright': true,
'panup': true,
'pandown': true,
// pinch
'pinch': true,
'pinchstart': true,
'pinchmove': true,
'pinchend': true,
'pinchcancel': true,
'pinchin': true,
'pinchout': true,
// press
'press': true,
'pressup': true,
// rotate
'rotate': true,
'rotatestart': true,
'rotatemove': true,
'rotateend': true,
'rotatecancel': true,
// swipe
'swipe': true,
'swipeleft': true,
'swiperight': true,
'swipeup': true,
'swipedown': true,
// tap
'tap': true,
};
return true;
}
mc.get('pinch').set({enable: true});
mc.get('rotate').set({enable: true});
return mc;
}
}
Source 115
These event manager plugins need to be set in the NgModule configuration. We see
how this is done in BrowserModule:
@NgModule({
providers: [
..
{provide: EVENT_MANAGER_PLUGINS, useClass: DomEventsPlugin, multi: true},
{provide: EVENT_MANAGER_PLUGINS, useClass: KeyEventsPlugin, multi: true},
{provide: EVENT_MANAGER_PLUGINS, useClass: HammerGesturesPlugin,
multi: true},
{provide: HAMMER_GESTURE_CONFIG, useClass: HammerGestureConfig},
..
],
exports: [CommonModule, ApplicationModule]
})
export class BrowserModule {..}
Platform-browser/src/browser
This directory contains these files:
● generic_browser_adapter.ts
● browser_adapter.ts
● estability.ts
● title.ts
● location/browser_platform_location.ts
● location/history.ts
● tools/common_tools.ts
● tools/tools.ts
generic_browser_adapter.ts defines the abstract GenericBrowserDomAdapter class
that extends DomAdapter with DOM operations suitable for general browsers:
export abstract class GenericBrowserDomAdapter extends DomAdapter{}
/**
* Set the title of the current HTML document.
* @param newTitle
*/
setTitle(newTitle: string) { getDOM().setTitle(newTitle); }
}
The tools sub-directory contains tools.ts and common_tools.ts, which implement the
functions enableDebugTools() and disableDebugTools(), and the AngularTools and
AngularProfiler classes. The classes are defined as follows:
/**
* Entry point for all Angular debug tools.
* This object corresponds to the `ng`
* global variable accessible in the dev console.
*/
export class AngularTools {
profiler: AngularProfiler;
constructor(ref: ComponentRef<any>) {
this.profiler = new AngularProfiler(ref); }
}
/**
* Entry point for all Angular profiling-related debug tools. This object
* corresponds to the `ng.profiler` in the dev console.
*/
export class AngularProfiler {
appRef: ApplicationRef;
constructor(ref: ComponentRef<any>) {
this.appRef = ref.injector.get(ApplicationRef); }
timeChangeDetection(config: any): ChangeDetectionPerfRecord { .. }
}
Source 117
The functions add 1 and remove 2 a ng property to global and are defined as follows:
var context = <any>global;
/**
* Enabled Angular debug tools that are accessible via your browser's
* developer console.
*
* Usage:
* 1. Open developer console (e.g. in Chrome Ctrl + Shift + j)
* 2. Type `ng.` (usually the console will show auto-complete suggestion)
* 3. Try the change detection profiler `ng.profiler.timeChangeDetection()`
* then hit Enter.
*/
export function enableDebugTools<T>(ref: ComponentRef<T>): ComponentRef<T> {
1 context.ng = new AngularTools(ref);
return ref;
}
/**
* Disables Angular tools.
*
* @experimental All debugging apis are currently experimental.
*/
export function disableDebugTools(): void {
2 delete context.ng;
}
This manages two private fields, location and history, which are initialized with
getDOM().
export class BrowserPlatformLocation extends PlatformLocation {
private _location: Location;
private _history: History;
constructor() {
super();
this._init();
}
_init() {
118 7:@Angular/Platform-Browser
this._location = getDOM().getLocation();
this._history = getDOM().getHistory();
}
The rest of the class provides functionality to work with location and history:
getBaseHrefFromDOM(): string { return getDOM().getBaseHref(); }
● url_sanitizer.ts
Security sanitizers help prevent the use of dangerous constructs in HTML, CSS styles
and URLs. Sanitizers are configured via an NgModule setting. This is the relevant
extract from platform-browser/src/browser.ts:
/**
* @security Replacing built-in sanitization providers
*exposes the application to XSS risks.
* Attacker-controlled data introduced by an unsanitized provider could
* expose your application to XSS risks. For more detail, see the
* [Security Guide](http://g.co/ng/security).
*/
export const BROWSER_SANITIZATION_PROVIDERS: Array<any> = [
{provide: Sanitizer, useExisting: DomSanitizer},
{provide: DomSanitizer, useClass: DomSanitizerImpl},
];
@NgModule({
providers: [
BROWSER_SANITIZATION_PROVIDERS,
..
],
exports: [CommonModule, ApplicationModule]
})
export class BrowserModule {..}
The DomSanitizer abstract class implements Core’s Sanitizer class. Do read the large
comment at the beginning – you really do not want to be bypassing security it at all
possible.
/**
* DomSanitizer helps preventing Cross Site Scripting Security bugs (XSS) by
* sanitizing values to be safe to use in the different DOM contexts.
*
* For example, when binding a URL in an `<a [href]="someValue">` hyperlink,
* `someValue` will be sanitized so that an attacker cannot inject e.g. a
* `javascript:` URL that would execute code on the website.
*
* In specific situations, it might be necessary to disable sanitization, for
* example if the application genuinely needs to produce a `javascript:`
* style link with a dynamic value in it.
* Users can bypass security by constructing a value with one of the
* `bypassSecurityTrust...` methods, and then binding to that value from
* the template.
*
* These situations should be very rare, and extraordinary care must be taken
* to avoid creating a Cross Site Scripting (XSS) security bug!
*
* When using `bypassSecurityTrust...`, make sure to call the method as early
* as possible and as close as possible to the source of the value, to make
* it easy to verify no security bug is created by its use.
*
* It is not required (and not recommended) to bypass security if the value
* is safe, e.g. a URL that does not start with a suspicious protocol, or an
* HTML snippet that does not contain dangerous
* code. The sanitizer leaves safe values intact.
*
* @security Calling any of the `bypassSecurityTrust...` APIs disables
* Angular's built-in sanitization for the value passed in. Carefully check
* and audit all values and code paths going into this call. Make sure any
* user data is appropriately escaped for this security context.
* For more detail, see the [Security Guide](http://g.co/ng/security).
*/
export abstract class DomSanitizer implements Sanitizer {
abstract sanitize(context: SecurityContext, value: any): string;
abstract bypassSecurityTrustHtml(value: string): SafeHtml;
abstract bypassSecurityTrustStyle(value: string): SafeStyle;
abstract bypassSecurityTrustScript(value: string): SafeScript;
Source 121
It can be divided into three sections, the checkNotSafeValue method, the sanitize
method and the bypassSecurityTrustXYZ methods. The checkNotSafeValue method
throws an error is the value parameter is an instance of SafeValueImpl:
private checkNotSafeValue(value: any, expectedType: string) {
if (value instanceof SafeValueImpl) {
throw new Error(
`Required a safe ${expectedType}, got a ${value.getTypeName()} ` +
`(see http://g.co/ng/security#xss)`); } }
Overview
The term “dynamic” essentially means use the runtime template compiler and its
absence means use the offline template compiler.
When Angular applications are bootstrapping they need to supply a platform. Those
applications that wish to use runtime template compilation will need to supply a
platform from Platform-Browser-Dynamic. If the application is to run in the browser’s
main thread the platform to use is platformBrowserDynamic. If the application is to
run in a webworker, then the platform to use is platformWorkerAppDynamic from the
@Angular/Platform-WebWorker-Dynamic package.
Platform-Browser-Dynamic API
The exported API of the @Angular/Platform-Browser-Dynamic package can be
represented as:
@Angular/Platform-Browser-Dynamic API
RESOURCE_CACHE_PROVIDER
platformBrowserDynamic
This is how applications that use the runtime template compiler bootstrap a platform.
We will need to see how INTERNAL_BROWSER_DYNAMIC_PLATFORM_PROVIDERS registers
the compiler with dependency injection (it is defined in src/platform_providers.ts).
Creates a platform factory for running the application in a webworker. We note it adds
a single provider configurations to platformCoreDynamic – COMPILER_OPTIONS, and
this uses a ResourceLoader.
Source
src
Apart from the import/export files, this directory contains one file:
● platform_providers.ts
Source 125
constructor() {
super();
this._cache = (<any>global).$templateCache;
if (this._cache == null) {
throw new Error(
'CachedResourceLoader: Template cache was not found in $templateCache.');
}
}
return <Promise<any>>Promise.reject(
'CachedResourceLoader: Did not find cached template for ' + url);
}
}
}
xhr.onload = function() { .. };
xhr.send();
return promise;
}
}
9: @Angular/Platform-WebWorker
Overview
Platform-WebWorkers is used to help applications run inside a webworker and render
to the main UI thread via a message bus.
Platform-WebWorker API
The Platform-WebWorker related API is large and can be represented as:
@Angular/Platform-Webworkers API
Opaque Token
WORKER_SCRIPT PRIMITIVE
Exports
WORKER_UI_STARTABLE_MESSAGING_SERVICE
Provider
WORKER_[UI|APP}_LOCATION_PROVIDERS
Exports
Interface
Exports
MessageBusSource MessageBusSink
{ implements } { implements }
WebWorkerInstance
MessageBus
Class Exports
FnArg ReceivedMessage
{ uses }
UiArguments
ClientMessageBroker ServiceMessageBroker
WorkerAppModule
{ creates }
{ creates }
Function Exports
bootstrapWorkerUi
ClientMessageBrokerFactory
platformWorkerUi
ServiceMessageBrokerFactory
platformWorkerApp
128 9:@Angular/Platform-WebWorker
The src/platfrom-webworker.ts file lists the various exports and defines the
bootstrapWorkerUi function:
import {PlatformRef, Provider} from '@angular/core';
Source
platform-webworker/src
In addition to the import/export files, this directory contains the following files:
● worker_app.ts
● worker_render.ts
worker_app.ts supplies functionality for an application that runs in a worker and
worker_render.ts supplies functionality for the the main UI thread. They communicate
via a message broker layered above a simple message bus.
The platformWorkerApp const creates a platform factory for a worker app:
export const platformWorkerApp =
createPlatformFactory(platformCore, 'workerApp');
Two important helper functions are supplied. createMessageBus creates the message
bus that will supply communications between the main UI thread and the webworker:
export function createMessageBus(zone: NgZone): MessageBus {
let sink = new PostMessageBusSink(_postMessage);
let source = new PostMessageBusSource();
let bus = new PostMessageBus(sink, source);
bus.attachToZone(zone);
return bus;
}
The worker_render.ts file has implementations for the Worker UI. This runs in the
main UI thread and uses the BrowserDomAdapter, which writes to the underlying DOM
API of the browser. PlatformWorkerUi represents a platform factory:
export const platformWorkerUi =
createPlatformFactory(platformCore, 'workerUi',
_WORKER_UI_PLATFORM_PROVIDERS);
It returns a function that makes the browser Dom adatper the current adapter 1; then
gets the worker script from di 2; then calls spawnWebWorker 3; and finally calls
initializeGenericWorkerRenderer 4. The spawnWebWorker function is defined as:
function spawnWebWorker(uri: string, instance: WebWorkerInstance): void {
var webWorker: Worker = new Worker(uri); 1
var sink = new PostMessageBusSink(webWorker); 2
var source = new PostMessageBusSource(webWorker);
var bus = new PostMessageBus(sink, source);
instance.init(webWorker, bus); 3
}
It first creates a new webworker 1; then it create the message bus with its sinka dn
source 2 and finally it calls WebWorkerInstance.init 3 which we have already seen.
The initializeGenericWorkerRenderer function is defined as:
function initializeGenericWorkerRenderer(injector: Injector) {
var bus = injector.get(MessageBus);
let zone = injector.get(NgZone);
bus.attachToZone(zone);
zone.runGuarded(() => {
services.forEach((svc: any) => { svc.start(); }); });
}
It first asks the dependency injector for a message bus and a zone and attached the
bus to the zone. Then it also asks the dependency injector for a list of
WORKER_UI_STARTABLE_MESSAGING_SERVICE (remember we noted it was
configured as a multi provider), and for each service, starts it.
platform-webworker/src/web_workers
The source files for web_workers are provided in thre sub-directories, one of which
has shared messaging functionality use both by the UI side and worker side and the
communication, the second refers only to the UI side and the third only to the worker
side.
● shared/api.ts
● shared/client_message_broker.ts
● shared/message_bus.ts
● shared/messaging_api.ts
● shared/post_message_bus.ts
● shared/render_store.ts
● shared/serialized_types.ts
● shared/serializer.ts
● shared/service_message_broker.ts
● ui/event_dispatcher.ts
● ui/event_serializer.ts
● ui/location_providers.ts
● ui/platform_location.ts
● ui/renderer.ts
● worker/event_deserializer.ts
● worker/location_providers.ts
● worker/platform_location.ts
● worker/renderer.ts
● worker/worker_adapter.ts
Communication between the UI thread and the webworker is handled by a low-level
multi-channel message bus. The shared/messaging_api.ts file defines the names of
the three channels used by Angular:
/**
* All channels used by angular's WebWorker components are listed here.
* You should not use these channels in your application code.
*/
export const RENDERER_CHANNEL = 'ng-Renderer';
export const EVENT_CHANNEL = 'ng-Events';
export const ROUTER_CHANNEL = 'ng-Router';
If you are familiar with TCP/IP, the channel name here serves the same purpose as a
port number – it is needed to multiplex multiple independent message streams on a
single data connection.
Source 133
MessageBusSource MessageBusSink
{ implements } { implements }
MessageBus
{ implements } { implements }
PostMessageBus
sink PostMessageBusSink
PostMessageBusSource source
The message_bus.ts file defines an abstract MessageBus class and two interfaces,
MessageBusSource and MessageBusSink. Let’s look at the interfaces first.
MessageBusSource is for incoming messages. This interface describes the functionality
a message source is expected to supply. It has methods to initialize a channel based
on a string name, to attache dto a zone, and to return an RxJS observable
(EventEmitter) that can be observed in order to read the incoming messages.
export interface MessageBusSource {
/**
* Sets up a new channel on the MessageBusSource.
* MUST be called before calling from on the channel.
* If runInZone is true then the source will emit events inside
* the angular zone. if runInZone is false then the source will emit
* events inside the global zone.
*/
initChannel(channel: string, runInZone: boolean): void;
/**
* Assigns this source to the given zone.
* Any channels which are initialized with runInZone set to true
* will emit events that will be executed within the given zone.
*/
attachToZone(zone: NgZone): void;
/**
* Returns an {@link EventEmitter} that emits every time a message
134 9:@Angular/Platform-WebWorker
/**
* Assigns this sink to the given zone.
* Any channels which are initialized with runInZone set to true
* will wait for the given zone to exit before sending messages.
*/
attachToZone(zone: NgZone): void;
/**
* Returns an {@link EventEmitter} for the given channel
* To publish methods to that channel just call next on
* the returned emitter
*/
to(channel: string): EventEmitter<any>;
}
/**
* Assigns this bus to the given zone.
Source 135
/**
* Returns an {@link EventEmitter} for the given channel
* To publish methods to that channel just call next on the returned
* emitter
*/
abstract to(channel: string): EventEmitter<any>;
}
So far the message bus description has only specified what the functionality that
needs to be supplied. A single implementation is supplied, in post_message_bus.ts,
based on the postMessage API. This defines three classes, PostMessageBusSource,
PostMessageBusSink and PostMessageBus, that implement the above similarly named
types.
A useful private class is supplied called _Channel that keeps track of two pieces of
data:
/**
* Helper class that wraps a channel's {@link EventEmitter} and
* keeps track of if it should run in the zone.
*/
class _Channel {
constructor(
public emitter: EventEmitter<any>,
public runInZone: boolean) {}
}
So we see a channel is nothing more that a name that maps to a _Channel, which is
just an EventEmitter and a boolean (runInZone). PostMessageBusSource manages a
map of these called _channels:
export class PostMessageBusSource implements MessageBusSource {
private _zone: NgZone;
private _channels: {[key: string]: _Channel}
The initChannel method subscribes to the emitter with a next handler that either
(when runnign inside the Angular zone) adds the message to the messageBuffer
where its sending is deferred, or (if running outside the Angualr zone), calls
_sendMessages, to immediately send the message:
initChannel(channel: string, runInZone: boolean = true): void {
if (StringMapWrapper.contains(this._channels, channel)) {
throw new Error(`${channel} has already been initialized`);
}
Source 137
So we saw with initChannel that the subscription to the emitter either calls
_sendMessages immediately or parks the message in a message buffer, for later
transmission. So two questions arise – what triggers that transmission and how does
it work. Well, to answer the second question first, _sendMessages() is also called for
the bulk transmission, from inside the _handleOnEventDone() message:
private _handleOnEventDone() {
if (this._messageBuffer.length > 0) {
this._sendMessages(this._messageBuffer);
this._messageBuffer = [];
}
}
So, what calls handleOnEventDone()? Let’s digress to look at the NgZone class in
● <ANGULAR2>/modules/@angular/core/src/zone/ngzone.ts
which has this getter:
/**
* Notifies when the last `onMicrotaskEmpty` has run and there are no more
* microtasks, which implies we are about to relinquish VM turn.
* This event gets called just once.
*/
get onStable(): EventEmitter<any> { return this._onStable; }
So when the zone has no more work to immedaitely carry out, it emits a message via
onStable. Back to PostMessageBusSink – which has this code, that subscribes to the
onStable event emitter:
attachToZone(zone: NgZone): void {
this._zone = zone;
this._zone.runOutsideAngular(
() => { this._zone.onStable.subscribe(
{next: () => { this._handleOnEventDone(); }}); });
}
With all the hard work done in PostMessageBusSource and PostMessageBusSink, the
implementation of PostMessageBus is quite simple:
@Injectable()
138 9:@Angular/Platform-WebWorker
Message Bus
RENDERER CHANNEL
EVENT CHANNEL
ROUTER CHANNEL
<Custom> CHANNEL
Angular also supplies a richer message broker layered above this simple message bus.
The files client_message_broker.ts and service_message_broker.ts along with a
number of helper files for serialization implement the message broker.
The service_message_broker.ts file defines the ReceivedMessage class that represents
a message:
export class ReceivedMessage {
method: string;
args: any[];
id: string;
type: string;
this.type = data['type'];
}
}
ClientMessageBroker ServiceMessageBroker
{ implements } { implements }
ClientMessageBroker_ ServiceMessageBroker_
@Injectable()
export class ServiceMessageBrokerFactory_ extends ServiceMessageBrokerFactory
{
/** @internal */
_serializer: Serializer;
createMessageBroker(
channel: string, runInZone: boolean = true): ServiceMessageBroker {
this._messageBus.initChannel(channel, runInZone);
return new ServiceMessageBroker_(
this._messageBus, this._serializer, channel);
}
}
Source 141
The service message broker is created based on the supplied message bus, serializer
and channel name. The abstract ServiceMessageBroker class contains just one
abstract class declaration, registerMethod():
export abstract class ServiceMessageBroker {
abstract registerMethod(
methodName: string,
signature: Type<any>[],
method: Function, returnType?: Type<any>): void;
}
constructor(
messageBus: MessageBus,
private _serializer: Serializer,
public channel: any) {
super();
this._sink = messageBus.to(channel);
var source = messageBus.from(channel);
source.subscribe({next: (message: any) => this._handleMessage(message)});
}
It has two private fields, an event emitter _sink and a map from string to function
_methods. In its constructor it subscribes its internal method _ handleMessage to
messageBus.from (this handles incoming messages), and set _sink to messageBus.to
(this will be used to send messages).
_handleMessage() message creates a ReceivedMessage based on the map parameter,
and then if message.method is listed as a supported message in _methods, looks up
_methods for the appropriate function to execute, to handle the message:
private _handleMessage(map: {[key: string]: any}): void {
var message = new ReceivedMessage(map);
if (this._methods.has(message.method)) {
this._methods.get(message.method)(message);
}
}
The next question is how methods get registered in _methods. That is the job of
registerMethod(), whose implementation adds an entry to the _methods map based
on the supplied parameters:
registerMethod(
methodName: string,
signature: Type<any>[],
method: (..._: any[]) => Promise<any>| void,
returnType?: Type<any>): void {
this._methods.set(methodName, (message: ReceivedMessage) => { .. });
142 9:@Angular/Platform-WebWorker
Its key is the methodName supplied as a parameter and its value is an anonymous
method, with a single parameter, message, of type ReceivedMessage, which is defined
as:
(message: ReceivedMessage) => {
var serializedArgs = message.args;
let numArgs = signature === null ? 0 : signature.length;
var deserializedArgs: any[] = ListWrapper.createFixedSize(numArgs);
for (var i = 0; i < numArgs; i++) {
var serializedArg = serializedArgs[i];
1 deserializedArgs[i] =
this._serializer.deserialize(serializedArg, signature[i]);
}
We see at its 1 deserialization occuring with the help of the serializer, and at 2 method
is called with the deserialized arguments, and at 3 we see if a returnType is needed, _
wrapWebWorkerPromise is called, to handle the then of the promise, which emits the
result to the sink:
private _wrapWebWorkerPromise(
id: string, promise: Promise<any>, type: Type<any>): void {
promise.then((result: any) => {
this._sink.emit(
{'type' : 'result',
'value': this._serializer.serialize(result, type), 'id': id});
});
}
}
abstract createMessageBroker(
channel: string, runInZone?: boolean): ClientMessageBroker; }
ClientMessageBroker has a single method, run on service, which calls a method with
the supplied UiArguments on the remote service side and returns a promise with the
return value (if any)
export abstract class ClientMessageBroker {
abstract runOnService(
args: UiArguments, returnType: Type<any>): Promise<any>;
}
constructor(
messageBus: MessageBus, _serializer: Serializer, public channel: any){
super();
this._sink = messageBus.to(channel);
this._serializer = _serializer;
var source = messageBus.from(channel);
source.subscribe(
{next: (message: {[key: string]: any}) => this._handleMessage(message)});
}
It has three fields, _pending, _sink and _serializer. _pending is a map from string to
PromiseCompleter. It is used to keep track of method calls that require a return value
and are outstanding – the message has been set to the service, and the result is
awaited. In its constructor _sink is set to the messageBus.to and serializer set to the
Serializer parameter. Also a source subscription is set for _handleMessage.
144 9:@Angular/Platform-WebWorker
Messages for which a return value is expected have a message id generated for them
via:
It is this string that is the key into the _pending map. We will now see how it is set up
in runOnService and used in _handleMessage. RunOnService is what the client calls
when it wants the service to execute a method. It returns a promise, which is a return
value is required, it is completed when the service returns it.
Let’s first examine runOnService when returnType is null. This creates an array of
serialized arguments in fnArgs 1, sets up an object literal called message with
properties “method” and “args” 2, and then calls _sink.emit(message) 3:
runOnService(args: UiArguments, returnType: Type<any>): Promise<any> {
var fnArgs: any[] /** TODO #9100 */ = [];
if (isPresent(args.args)) {
args.args.forEach(argument => {
if (argument.type != null) {
1 fnArgs.push(
this._serializer.serialize(argument.value, argument.type));
} else {
fnArgs.push(argument.value);
}
});
}
var promise: Promise<any>;
var id: string = null;
if (returnType != null) { .. }
2 var message = {'method': args.method, 'args': fnArgs};
..
3 this._sink.emit(message);
return promise;
}
Things are a little more complex when a return value is required. A promise and a
promise completer are created 1; _generateMessageId() is called 2 to generate a
unique message id for this message; an entry is made into _pending 3, whose key is
the id and whose value is the promise completer. The then of the promise 4 returns
the result 5a (deserialized if needed 5b). Before the message is sent via sink.emit,
the generated message id is attached to the message6.
if (returnType != null) {
1 let completer: PromiseCompleter;
promise =
new Promise((resolve, reject) => { completer = {resolve, reject}; });
2 id = this._generateMessageId(args.method);
Source 145
3 this._pending.set(id, completer);
promise.catch((err) => {
print(err);
completer.reject(err);
});
Three helper files are involved with serialization – render_store.ts, serializer.ts and
serialized_types.ts. The RenderStore class, define in render_store.ts, maps two maps
– the first, _lookupById, from number to any, and the second, lookupByObject, from
any to number. It supplies methods to store and remove objects and serialize and de-
serialize them. The serialized_types.ts file simply has:
// This file contains interface versions of browser types
// that can be serialized to Plain Old JavaScript Objects
export class LocationType {
constructor(
public href: string,
public protocol: string,
public host: string,
public hostname: string,
146 9:@Angular/Platform-WebWorker
The Serializer class is defined in serializer.ts and has methods to serialize and
deserialize. The serialize method is:
@Injectable()
export class Serializer {
constructor(private _renderStore: RenderStore) {}
ClientMessageBroker ServiceMessageBroker
RunOnService() _wrapWebWorkerPromise()
1
_handleMessage() _handleMessage()
3
4 sink sink 2
source source
message bus
The last file in the shared directory is api.ts, which has this one line:
export const ON_WEB_WORKER = new OpaqueToken('WebWorker.onWebWorker');
It is used to store a boolean value with dependency injection, stating whether the
current code is running in a webworker or not. At this point it would be helpful to
review how shared functionality is actually configured with dependency injection. On
the worker side:
● <ANGULAR2>/modules/@angular/platfrom-browser/src/worker_app.ts
has this:
@NgModule({
providers: [
Serializer,
{provide: ClientMessageBrokerFactory, useClass: ClientMessageBrokerFactory_},
{provide: ServiceMessageBrokerFactory, useClass: ServiceMessageBrokerFactory_},
{provide: ON_WEB_WORKER, useValue: true},
RenderStore,
{provide: MessageBus, useFactory: createMessageBus, deps: [NgZone]},
..
],
exports: [CommonModule, ApplicationModule]
})
export class WorkerAppModule { }
createElement(parentElement: any,
name: string,
debugInfo?: RenderDebugInfo): any {
var node = this._rootRenderer.allocateNode();
this._runOnService('createElement', [
new FnArg(parentElement, RenderStoreObject), new FnArg(name, null),
new FnArg(node, RenderStoreObject)
]);
return node;
}
element.events.dispatchEvent(eventName, event); } }
constructor(
private _brokerFactory: ServiceMessageBrokerFactory,
private _bus: MessageBus,
private _serializer: Serializer,
private _renderStore: RenderStore,
private _rootRenderer: RootRenderer) {}
broker.registerMethod(
'renderComponent',
[RenderComponentType, PRIMITIVE],
FunctionWrapper.bind(this._renderComponent, this));
broker.registerMethod(
'selectRootElement',
[RenderStoreObject, PRIMITIVE, PRIMITIVE],
FunctionWrapper.bind(this._selectRootElement, this));
broker.registerMethod(
'createElement',
[RenderStoreObject, RenderStoreObject, PRIMITIVE, PRIMITIVE],
FunctionWrapper.bind(this._createElement, this));
broker.registerMethod(
'createViewRoot',
[RenderStoreObject, RenderStoreObject, PRIMITIVE],
FunctionWrapper.bind(this._createViewRoot, this));
The local methods usually call the qequivalent method in the configured renderer, and
often stores the result in the RenderStore. Here is _createElement():
private _createElement(
renderer: Renderer, parentElement: any, name: string, elId: number) {
this._renderStore.store(
renderer.createElement(parentElement, name, null), elId);
Source 151
MessageBasedRenderer
WebWorkerRootRenderer
start()
_messageBroker: ClientMessageBroker
_brokerFactory
Message Bus
MessageBasedRenderer.start() WebWorkerRootRenderer’s
initializes broker as a constructor initializes
ServiceMessageBroker _messageBroker as a ClientMessageBroker
with the RENDERER_CHANNEL with the RENDERER_CHANNEL
switch (event.type) {
...
case 'keydown':
case 'keypress':
case 'keyup':
serializedEvent = serializeKeyboardEvent(event);
break;
...
}
this._sink.emit({
'element': this._serializer.serialize(element, RenderStoreObject),
'eventName': eventName,
'eventTarget': eventTarget,
'event': serializedEvent
});
return false;
}
}
The switch in the middle is a long list of all the supported events and appropriate calls
to an event serializer. Above we show what is has for keyboard events.
Source 153
MessageBasedRenderer WebWorkerRootRenderer
_listen() _dispatchEvent()
_listenGlobal()
_eventDispatcher EventDispatcher
dispatchRenderEvent()
Overview
Platform-WebWorkers-Dynamic is the smallest of all the Angular packages. It define
one const, platformWorkerAppDynamic, which is a call to createPlatformFactory().
The reason to manage this as a separate package is this one line from package.json:
"peerDependencies": { .."@angular/compiler": "0.0.0-PLACEHOLDER", .. },
It brings in the runtime compiler, which is quite large. We wish to avoid this if it not
used (it is not needed in the non-dynamic packages, which use the offline compiler).
Platform-WebWorker-Dynamic API
The exported API of the @Angular/Platform-WebWorker-Dynamic package can be
represented as:
@Angular/Platform-WebWorker-Dynamic API
platformWorkerAppDynamic
Source
Platform-webworker-dynamic just has this code:
export const platformWorkerAppDynamic = createPlatformFactory(
platformCoreDynamic, 'workerAppDynamic', [{
provide: COMPILER_OPTIONS,
useValue: {providers:
[{provide: ResourceLoader, useClass: ResourceLoaderImpl}]},
multi: true
}]);
11: @Angular/Platform-Server
Overview
Platform-Server represents a platform when the application is running on a server.
Most of the time Angular applications run in the browser and use either
Platform-Browser (with the offline template compiler) or Platform-Browser-Dynamic
(with the runtime template compiler). Running Angular applications on the server is a
more specialist deployment. It can be of interest for search engine optimization and
for pre-rendering output, which is later downloaded to browsers (this can speed up
initial display of content to users for some types of applications; and also ease
development with backend databases that might not be exposed via REST API to
browser code). Platform-Server is used by Angular Universal as its platform module.
When considering Platform-Server, there are two questions the curious software
engineer might ponder. Firstly, we wonder, if for the browser there are dynamic and
non-dynamic versions of the platform, why not for the server? The answer to this is
that for the browser, one wishes to support low-end devices serviced by poor network
connections, so reducing the burden on the browser is of great interest (hence using
the offline template compiler only); but one assumes the server has ample disk space
and RAM and a small amount of extra code is not an issue, so bundling both kinds of
server platforms (one that uses the offline template compiler, the other - “dynamic” -
that uses the runtime template compiler) in the same module simplifies matters.
The second question is how rendering works on the server (where there is no DOM)?
The answer is the rendered output is written via the Angular renderer API to an HTML
file, which then can be downloaded to a browser or made available to a search engine
- we need to explore how this rendering works.
Platform-Server API
The exported API of the @Angular/Platform-Server package can be represented as:
@Angular/Platform-Server API
Export Class
ServerModule
Export Functions
platformServer
platformDynamicServer
156 11:@Angular/Platform-Server
The main directory for Platform-Server also contains these private import/export files:
● compiler_private.ts
● core_private.ts
● platform_browser_dynamic_testing_private.ts
● platform_browser_private.ts
As an example, let’s look at core_private.ts:
import {__core_private__ as r} from '@angular/core';
Platfrom-Server needs access to additional Core functionailty that is not part of the
exported Core API and core-private.ts exports the additional types.
Source 157
Source
platform-server/src
These source files are present:
● server.ts
● parse5_adapter.ts
There are no sub-directoies beneath src.
The server.ts file declares this const:
export const INTERNAL_SERVER_PLATFORM_PROVIDERS: Array<any> = [
{provide: PLATFORM_INITIALIZER, useValue: initParse5Adapter, multi: true},
{provide: PlatformLocation, useClass: ServerPlatformLocation},
];
The wtfInit() function comes from core_private and works with the WTF code in
core/src/profile.
Secondly, it adds PlatformLocation, which is used by applications to interact with
location (URL) information. It is set to a local class, ServerPlatformLocation, which
just throws exceptions:
class ServerPlatformLocation extends PlatformLocation {
getBaseHrefFromDOM(): string { throw notSupported('getBaseHrefFromDOM'); };
onPopState(fn: any): void { notSupported('onPopState'); };
onHashChange(fn: any): void { notSupported('onHashChange'); };
get pathname(): string { throw notSupported('pathname'); }
get search(): string { throw notSupported('search'); }
get hash(): string { throw notSupported('hash'); }
replaceState(state: any, title: string, url: string):
void { notSupported('replaceState'); };
pushState(state: any, title: string, url: string):
void { notSupported('pushState'); };
forward(): void { notSupported('forward'); };
back(): void { notSupported('back'); };
}
Its static makeCurrent() method, that we saw Universal Angular uses for server-side
rendering, initializes those three variables and then calls setRootDomAdapter():
static makeCurrent() {
parser = new parse5.Parser(parse5.TreeAdapters.htmlparser2);
serializer = new parse5.Serializer(parse5.TreeAdapters.htmlparser2);
treeAdapter = parser.treeAdapter;
setRootDomAdapter(new Parse5DomAdapter());
}
and that getDOM() is used by the DOMRenderer. Hence our Parse5DomAdapter gets
wired into the DOM renderer.
The various DomAdapter methods are defined mostly in terms of the parse5’s tree
adapter. As an example, appendChild() and insertBefore() are defined as:
appendChild(el: any, node: any) {
this.remove(node);
treeAdapter.appendChild(this.templateAwareRoot(el), node);
}
insertBefore(el: any, node: any) {
this.remove(node);
treeAdapter.insertBefore(el.parent, node, el);
}
160 11:@Angular/Platform-Server
Overview
The Http module provides client-side access to an HTTP stack with configurable HTTP
backends (e.g. alternatives for test). For production use, it usually makes calls to the
browser’s XmlHttpRequest() function. An interesting additional project, in-memory-
web-api, can be used for a test-friendly implementation.
Http API
The exported API of the @Angular/Http package can be represented as:
HttpModule JsonpModule
ReadyState ResponseType
Exported
Enums
ResponseContentType RequestMethod
QueryEncoder
Http
URLSearchParams
Exported
Classes
Jsonp Headers
BrowserXhr
162 12:@Angular/HTTP
ConnectionBackend ResponseOptionsArgs
CookieXSRFStrategy
XHRConnection JSONPConnection
Exported Classes
XHRBackend JSONPBackend
@NgModule({
providers: [
{provide: Jsonp, useFactory: jsonpFactory,
deps: [JSONPBackend, RequestOptions]},
BrowserJsonp,
{provide: RequestOptions, useClass: BaseRequestOptions},
{provide: ResponseOptions, useClass: BaseResponseOptions},
{provide: JSONPBackend, useClass: JSONPBackend_},
],
})
export class JsonpModule {
}
That XHRBackend provider for HttpModule may need to be replaced when using an in-
memory-web-api for testing, as explained here (search for InMemoryWebApiModule):
● https://angular.io/docs/ts/latest/tutorial/toh-pt6.html
http_module.ts also has three simple factory functions:
export function _createDefaultCookieXSRFStrategy() {
return new CookieXSRFStrategy();
}
The headers.ts file provides the implementation for the Headers class. This is
essentially a wrapper around a Map data structure. It imports Map from
../src/facade/collection. It defines its primary data structure as:
_headersMap: Map<string, string[]>;
case 'application/json':
return ContentType.JSON;
case 'application/x-www-form-urlencoded':
return ContentType.FORM;
case 'multipart/form-data':
return ContentType.FORM_DATA;
case 'text/plain':
case 'text/html':
return ContentType.TEXT;
case 'application/octet-stream':
return ContentType.BLOB;
default:
return this.detectContentTypeFromBody();
}
}
Both Request and Response are low-level classes, usually not called form application
code directly. Instead, the Http class is usually used, which we’ll cover shortly.
The url_search_params.ts file implements the UrlSearchParams and QueryEncoder
classes. There are used to expose a map interface to url search parameters.
166 12:@Angular/HTTP
The interfaces.ts file provides a number of classes and interfaces for pluggable
connection handling. By providing alternative implementations of these, flexible server
communication is supported.
export abstract class ConnectionBackend {
abstract createConnection(request: any): Connection; }
export abstract class Connection {
readyState: ReadyState;
request: Request;
response: any; // TODO: generic of <Response>;
}
export abstract class XSRFStrategy {
abstract configureRequest(req: Request): void; }
The http.ts file provides two injectable classes – Http and Jsonp – and two functions
– httpRequest and mergeOptions. The Jsonp class extends the Http class.
http’s constructor takes in parameters of a backend and request options.
@Injectable()
export class Http {
constructor(
protected _backend: ConnectionBackend,
protected _defaultOptions: RequestOptions) {}
Http/backends
This sub-directory has the following source files:
● browser_jsonp.ts
● browser_xhr.ts
● jsonp_backend.ts
● xhr_backend.ts
The browser_xhr.ts file implements the BrowserXhr class, which is a wrapper for
XMLHttpRequest calls:
/**
* A backend for http that uses the `XMLHttpRequest` browser API.
* Take care not to evaluate this in non-browser contexts.
*/
@Injectable()
export class BrowserXhr {
constructor() {}
build(): any { return <any>(new XMLHttpRequest()); }
}
Overview
The Forms module provides capabilities to manage web forms.
It supplies functionality in areas such as validation, submit, data model, additional
directives and a builder for dynamic forms.
Forms API
The exported API of the @Angular/Forms package can be sub-divided into four groups
– main, directives, accessors and validator directives.
The main group can be represented as:
FormsModule ReactiveFormsModule
NG_ASYNC_VALIDATORS NG_VALIDATORS
Validators FormBuilder
Exports
AbstractControl
Two NgModules are supplied, one for normal forms, FormsModule, and the other for
reactive forms, ReactiveFormsModule. The control hierarchy starts with a root, and
has FormControl for actual controls,and two combinations of controls, one for group
(of fixed size) and one for array (of dynamic size).
170 13:@Angular/Forms
AbstractControlDirective
{implements}
FormControlName NgModel
FormArrayName NgForm
FormControlDirective
AbstractFormGroupDirective FormGroupDirective
NgModelGroup FormGroupName
AbstractControlStatus
NgControlStatus NgControlStatusGroup
from './directives/ng_control_status';
export {NgForm} from './directives/ng_form';
export {NgModel} from './directives/ng_model';
export {NgModelGroup} from './directives/ng_model_group';
export {FormControlDirective}
from './directives/reactive_directives/form_control_directive';
export {FormControlName}
from './directives/reactive_directives/form_control_name';
export {FormGroupDirective}
from './directives/reactive_directives/form_group_directive';
export {FormArrayName}
from './directives/reactive_directives/form_group_name';
export {FormGroupName}
from './directives/reactive_directives/form_group_name';
ControlValueAccessor
CheckboxControlValueAccessor
NumberValueAccessor
SelectMultipleControlValueAccessor
DefaultValueAccessor
SelectControlValueAccessor RadioControlValueAccessor
AsyncValidatorFn MaxLengthValidator
MinLengthValidator PatternValidator
Exports
RequiredValidator Validator
ValidatorFn
from './directives/validators';
Source
forms/src
The @angular/forms/src directory contains these files:
● directives.ts
● form_builder.ts
● form_providers.ts
● forms.ts
● model.ts
● validators.ts
forms_providers.ts defines the FormsModule and ReactiveFormsModule NgModules as
follows:
@NgModule({
declarations: TEMPLATE_DRIVEN_DIRECTIVES,
Source 173
providers: [RadioControlRegistry],
exports: [InternalFormsSharedModule, TEMPLATE_DRIVEN_DIRECTIVES]
})
export class FormsModule { }
@NgModule({
declarations: [REACTIVE_DRIVEN_DIRECTIVES],
providers: [FormBuilder, RadioControlRegistry],
exports: [InternalFormsSharedModule, REACTIVE_DRIVEN_DIRECTIVES]
})
export class ReactiveFormsModule { }
We note the two difference between FormsModule and ReactiveFormsModule are that
ReactiveFormsModule has an additional FormBuilder provider configuration, and the
export from FormModule includes TEMPLATE_DRIVEN_DIRECTIVES whereas the export
from ReactiveFormsModule includes REACTIVE_DRIVEN_DIRECTIVES.
directives.ts defines these:
export const SHARED_FORM_DIRECTIVES: Type<any>[] = [
NgSelectOption, NgSelectMultipleOption, DefaultValueAccessor,
NumberValueAccessor, CheckboxControlValueAccessor,
SelectControlValueAccessor, SelectMultipleControlValueAccessor,
RadioControlValueAccessor, NgControlStatus, NgControlStatusGroup,
RequiredValidator, MinLengthValidator, MaxLengthValidator, PatternValidator
];
export const TEMPLATE_DRIVEN_DIRECTIVES: Type<any>[] = [NgModel,
NgModelGroup, NgForm];
export const REACTIVE_DRIVEN_DIRECTIVES: Type<any>[] =
[FormControlDirective, FormGroupDirective, FormControlName,
FormGroupName, FormArrayName];
export const FORM_DIRECTIVES: Type<any>[][] = [TEMPLATE_DRIVEN_DIRECTIVES,
SHARED_FORM_DIRECTIVES];
export const REACTIVE_FORM_DIRECTIVES: Type<any>[][] =
[REACTIVE_DRIVEN_DIRECTIVES, SHARED_FORM_DIRECTIVES];
@NgModule(
{declarations: SHARED_FORM_DIRECTIVES, exports: SHARED_FORM_DIRECTIVES})
export class InternalFormsSharedModule { }
control(
formState: Object, validator: ValidatorFn|ValidatorFn[] = null,
asyncValidator: AsyncValidatorFn|AsyncValidatorFn[]=null):FormControl {
return new FormControl(formState, validator, asyncValidator);
}
array(
controlsConfig: any[], validator: ValidatorFn = null,
asyncValidator: AsyncValidatorFn = null): FormArray {
var controls = controlsConfig.map(c => this._createControl(c));
return new FormArray(controls, validator, asyncValidator);
}
The validators.ts file first declares two opaque tokens for dependency injection:
export const NG_VALIDATORS: OpaqueToken = new OpaqueToken('NgValidators');
export const NG_ASYNC_VALIDATORS: OpaqueToken =
new OpaqueToken('NgAsyncValidators');
The return value 1 is a string to boolean map. If the first line 2 is true, then
{'required': true} is returned 3, otherwise null 4 is returned.
The model.ts file is large and defines the form control hierarchy:
Source 175
AbstractControl
The AbstractControl class defines a constructor, that takes in validator and async
validator functions. This class also defines a bunch of getters which map to private
fields. The value field refers to data we wish to strore within the control:
get value(): any { return this._value; }
The _pristine field refers to whether the control’s data has been changed –
pristine() is true if unchanged, and dirty() is true if changed:
get pristine(): boolean { return this._pristine; }
get dirty(): boolean { return !this.pristine; }
The _touched field refers to whether the user has visited the control (if does not
mean that the control’s value has been changed):
176 13:@Angular/Forms
There are also two xxChanges() getters, for value changes and status changes, that
return observables:
get valueChanges(): Observable<any> { return this._valueChanges; }
get statusChanges(): Observable<any> { return this._statusChanges; }
which executes the condition function over the control and its children and return a
boolean. This _anyControls function is used in many helper methods to determine
information about the control, e.g.:
_anyControlsHaveStatus(status: string): boolean {
return this._anyControls(
(control: AbstractControl) => control.status == status);
}
It is set via:
setParent(parent: FormGroup|FormArray): void { this._parent = parent; }
The FormControl class is supplied for atomic controls (that do not contain any child
controls).
// By default, a `FormControl` is created for every `<input>` or
// other form component.
export class FormControl extends AbstractControl {
Source 177
Its _value field is set via setValue() method which reacts depending on the four
optional booleans supplied:
setValue(value: any, {onlySelf, emitEvent, emitModelToViewChange,
emitViewToModelChange}: {
onlySelf?: boolean,
emitEvent?: boolean,
emitModelToViewChange?: boolean,
emitViewToModelChange?: boolean
} = {}): void {
emitModelToViewChange = isPresent(emitModelToViewChange) ?
emitModelToViewChange : true;
emitViewToModelChange = isPresent(emitViewToModelChange) ?
emitViewToModelChange : true;
this._value = value;
if (this._onChange.length && emitModelToViewChange) {
this._onChange.forEach((changeFn) => changeFn(this._value,
emitViewToModelChange));
}
this.updateValueAndValidity({onlySelf: onlySelf, emitEvent: emitEvent});
}
Its constructor’s first parameter defines a controls associative map (in constrast to
FormArray):
constructor(
public controls: {[key: string]: AbstractControl},
validator: ValidatorFn = null,
asyncValidator: AsyncValidatorFn = null) {
super(validator, asyncValidator);
this._initObservables();
this._setParentForControls();
this.updateValueAndValidity({onlySelf: true, emitEvent: false});
}
setValue(
value: {[key: string]: any}, {onlySelf}: {onlySelf?: boolean} = {}): void {
this._checkAllValuesPresent(value);
StringMapWrapper.forEach(value, (newValue: any, name: string) => {
this._throwIfControlMissing(name); 1
this.controls[name].setValue(newValue, {onlySelf: true});
});
this.updateValueAndValidity({onlySelf: onlySelf});
}
It allows you to insert at the end of the array or at a given location, and to remove:
// Insert a new {@link AbstractControl} at the end of the array.
push(control: AbstractControl): void {
this.controls.push(control);
control.setParent(this);
this.updateValueAndValidity();
}
Overview
The Angular Router provides functionality to:
● manage application state
● manage state transitions
● reflect state in the URL that the user sees in the browser
● dynamically load compoments as needed
Router API
The exported API of the @Angular/Router package can be represented as:
@Directive
Directives
_outlets
Config
{array of}
CanActivateChild CanDeactivate<T>
Consts
Types
PRIMARY_OUTLET Params
180 14:@Angular/Router
NgModule
ActivatedRoute RouterState
RouterModule
Tree< > Tree< >
provideRoutes
Exports
ExtraOptions
ActivatedRouteSnapshot RouterStateSnapshot
UrlSerializer
NavigationStart
Event
DefaultUrlSerializer NavigationEnd
NavigationExtras
union type
UrlSegment NavigationError
ErrorHandler
UrlTree NavigationCancel
Router
RoutesRecognized
from './src/router_module';
export {RouterOutletMap} from './src/router_outlet_map';
export {ActivatedRoute, ActivatedRouteSnapshot, RouterState,
RouterStateSnapshot} from './src/router_state';
export {PRIMARY_OUTLET, Params} from './src/shared';
export {DefaultUrlSerializer, UrlSegment, UrlSerializer, UrlTree}
from './src/url_tree';
It contains an extra provider for the NgProbeToken for the router – helpful for
debugging.
Source
router/src
The router/src directory contains:
● apply_redirects.ts
● common_router_providers.ts
● config.ts
● create_router_state.ts
● create_url_tree.ts
● interfaces.ts
182 14:@Angular/Router
● recognize.ts
● resolve.ts
● router.ts
● router_config-loader.ts
● router_module.ts
● router_outlet_map.ts
● router_providers.ts
● router_state.ts
● shared.ts
● url_tree.ts
We’ll start by looking at RouterModule (router/src/router_module), which is defined
as:
// When registered at the root, it should be used as follows:
// bootstrap(AppCmp, {imports: [RouterModule.forRoot(ROUTES)]});
@NgModule({declarations: ROUTER_DIRECTIVES, exports: ROUTER_DIRECTIVES})
export class RouterModule {
static forRoot(routes: Routes, config?: ExtraOptions):ModuleWithProviders {
return {
ngModule: RouterModule,
providers: [
ROUTER_PROVIDERS,
provideRoutes(routes),
{provide: ROUTER_CONFIGURATION, useValue: config ? config : {}}, {
provide: LocationStrategy,
useFactory: provideLocationStrategy,
deps: [
PlatformLocation,
[new Inject(APP_BASE_HREF), new Optional()],
ROUTER_CONFIGURATION
]
},
provideRouterInitializer()
]
};
}
}
];
}
}
The router_state.ts file contains these classes (and some helper functions):
● RouterState
● ActivatedRoute
● InheritedResolve
● RouterStateSnapshot
RouterState is defined as:
export class RouterState extends Tree<ActivatedRoute> {
constructor(root: TreeNode<ActivatedRoute>,
public snapshot: RouterStateSnapshot) {
super(root);
setRouterStateSnapshot<RouterState, ActivatedRoute>(this, root);
}
get fragment(): Observable<string> { return this.root.fragment; }
toString(): string { return this.snapshot.toString(); }
}
So its sets the router state for the current node, and then recursively calls
setRouterStateSnapshot() to set it for all children.
The ActivatedRoute class is used byt the router outlet directive to describe the
component it has loaded:
Source 185
constructor(
public url: Observable<UrlSegment[]>,
public params: Observable<Params>,
public queryParams: Observable<Params>,
public fragment: Observable<string>,
public data: Observable<Data>,
public outlet: string,
public component: Type|string,
futureSnapshot: ActivatedRouteSnapshot) {
this._futureSnapshot = futureSnapshot;
}
get routeConfig(): Route { return this._futureSnapshot.routeConfig; }
get parent(): ActivatedRoute { return this._routerState.parent(this); }
get firstChild(): ActivatedRoute { return
this._routerState.firstChild(this); }
get children(): ActivatedRoute[] { return this._routerState.children(this);
}
get pathFromRoot(): ActivatedRoute[] { return
this._routerState.pathFromRoot(this); }
}
router/src/directives
This directory has the following files:
● router_link.ts
● router_link_active.ts
● router_outlet.ts
The router_link.ts file contains the RouterLink directive:
@Directive({selector: ':not(a)[routerLink]'})
export class RouterLink {
private commands: any[] = [];
@Input() queryParams: {[k: string]: any};
@Input() fragment: string;
@Input() preserveQueryParams: boolean;
@Input() preserveFragment: boolean;
constructor(
private router: Router,
private route: ActivatedRoute,
private locationStrategy: LocationStrategy) {}
..
}
and manages the urlTree as a field and sets it from the constructor via a call to:
private updateTargetUrlAndHref(): void {
this.urlTree = this.router.createUrlTree(this.commands, {
relativeTo: this.route,
queryParams: this.queryParams,
fragment: this.fragment,
preserveQueryParams: toBool(this.preserveQueryParams),
preserveFragment: toBool(this.preserveFragment)
});
if (this.urlTree) {
this.href = this.locationStrategy.prepareExternalUrl(
this.router.serializeUrl(this.urlTree));
}
}
This is used to add a CSS class to an element representing an active route. Its
constructor is defiend as:
constructor(private router: Router, private element: ElementRef, private
renderer: Renderer) {
this.subscription = router.events.subscribe(s => {
if (s instanceof NavigationEnd) {
this.update();
}
});
}
Its update method uses the configured renderer to set the element class:
private update(): void {
Source 187
This is where application component whose lifecycle depends on the router live. We
note the ViewContainerRef and ComponentFactoryResolver parameters to the
constructor. When its activate method is called, the resolver will be asked to resolve a
component factory for the component.
A somewhat simplified version of activate is:
activate(
activatedRoute: ActivatedRoute,
loadedResolver: ComponentFactoryResolver,
loadedInjector: Injector,
providers: ResolvedReflectiveProvider[],
outletMap: RouterOutletMap): void {
There are two uses of the location ViewContainerRef field, createComponent() and
length. ViewContainerRef is defined in
<ANGULAR2>/modules/@angular/core/src/linker/view_container_ref.ts and it has:
188 14:@Angular/Router
// Instantiates a single Component and inserts its Host View into this
// container at the specified `index`.
abstract createComponent<C>(
componentFactory: ComponentFactory<C>,
index?: number,
injector?: Injector,
projectableNodes?: any[][]): ComponentRef<C>;
Overview
The Compiler-CLI (command line interface) provides two applications – ngc and
ng-xi18n - for developers to run as build steps. It also supplies a small API that allows
embedding of its functionality within other tools.
Most developers start using Angular with the QuickStart application layout that uses
the runtime template compiler. Their beginner applications call bootstrapModule() -
but as their applications get larger and there is a business demand to deliver as small
as possible downloads and as fast as possible launch time, interest grows in the idea
of performing template compilation ahead of time, as a build step on the developer’s
computer.
This is where ngc comes in. A second requirement is to support internationalization
and this is where ng-xi18n comes in.
Compiler-CLI API
The exported API of the @AngularCompiler-CLI package can be represented as:
@Angular/Compiler-CLI API
Interface from
Core Module
ReflectorReader
Interfaces
Exported
ReflectorHostContext StaticReflectorHost
CodeGenerator StaticSymbol
Angular applications are built with templates, which may be `.html` or `.css`
files, or may be inline `template` attributes on Decorators like
`@Component`.
It is recommended reading the entire readme.md in your favorite markdown viewer (if
using Visual studio Code, open the .md file, and select CTRL-SHIFT-V to get nicely
formatted text).
The package.json file in the root directory includes:
"name": "@angular/compiler-cli",
"version": "0.0.0-PLACEHOLDER",
"description": "Execute angular2 template compiler in nodejs.",
Source Tree Layout 191
"main": "index.js",
"typings": "index.d.ts",
"bin": {
"ngc": "./src/main.js",
"ng-xi18n": "./src/extract_i18n.js"
},
"dependencies": {
"@angular/tsc-wrapped": "^0.2.2",
"reflect-metadata": "^0.1.2",
"parse5": "1.3.2",
"minimist": "^1.2.0"
},
"peerDependencies": {
"typescript": "^1.9.0-dev",
"@angular/compiler": "0.0.0-PLACEHOLDER",
"@angular/platform-server": "0.0.0-PLACEHOLDER",
"@angular/core": "0.0.0-PLACEHOLDER"
},
Note the two bin entries, which map to two entry points we need to explore, in
main.ts and extract_i18n.ts.
Source
The other exported types are defined in <ANGULAR2>/modules/@angular/compiler-
cli/src in these source files:
● codegen.ts
● compiler_private.ts
● core_private.ts
● extract_i18n.ts
● main.ts
● path_mapped_reflector.host.ts
● reflector_host.ts
● static_reflection_capabilities.ts
● static_reflector.ts
main.ts contains this code:
function codegen(
ngOptions: tsc.AngularCompilerOptions,
cliOptions: tsc.NgcCliOptions,
program: ts.Program,
host: ts.CompilerHost) {
3 return CodeGenerator.create(
ngOptions, cliOptions, program, host).codegen();
}
console.error('Compilation failed');
process.exit(1);
});
}
We see main uses the minimist library to access the arguments 1; it then has a call to
tsc.main() 2 passing in a function named codegen(). Earlier in the file we see that
codegen() function defined – it has a call to CodeGenerator.create()3.
The codegen.ts file defines the CodeGenerator class. It has four methods:
● create() - static that creates an offline compiler and instantiates
CodeGenerator, which it returns
● codegen() - actual compilation call to offline compiler happens here
● calculateEmitPath() - helper used for directory structure
● readFileMetadata() - metadata access via static reflector
This file also defines the ReflectorHost class, which handles the interaction between
the reflector and the host, such as making additional import locations available to the
reflector and lots more.
Source 193
This file also defines the StaticReflector class, a large 500-line class whose task is to:
/**
* A static reflector implements enough of the Reflector API that
* is necessary to compile templates statically.
*/
export class StaticReflector implements ReflectorReader { .. }
Normally with reflection the code to be reflected over is actually running. However,
with the StaticReflector (and Compiler-CLI in general), this is not the case, and hence
the need to statically (without running the code), access decorators in the code.
The extract_i18n.ts file implements the main and helper functions for
internationalization. Its main just calls tsc.main passing in an extract function:
tsc.main(project, cliOptions, extract)
extract() supportedboth xmb and xliff localization string formats and is implemented
as:
function extract(
ngOptions: tsc.AngularCompilerOptions,
cliOptions: tsc.I18nExtractionCliOptions,
program: ts.Program, host: ts.CompilerHost) {
const htmlParser = new compiler.i18n.HtmlParser(new HtmlParser());
const extractor = Extractor.create(
ngOptions, cliOptions.i18nFormat, program, host, htmlParser);
const bundlePromise: Promise<compiler.i18n.MessageBundle> =
extractor.extract();
194 15:@Angular/Compiler-CLI
switch (format) {
case 'xmb':
ext = 'xmb';
serializer = new compiler.i18n.Xmb();
break;
case 'xliff':
case 'xlf':
default:
ext = 'xlf';
serializer = new compiler.i18n.Xliff(
htmlParser, compiler.DEFAULT_INTERPOLATION_CONFIG);
break;
}
staticReflector);
const offlineCompiler = new compiler.OfflineCompiler(
resolver, normalizer, tmplParser,
new StyleCompiler(urlResolver),
new ViewCompiler(config),
new NgModuleCompiler(),
new TypeScriptEmitter(reflectorHost), null, null);
let messageBundle = new compiler.i18n.MessageBundle(htmlParser, [], {});
return new Extractor(program, compilerHost, staticReflector,
messageBundle, reflectorHost, resolver, normalizer, offlineCompiler);
}
}
16: @Angular/Compiler
Overview
The compiler module provides both runtime and offline template compilation services.
It also helps with internationalization (i18n) and it supplies a registry of elements
(ElementSchemaRegistry) with appropriate security contexts.
Compiler API
The exported API of the @AngularCompiler module can be sub-divied into a number
of areas.
@Angular/Compiler API
Exported Classes
NgModule
string DirectiveMetadata PipeMetadata
Metadata
NgModule
UrlResolver DirectiveResolver PipeResolver
Resolver
DEFAULT_INTERPOLATION_CONFIG
Functions
Exported
createOfflineCompileUrlResolver
Compiler API 197
TBD
Serializer
BaseHtmlParser
Exported Classes
MessageBundle
{implements}
{implements}
PropertyBindingType ProviderAstType
interfaces
Exported
templateVisitAll( TemplateAstVisitor
Exported
function
Visitor,
Asts: [], ..)
TemplateAst
{implements}
Embedded BoundDirective
DirectiveAst ProviderAst NgContentAst
TemplateAst PropertyAst
index.ts lists the public exports which come from five files – compiler.ts (we can divide
its exports into metadata1a, general1b and resolvers1c), interpolation_config2,
element_schema_registry3, i18n4 and template_ast5:
export {
1a
CompileDiDependencyMetadata, CompileDirectiveMetadata,
CompileFactoryMetadata, CompileIdentifierMetadata,
CompileMetadataWithIdentifier, CompilePipeMetadata, CompileProviderMetadata,
CompileQueryMetadata, CompileTemplateMetadata, CompileTokenMetadata,
CompileTypeMetadata,
1b
COMPILER_PROVIDERS, OfflineCompiler, RuntimeCompiler, RenderTypes,
ResourceLoader, CompilerConfig, DEFAULT_PACKAGE_URL_PROVIDER, SourceModule,
TEMPLATE_TRANSFORMS, platformCoreDynamic,
1c
Compiler API 199
● Compiler
● Resolver
● Ast
● Lexer
● Parser
● Reflector
TBD
compiler/src/schema
This directory has the following files:
● dom_element_schema_registry,ts
● dom_security_schema.ts
● element_schema_registry.ts
The SchemaMetadata interface is declared in Core’s src/metadata/ng_module.ts:
// Interface for schema definitions in @NgModules.
export interface SchemaMetadata { name: string; }
It contains only abstract methods that a derived class needs to implement to return
information about elements.
We have already seen that the SecurityContext enum is defined in Core’s
src/security.ts class:
export enum SecurityContext {
NONE,
HTML,
STYLE,
SCRIPT,
URL,
RESOURCE_URL,
}
This called four times with lists of items to be asspociated with the security context:
registerContext(SecurityContext.HTML, ..);
registerContext(SecurityContext.STYLE, ..);
registerContext(SecurityContext.URL, ..);
registerContext(SecurityContext.RESOURCE_URL, ..);
Overview
Tsickle is a small utility used to transpile from TypeScript to JavaScript and adds
annotations (in the form of JSDoc comments) for the Google Closure Compiler to
further optimize the generated JavaScript code. To learn more about Closure, visit:
● https://github.com/google/closure-compiler/
Tsickle is used by tsc-wrapped, the compilation utility used to build Angular - located
in the main Angular project under tools/@angular (in contrast, in the main Angular
project, most of the source is located under modules/@angular).
5 if (settings.externsPath) {
mkdirp.sync(path.dirname(settings.externsPath));
fs.writeFileSync(settings.externsPath, closure.externs);
}
return 0;
}
The main function first loads the settings 1 from the args and 2 the tsc config. Then it
calls the toClosureJs() function 3, and outputs to a file 4 each resulting JavaScript
file. If externsPath is set in settings, they too are written out to files 5.
The loadSettingsfromArgs() function handles the command-line arguments, which
can be a mix of tsickle-specific arguments and regular tsc arguments. The tsickle-
specific arguments are –externs (generate externs file) and –untyped (every
TypeScript type becomes a Closure {?} type).
The toClosureJs() function is where the transformation occurs. It returns 1 a map of
transformed file contents, optionally with externs information, it so configured.
function toClosureJS(
options: ts.CompilerOptions, fileNames: string[], settings: Settings,
204 17:Tsickle
allDiagnostics: ts.Diagnostic[]):
1 {jsFiles: Map<string, string>, externs: string}|null {
// Parse and load the program without tsickle processing.
// This is so:
// - error messages point at the original source text
// - tsickle can use the result of typechecking for annotation
2 let program = ts.createProgram(fileNames, options);
{
let diagnostics = ts.getPreEmitDiagnostics(program);
if (diagnostics.length > 0) {
allDiagnostics.push(...diagnostics);
return null;
}
}
..
// Process each input file with tsickle and save the output.
const tsickleOutput = new Map<string, string>();
let tsickleExterns = '';
for (let fileName of fileNames) {
let {output, externs, diagnostics} =
3 tsickle.annotate(program, program.getSourceFile(fileName),
tsickleOptions);
..
4 tsickleOutput.set(ts.sys.resolvePath(fileName), output);
if (externs) { tsickleExterns += externs; }
}
..
/**
* Constructs a new ts.CompilerHost that overlays sources in substituteSource
* over another ts.CompilerHost.
*
* @param substituteSource A map of source file name -> overlay source text.
*/
function createSourceReplacingCompilerHost(
substituteSource: Map<string, string>, delegate: ts.CompilerHost):
ts.CompilerHost {
return {
1 getSourceFile,
getCancellationToken: delegate.getCancellationToken,
getDefaultLibFileName: delegate.getDefaultLibFileName,
writeFile: delegate.writeFile,
getCurrentDirectory: delegate.getCurrentDirectory,
getCanonicalFileName: delegate.getCanonicalFileName,
useCaseSensitiveFileNames: delegate.useCaseSensitiveFileNames,
getNewLine: delegate.getNewLine,
fileExists: delegate.fileExists,
readFile: delegate.readFile,
directoryExists: delegate.directoryExists,
getDirectories: delegate.getDirectories,
};
function getSourceFile(
fileName: string, languageVersion: ts.ScriptTarget,
onError?: (message: string) => void): ts.SourceFile {
let path: string = ts.sys.resolvePath(fileName);
let sourceText = substituteSource.get(path);
if (sourceText) {
return ts.createSourceFile(path, sourceText, languageVersion);
}
return delegate.getSourceFile(path, languageVersion, onError);
}
}
We have seen that the annotate function from the tsickle source file is called from
toClosureJS(). It is a simple function:
export function annotate(
program: ts.Program, file: ts.SourceFile, options: Options = {}): Output {
assertTypeChecked(file);
return new Annotator(program, file, options).annotate();
}
So it uses the Annotator class and returns an Output instance. Output is an interface
defined as:
export interface Output {
/** The TypeScript source with Closure annotations inserted. */
output: string;
/** Generated externs declarations, if any. */
externs: string|null;
/** Error messages, if any. */
diagnostics: ts.Diagnostic[];
/** A source map mapping back into the original sources. */
sourceMap: SourceMapGenerator;
}
206 17:Tsickle
Classes called rewriters are used to rewrite the source. The rewriter.ts file has the
rewriter abstract class. An important method is maybeProcess().
/**
* A Rewriter manages iterating through a ts.SourceFile, copying input
* to output while letting the subclass potentially alter some nodes
* along the way by implementing maybeProcess().
*/
export abstract class Rewriter {
..
/**
* maybeProcess lets subclasses optionally processes a node.
*
* @return True if the node has been handled and doesn't need to be traversed;
* false to have the node written and its children recursively visited.
*/
protected maybeProcess(node: ts.Node): boolean {
return false;
}
}
tsickle.ts has some classes that derive from Rewriter, according to this hierarchy:
Rewriter (abstract)
ExternsRewriter
ClosureRewriter
Server
Rewriter
(rewriter.ts) Classes (most classes are in tsickle.ts)
Rewriter
ClosureRewriter ExternsWriter
Annotator
Overview
Ts-api-guardian is a small tool that tracks a package’s public API.
It is used in the Angular build to check for changes to the Angular public API and to
ensure that inadvertent changes to the public API are detected. Specifically, it you
examine gulpfile.ts in the main Angular project:
● <ANGULAR-MASTER>/gulpfile.js
and look at two tasks named 'public-api:enforce' and 'public-api:update' we see how
ts-api-guardian is used, to generate a “golden file” representing the API, and to
ensure it has not been unexpectedly changed:
// Enforce that the public API matches the golden files
// Note that these two commands work on built d.ts files instead of the source
gulp.task('public-api:enforce', (done) => {
const childProcess = require('child_process');
childProcess
.spawn(
path.join(__dirname, platformScriptPath(`/node_modules/.bin/ts-api-guardian`)),
['--verifyDir', publicApiDir].concat(publicApiArgs), {stdio: 'inherit'})
.on('close', (errorCode) => {
if (errorCode !== 0) {
done(new Error(
'Public API differs from golden file. Please run `gulp public-api:update`.'));
} else {
done();
}
});
});
childProcess
.spawn(
path.join(__dirname, platformScriptPath(`/node_modules/.bin/ts-api-guardian`)),
['--outDir', publicApiDir].concat(publicApiArgs), {stdio: 'inherit'})
.on('close', done);
});
Source Tree
The ts-api-guardian source tree contains three top-level directories:
● bin
● lib
● test
The main directory of ts-api-guardian contains:
● gulpfile.js
● package.json
208 18:TS-API-Guardian
● tsconfig.json
● tsd.json
The primary tasks in the gulpfile are:
● compile
● test.compile
● test.unit
● watch
The unit tests are based on mocha (unlike most of the rest of Angular, which uses
jasmine).
The package.json has the following dependencies:
"dependencies": {
"chalk": "^1.1.3",
"diff": "^2.2.3",
"minimist": "^1.2.0",
"typescript": "1.7.3"
},
The most important of these is the diff package, which is used to determine
differences between blocks of text (https://www.npmjs.com/package/diff).
Package.json also list the single callable program inside ts-api-guardian:
"bin": {
"ts-api-guardian": "./bin/ts-api-guardian"
},
bin
This is the single file inside the bin sub-directory, which contains just a single line:
require('../build/lib/cli').startCli();
lib
The lib sub-directory contains these files:
● cli.ts – command-line interface, processes argument list and invokes commands
● main.ts – main logic for generating and verifying golden files
● serializer.ts – code to serialize an API (to create the contents of a golden file)
A golden file is a textual representation of an API and the two key tasks of ts-api-
guardian is to either create or verify golden files based on supplied command line
arguments.
Cli.ts starts with some useful comments about how to call ts-api-guardian:
// # Generate one declaration file
// ts-api-guardian --out api_guard.d.ts index.d.ts
//
// # Generate multiple declaration files
// # (output location like typescript)
// ts-api-guardian --outDir api_guard [--rootDir .]
core/index.d.ts core/testing.d.ts
lib 209
//
// # Print usage
// ts-api-guardian --help
//
// # Check against one declaration file
// ts-api-guardian --verify api_guard.d.ts index.d.ts
//
// # Check against multiple declaration files
// ts-api-guardian --verifyDir api_guard [--rootDir .]
core/index.d.ts core/testing.d.ts
The Angular API allows annotations to be attached to each API indicating whether it is
stable, deprecated or experiemental. The onStabilityMissing option indicates what
action is required if such an annotation is missing. The startCli() function parses
the command line and initializes an instance of SerializationOptions, and then for
generation mode calls generateGoldenFile() or for verification mode calls
verifyAgainstGoldenFile() - both are in main.ts and are actually quite short
functions:
export function generateGoldenFile(
entrypoint: string,
outFile: string, options: SerializationOptions = {}): void {
const output = publicApi(entrypoint, options);
ensureDirectory(path.dirname(outFile));
fs.writeFileSync(outFile, output); }
return patch.substring(start);
}
}