diff --git a/modules/benchmarks/src/js-web-frameworks/BUILD.bazel b/modules/benchmarks/src/js-web-frameworks/BUILD.bazel
new file mode 100644
index 0000000000..d319d9b297
--- /dev/null
+++ b/modules/benchmarks/src/js-web-frameworks/BUILD.bazel
@@ -0,0 +1,13 @@
+load("//tools:defaults.bzl", "ts_library")
+
+package(default_visibility = ["//visibility:public"])
+
+ts_library(
+ name = "perf_lib",
+ testonly = True,
+ srcs = ["perf.spec.ts"],
+ deps = [
+ "//modules/e2e_util",
+ "@npm//protractor",
+ ],
+)
diff --git a/modules/benchmarks/src/js-web-frameworks/ng2/BUILD.bazel b/modules/benchmarks/src/js-web-frameworks/ng2/BUILD.bazel
new file mode 100644
index 0000000000..ea6927b6f7
--- /dev/null
+++ b/modules/benchmarks/src/js-web-frameworks/ng2/BUILD.bazel
@@ -0,0 +1,39 @@
+load("//tools:defaults.bzl", "ng_module", "ng_rollup_bundle", "ts_devserver")
+load("//modules/benchmarks:benchmark_test.bzl", "benchmark_test")
+
+package(default_visibility = ["//modules/benchmarks:__subpackages__"])
+
+ng_module(
+ name = "ng2",
+ srcs = glob(["*.ts"]),
+ generate_ve_shims = True,
+ tsconfig = "//modules/benchmarks:tsconfig-build.json",
+ deps = [
+ "//modules/benchmarks/src:util_lib",
+ "//packages/core",
+ "//packages/platform-browser",
+ ],
+)
+
+ng_rollup_bundle(
+ name = "bundle",
+ entry_point = ":index.ts",
+ deps = [
+ ":ng2",
+ "@npm//rxjs",
+ ],
+)
+
+ts_devserver(
+ name = "prodserver",
+ bootstrap = ["//packages/zone.js/dist:zone.js"],
+ port = 4200,
+ static_files = ["index.html"],
+ deps = [":bundle.min_debug.es2015.js"],
+)
+
+benchmark_test(
+ name = "perf",
+ server = ":prodserver",
+ deps = ["//modules/benchmarks/src/js-web-frameworks:perf_lib"],
+)
diff --git a/modules/benchmarks/src/js-web-frameworks/ng2/index.html b/modules/benchmarks/src/js-web-frameworks/ng2/index.html
new file mode 100644
index 0000000000..0375e88279
--- /dev/null
+++ b/modules/benchmarks/src/js-web-frameworks/ng2/index.html
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Loading...
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/modules/benchmarks/src/js-web-frameworks/ng2/index.ts b/modules/benchmarks/src/js-web-frameworks/ng2/index.ts
new file mode 100644
index 0000000000..c658092360
--- /dev/null
+++ b/modules/benchmarks/src/js-web-frameworks/ng2/index.ts
@@ -0,0 +1,16 @@
+/**
+ * @license
+ * Copyright Google Inc. All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+
+import {enableProdMode} from '@angular/core';
+import {platformBrowser} from '@angular/platform-browser';
+
+import {init} from './init';
+import {JsWebFrameworksModuleNgFactory} from './rows.ngfactory';
+
+enableProdMode();
+platformBrowser().bootstrapModuleFactory(JsWebFrameworksModuleNgFactory).then(init);
diff --git a/modules/benchmarks/src/js-web-frameworks/ng2/init.ts b/modules/benchmarks/src/js-web-frameworks/ng2/init.ts
new file mode 100644
index 0000000000..b1d4861075
--- /dev/null
+++ b/modules/benchmarks/src/js-web-frameworks/ng2/init.ts
@@ -0,0 +1,92 @@
+/**
+ * @license
+ * Copyright Google Inc. All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+
+import {ApplicationRef, NgModuleRef} from '@angular/core';
+
+import {bindAction} from '../../util';
+
+import {JsWebFrameworksComponent, JsWebFrameworksModule, RowData} from './rows';
+
+
+function _random(max: number) {
+ return Math.round(Math.random() * 1000) % max;
+}
+
+function buildData(count: number): Array {
+ const data: Array = [];
+ for (let i = 0; i < count; i++) {
+ data.push({
+ id: i,
+ label: ADJECTIVES[_random(ADJECTIVES.length)] + ' ' + COLOURS[_random(COLOURS.length)] + ' ' +
+ NOUNS[_random(NOUNS.length)]
+ });
+ }
+ return data;
+}
+
+const ADJECTIVES = [
+ 'pretty', 'large', 'big', 'small', 'tall', 'short', 'long',
+ 'handsome', 'plain', 'quaint', 'clean', 'elegant', 'easy', 'angry',
+ 'crazy', 'helpful', 'mushy', 'odd', 'unsightly', 'adorable', 'important',
+ 'inexpensive', 'cheap', 'expensive', 'fancy'
+];
+const COLOURS = [
+ 'red', 'yellow', 'blue', 'green', 'pink', 'brown', 'purple', 'brown', 'white', 'black', 'orange'
+];
+const NOUNS = [
+ 'table', 'chair', 'house', 'bbq', 'desk', 'car', 'pony', 'cookie', 'sandwich', 'burger', 'pizza',
+ 'mouse', 'keyboard'
+];
+
+export function init(moduleRef: NgModuleRef) {
+ let component: JsWebFrameworksComponent;
+ let appRef: ApplicationRef;
+
+ function create1K() {
+ component.data = buildData(1 * 1000);
+ appRef.tick();
+ }
+
+ function create10K() {
+ component.data = buildData(10 * 1000);
+ appRef.tick();
+ }
+
+ function deleteAll() {
+ component.data = [];
+ appRef.tick();
+ }
+
+ function update() {
+ for (let i = 0; i < component.data.length; i += 10) {
+ component.data[i].label += ' !!!';
+ }
+ appRef.tick();
+ }
+
+ function swapRows() {
+ const data = component.data;
+ if (data.length > 998) {
+ const a = data[1];
+ data[1] = data[998];
+ data[998] = a;
+ }
+ appRef.tick();
+ }
+
+ const injector = moduleRef.injector;
+ appRef = injector.get(ApplicationRef);
+
+ component = appRef.components[0].instance;
+
+ bindAction('#create1KRows', create1K);
+ bindAction('#create10KRows', create10K);
+ bindAction('#deleteAll', deleteAll);
+ bindAction('#update', update);
+ bindAction('#swap', swapRows);
+}
diff --git a/modules/benchmarks/src/js-web-frameworks/ng2/rows.ts b/modules/benchmarks/src/js-web-frameworks/ng2/rows.ts
new file mode 100644
index 0000000000..655c2385cd
--- /dev/null
+++ b/modules/benchmarks/src/js-web-frameworks/ng2/rows.ts
@@ -0,0 +1,70 @@
+/**
+ * @license
+ * Copyright Google Inc. All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+
+import {ApplicationRef, Component, NgModule} from '@angular/core';
+import {BrowserModule} from '@angular/platform-browser';
+
+export interface RowData {
+ id: number;
+ label: string;
+}
+
+
+@Component({
+ selector: 'js-web-frameworks',
+ template: `
+
+ `
+})
+export class JsWebFrameworksComponent {
+ data: Array = [];
+ selected: number|null;
+
+ constructor(private _appRef: ApplicationRef) {}
+
+ itemById(index: number, item: RowData) { return item.id; }
+
+ select(itemId: number) {
+ this.selected = itemId;
+ this._appRef.tick();
+ }
+
+ delete (itemId: number) {
+ const data = this.data;
+ for (let i = 0, l = data.length; i < l; i++) {
+ if (data[i].id === itemId) {
+ data.splice(i, 1);
+ break;
+ }
+ }
+ this._appRef.tick();
+ }
+}
+
+@NgModule({
+ imports: [BrowserModule],
+ declarations: [JsWebFrameworksComponent],
+ bootstrap: [JsWebFrameworksComponent],
+})
+export class JsWebFrameworksModule {
+}
\ No newline at end of file
diff --git a/modules/benchmarks/src/js-web-frameworks/perf.spec.ts b/modules/benchmarks/src/js-web-frameworks/perf.spec.ts
new file mode 100644
index 0000000000..dfa3ab797f
--- /dev/null
+++ b/modules/benchmarks/src/js-web-frameworks/perf.spec.ts
@@ -0,0 +1,78 @@
+/**
+ * @license
+ * Copyright Google Inc. All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+
+import {$} from 'protractor';
+import {runBenchmark, verifyNoBrowserErrors} from '../../../e2e_util/perf_util';
+
+interface Worker {
+ id: string;
+ prepare?(): void;
+ work(): void;
+}
+
+const Create1KWorker: Worker = {
+ id: 'create1K',
+ prepare: () => $('#deleteAll').click(),
+ work: () => $('#create1KRows').click()
+};
+
+const Delete1KWorker: Worker = {
+ id: 'delete1K',
+ prepare: () => $('#create1KRows').click(),
+ work: () => { $('#deleteAll').click(); }
+};
+
+const UpdateWorker: Worker = {
+ id: 'update',
+ prepare: () => $('#create1KRows').click(),
+ work: () => { $('#update').click(); }
+};
+
+const SwapWorker: Worker = {
+ id: 'swap',
+ prepare: () => $('#create1KRows').click(),
+ work: () => { $('#swap').click(); }
+};
+
+// In order to make sure that we don't change the ids of the benchmarks, we need to
+// determine the current test package name from the Bazel target. This is necessary
+// because previous to the Bazel conversion, the benchmark test ids contained the test
+// name. e.g. "largeTable.ng2_switch.createDestroy". We determine the name of the
+// Bazel package where this test runs from the current test target. The Bazel target
+// looks like: "//modules/benchmarks/src/largetable/{pkg_name}:{target_name}".
+const testPackageName = process.env['BAZEL_TARGET'] !.split(':')[0].split('/').pop();
+
+describe('js-web-frameworks benchmark perf', () => {
+
+ afterEach(verifyNoBrowserErrors);
+
+ [Create1KWorker, Delete1KWorker, UpdateWorker, SwapWorker].forEach((worker) => {
+ describe(worker.id, () => {
+ it(`should run benchmark for ${testPackageName}`, done => {
+ runTableBenchmark({
+ id: `js-web-frameworks.${testPackageName}.${worker.id}`,
+ url: '/',
+ ignoreBrowserSynchronization: true,
+ worker: worker
+ }).then(done, done.fail);
+ });
+ });
+ });
+});
+
+function runTableBenchmark(
+ config: {id: string, url: string, ignoreBrowserSynchronization?: boolean, worker: Worker}) {
+ return runBenchmark({
+ id: config.id,
+ url: config.url,
+ ignoreBrowserSynchronization: config.ignoreBrowserSynchronization,
+ params: [],
+ prepare: config.worker.prepare,
+ work: config.worker.work
+ });
+}