test(ivy): add expanding_rows performance benchmark which runs in ViewEngine and Ivy (#30449)

PR Close #30449
This commit is contained in:
Miško Hevery 2019-04-04 15:03:40 -07:00 committed by Jason Aden
parent 1714451a6d
commit b1ba2d6f4e
17 changed files with 1767 additions and 0 deletions

View File

@ -0,0 +1,74 @@
package(default_visibility = ["//modules/benchmarks:__subpackages__"])
load("//tools:defaults.bzl", "ng_module", "ng_rollup_bundle")
load("@npm_bazel_typescript//:index.bzl", "ts_devserver")
load("//modules/benchmarks:benchmark_test.bzl", "benchmark_test")
ng_module(
name = "application_lib",
srcs = glob(
["**/*.ts"],
exclude = ["**/*_spec.ts"],
),
deps = [
"//packages:types",
"//packages/common",
"//packages/core",
"//packages/platform-browser",
"@npm//rxjs",
],
)
ng_module(
name = "application_spec",
srcs = glob(["**/*_spec.ts"]),
deps = [
"//packages:types",
"//packages/common",
"//packages/core",
"@npm//reflect-metadata",
],
)
ng_rollup_bundle(
name = "bundle",
entry_point = "modules/benchmarks/src/expanding_rows/index.js",
deps = [
":application_lib",
"@npm//rxjs",
],
)
ts_devserver(
name = "prodserver",
static_files = [
":bundle.min_debug.js",
":bundle.min.js",
"@npm//node_modules/zone.js:dist/zone.js",
"index.html",
],
)
ts_devserver(
name = "devserver",
entry_module = "angular/modules/benchmarks/src/expanding_rows/index",
index_html = "index.html",
scripts = [
"@npm//node_modules/tslib:tslib.js",
"//tools/rxjs:rxjs_umd_modules",
],
serving_path = "/index.js",
static_files = [
"@npm//node_modules/zone.js:dist/zone.js",
"index.html",
],
deps = [":application_lib"],
)
benchmark_test(
name = "perf",
server = ":prodserver",
deps = [
":application_spec",
],
)

View File

@ -0,0 +1,76 @@
/**
* @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 {CommonModule} from '@angular/common';
import {AfterViewInit, Component, NgModule, ViewChild, ViewEncapsulation} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
import {BenchmarkModule} from './benchmark_module';
import {BenchmarkableExpandingRow} from './benchmarkable_expanding_row';
import {BenchmarkableExpandingRowModule} from './benchmarkable_expanding_row_module';
@Component({
selector: 'benchmark-root',
encapsulation: ViewEncapsulation.None,
template: `
<h2>cfc-expanding-row initialization benchmark</h2>
<section>
<button id="reset" (click)="reset()">Reset</button>
<button (click)="handleInitClick()">Init</button>
<button id="run" (click)="runAll()">Run All</button>
</section>
<benchmark-area>
<benchmarkable-expanding-row></benchmarkable-expanding-row>
</benchmark-area>`,
})
export class InitializationRoot implements AfterViewInit {
@ViewChild(BenchmarkableExpandingRow, {static: true})
expandingRow !: BenchmarkableExpandingRow;
ngAfterViewInit() {}
reset() { this.expandingRow.reset(); }
async runAll() {
await execTimed('initialization_benchmark', async() => { await this.doInit(); });
}
async handleInitClick() { await this.doInit(); }
private async doInit() {
await execTimed('initial_load', async() => { this.expandingRow.init(); });
}
}
@NgModule({
declarations: [InitializationRoot],
exports: [InitializationRoot],
imports: [
CommonModule,
BenchmarkableExpandingRowModule,
BenchmarkModule,
BrowserModule,
],
bootstrap: [InitializationRoot],
})
// Component benchmarks must export a BenchmarkModule.
export class ExpandingRowBenchmarkModule {
}
export async function execTimed(description: string, func: () => Promise<void>) {
console.time(description);
await func();
await nextTick(200);
console.timeEnd(description);
}
export async function nextTick(delay = 1) {
return new Promise((res, rej) => { setTimeout(() => { res(); }, delay); });
}

View File

@ -0,0 +1,54 @@
/**
* @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 {ErrorHandler} from '@angular/core';
import {Component, Injectable, NgModule} from '@angular/core';
@Component({
selector: 'benchmark-area',
template: '<ng-content></ng-content>',
styles: [`
:host {
padding: 1;
margin: 1;
background-color: white;
width: 1000px;
display: block;
}`],
host: {
'class': 'cfc-ng2-region',
}
})
export class BenchmarkArea {
}
declare interface ExtendedWindow extends Window {
benchmarkErrors?: string[];
}
const extendedWindow = window as ExtendedWindow;
@Injectable({providedIn: 'root'})
export class BenchmarkErrorHandler implements ErrorHandler {
handleError(error: Error) {
if (!extendedWindow.benchmarkErrors) {
extendedWindow.benchmarkErrors = [];
}
extendedWindow.benchmarkErrors.push(error.message);
console.error(error);
}
}
@NgModule({
declarations: [BenchmarkArea],
exports: [BenchmarkArea],
providers: [
{provide: ErrorHandler, useClass: BenchmarkErrorHandler},
]
})
export class BenchmarkModule {
}

View File

@ -0,0 +1,24 @@
/**
* @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 {openTreeBenchmark, runTreeBenchmark} from './tree_perf_test_utils';
describe('benchmarks', () => {
it('should work for createOnly', done => {
runTreeBenchmark({
// This cannot be called "createOnly" because the actual destroy benchmark
// has the "createOnly" id already. See: https://github.com/angular/angular/pull/21503
id: 'createOnlyForReal',
prepare: () => $('#destroyDom').click(),
work: () => $('#createDom').click()
}).then(done, done.fail);
});
});

View File

@ -0,0 +1,73 @@
/**
* @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 {Component} from '@angular/core';
export interface MlbTeam {
name: string;
id: number;
division: string;
stadium: string;
projection: string;
}
@Component({
selector: 'benchmarkable-expanding-row',
template: `
<cfc-expanding-row-host *ngIf="showExpandingRow">
<cfc-expanding-row *ngFor="let team of teams" [rowId]="$any(team.id)">
<cfc-expanding-row-summary>
Team {{team.id}}
</cfc-expanding-row-summary>
<cfc-expanding-row-details-caption>
{{team.name}}
<a href="https://www.google.com" class="cfc-demo-expanding-row-caption-link">
{{team.id}}
</a>
</cfc-expanding-row-details-caption>
<cfc-expanding-row-details-content>
<ul ace-list>
<li>Division: {{team.division}}</li>
<li>
<a href="https://www.google.com">{{team.stadium}}</a>
</li>
<li>Projected Record: {{team.projection}}</li>
</ul>
</cfc-expanding-row-details-content>
</cfc-expanding-row>
</cfc-expanding-row-host>`,
})
export class BenchmarkableExpandingRow {
// TODO(b/109816955): remove '!', see go/strict-prop-init-fix.
showExpandingRow!: boolean;
// TODO(b/109816955): remove '!', see go/strict-prop-init-fix.
teams!: MlbTeam[];
// TODO(b/109816955): remove '!', see go/strict-prop-init-fix.
private fakeTeams!: MlbTeam[];
init(): void {
this.teams = this.fakeTeams;
this.showExpandingRow = true;
}
reset(numItems = 5000): void {
this.showExpandingRow = false;
this.fakeTeams = [];
for (let i = 0; i < numItems; i++) {
this.fakeTeams.push({
name: `name ${i}`,
id: i,
division: `division ${i}`,
stadium: `stadium ${i}`,
projection: `projection ${i}`,
});
}
}
}

View File

@ -0,0 +1,25 @@
/**
* @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 {CommonModule} from '@angular/common';
import {NgModule} from '@angular/core';
import {ExpandingRowModule} from './expanding_row_module';
import {BenchmarkableExpandingRow} from './benchmarkable_expanding_row';
@NgModule({
declarations: [BenchmarkableExpandingRow],
exports: [BenchmarkableExpandingRow],
imports: [
CommonModule,
ExpandingRowModule,
],
})
export class BenchmarkableExpandingRowModule {
}

View File

@ -0,0 +1,371 @@
/**
* @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 {AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, EventEmitter, HostListener, Inject, InjectionToken, Input, Output, QueryList, ViewChild} from '@angular/core';
import {ExpandingRowSummary} from './expanding_row_summary';
import {ExpandingRowToggleEvent} from './expanding_row_toggle_event';
import { expanding_row_css } from './expanding_row_css';
/**
* Injection token to break cylic dependency between ExpandingRow and
* ExpandingRowHost
*/
export const EXPANDING_ROW_HOST_INJECTION_TOKEN =
new InjectionToken<ExpandingRowHostBase>('ExpandingRowHost');
/** The base class for ExpandingRowHost component to break cylic dependency. */
export interface ExpandingRowHostBase {
/**
* A reference to all child cfc-expanding-row elements. We will need for
* keyboard accessibility and scroll adjustments. For example, we need to know
* which row is previous row when user presses "left arrow" on a focused row.
*/
contentRows: QueryList<ExpandingRow>;
/**
* Keeps track of the last row that had focus before focus left the list
* of expanding rows.
*/
lastFocusedRow?: ExpandingRow;
/**
* Handles summary element click on a cfc-expanding-row component. Note
* that summary element is visible only when the row is collapsed. So this
* event will fired prior to expansion of a collapsed row. Scroll adjustment
* below makes sure mouse stays on the caption element when the collapsed
* row expands.
*/
handleRowSummaryClick(row: ExpandingRow): void;
/**
* Check if element is blacklisted. Blacklisted elements will not collapse an
* open row when clicked.
*/
isBlacklisted(element: HTMLElement|null): boolean;
/**
* Handles caption element click on a cfc-expanding-row component. Note
* that caption element is visible only when the row is expanded. So this
* means we will collapse the expanded row. The scroll adjustment below
* makes sure that the mouse stays under the summary of the expanded row
* when the row collapses.
*/
handleRowCaptionClick(row: ExpandingRow): void;
/**
* Handles expansion of a row. When a new row expands, we need to remove
* previous expansion and collapse. We also need to save the currently
* expanded row so that we can collapse this row once another row expands.
*/
handleRowExpand(row: ExpandingRow): void;
/**
* Handles focus on a row. When a new row gets focus (note that this is
* different from expansion), we need to remove previous focus and expansion.
* We need to save the reference to this focused row so that we can unfocus
* this row when another row is focused.
*/
handleRowFocus(row: ExpandingRow): void;
/**
* Function that is called by expanding row summary to focus on the last
* focusable element before the list of expanding rows.
*/
focusOnPreviousFocusableElement(): void;
/**
* Function that is called by expanding row summary to focus on the next
* focusable element after the list of expanding rows.
*/
focusOnNextFocusableElement(): void;
}
/**
* This component is used to render a single expanding row. It should contain
* cfc-expanding-row-summary, cfc-expanding-row-details-caption and
* cfc-expanding-row-details-content components.
*/
@Component({
selector: 'cfc-expanding-row',
styles: [expanding_row_css],
template: `
<div #expandingRowMainElement
class="cfc-expanding-row"
cdkMonitorSubtreeFocus
[attr.tabindex]="isExpanded ? '0' : '-1'"
[class.cfc-expanding-row-has-focus]="isFocused"
[class.cfc-expanding-row-is-expanded]="isExpanded"
ve="CfcExpandingRow">
<ng-content></ng-content>
</div>`,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ExpandingRow {
/**
* The identifier for this node provided by the user code. We need this
* while we are emitting onToggle event.
*/
@Input() rowId!: string;
/**
* An ElementRef to the main element in this component. We need a reference
* to this element to compute the height. The height of cfc-expanding-row
* is used in [cfcExpandingRowHost] directive for scroll adjustments.
*/
@ViewChild('expandingRowMainElement', {static: true})
expandingRowMainElement!: ElementRef;
/**
* This @Output event emitter will be triggered when the user expands or
* collapses this node.
*/
@Output() onToggle = new EventEmitter<ExpandingRowToggleEvent>();
/**
* A boolean indicating if this node is expanded. This value is used to
* hide/show summary, caption, and content of the expanding row. There should
* only be one expanded row within [cfcExpandingRowHost] directive. And if
* there is an expanded row, there shouldn't be any focused rows.
*/
set isExpanded(value: boolean) {
const changed: boolean = this.isExpandedInternal !== value;
this.isExpandedInternal = value;
if (changed) {
this.isExpandedChange.emit();
this.changeDetectorRef.markForCheck();
}
}
/** TS getter for isExpanded property. */
get isExpanded(): boolean {
return this.isExpandedInternal;
}
/** Triggered when isExpanded property changes. */
isExpandedChange = new EventEmitter<void>();
/** Triggered when index property changes. */
indexChange = new EventEmitter<void>();
/**
* A boolean indicating if this node is focused. This value is used to add
* a CSS class that should render a blue border on the right. There should
* only be one focused row in [cfcExpandingRowHost] directive.
*/
set isFocused(value: boolean) {
this.isFocusedInternal = value;
this.changeDetectorRef.markForCheck();
}
/** TS getter for isFocused property. */
get isFocused(): boolean {
return this.isFocusedInternal;
}
/** The index of the row in the context of the entire collection. */
set index(value: number) {
const changed: boolean = this.indexInternal !== value;
this.indexInternal = value;
if (changed) {
this.indexChange.emit();
this.changeDetectorRef.markForCheck();
}
}
/** TS getter for index property. */
get index(): number {
return this.indexInternal;
}
/**
* We should probably rename this to summaryContentChild. Because technically
* this is not a @ViewChild that is in a template. This will be transcluded.
* Note that we are not using @ContentChild directive here. The @ContentChild
* will cause cyclic reference if the class definition for ExpandingRowSummary
* component is not in the same file as ExpandingRow.
*/
// TODO(b/109816955): remove '!', see go/strict-prop-init-fix.
summaryViewChild!: ExpandingRowSummary;
/**
* We compute the collapsed height (which is just height of
* cfc-expanding-row-summary component) in this component. This is used in
* [cfcExpandingRowHost] for scroll adjustment calculation.
*/
collapsedHeight = -1;
/** Internal storage for isExpanded public property. */
private isExpandedInternal = false;
/** Internal storage for isFocused public property. */
private isFocusedInternal = false;
/** Internal storage for index public property. */
// TODO(b/109816955): remove '!', see go/strict-prop-init-fix.
private indexInternal!: number;
/**
* This holds a reference to [cfcExpandingRowHost] directive. We need
* this reference to notify the host when this row expands/collapses or is
* focused.
*/
constructor(
public elementRef: ElementRef,
@Inject(EXPANDING_ROW_HOST_INJECTION_TOKEN) public expandingRowHost:
ExpandingRowHostBase,
private readonly changeDetectorRef: ChangeDetectorRef) {}
/**
* Handles click on cfc-expanding-row-summary component. This will expand
* this row and collapse the previously expanded row. The collapse & blur
* is handled in [cfcExpandingRowHost] directive.
*/
handleSummaryClick(): void {
this.collapsedHeight = this.elementRef.nativeElement
.querySelector('.cfc-expanding-row-summary')
.offsetHeight;
this.expandingRowHost.handleRowSummaryClick(this);
this.expand();
}
/**
* When user tabs into child cfc-expanding-row-summary component. This method
* will make sure we focuse on this row, and blur on previously focused row.
*/
handleSummaryFocus(): void {
this.focus();
}
/**
* cfc-expanding-row-details-caption component will call this function to
* notify click on its host element. Note that caption is only shown when
* the row is expanded. Hence this will collapse this row and put the focus
* on it.
* If a blacklisted element exists in the caption, clicking that element will
* not trigger the row collapse.
*/
handleCaptionClick(event: MouseEvent): void {
if (this.expandingRowHost.isBlacklisted(
event.target as {} as HTMLElement)) {
return;
}
this.expandingRowHost.handleRowCaptionClick(this);
this.collapse();
this.focus();
}
/**
* Gets the height of this component. This height is used in parent
* [cfcExpandingRowHost] directive to compute scroll adjustment.
*/
getHeight(): number {
return this.expandingRowMainElement.nativeElement.offsetHeight;
}
/**
* Expands this row. This will notify the host so that it can collapse
* previously expanded row. This function also emits onToggle @Output event
* to the user code.
*/
expand(): void {
this.isExpanded = true;
this.expandingRowHost.handleRowExpand(this);
// setTimeout here makes sure we scroll this row into view after animation.
setTimeout(() => {
this.expandingRowMainElement.nativeElement.focus();
});
this.onToggle.emit({rowId: this.rowId, isExpand: true});
}
/**
* Collapses this row. Setting isExpanded to false will make sure we hide
* the caption and details, and show cfc-expanding-row-summary component.
* This also emits onToggle @Output event to the user code.
*/
collapse(): void {
this.isExpanded = false;
this.onToggle.emit({rowId: this.rowId, isExpand: false});
}
/**
* Blurs this row. This should remove the blue border on the left if there
* is any. This function will remove DOM focus on the
* cfc-expanding-row-summary
* component.
*/
blur(): void {
this.isFocused = false;
this.summaryViewChild.blur();
}
/**
* Focuses this row. This should put blue border on the left. If there is
* any previous focus/selection, those should be gone. Parent
* [cfcExpandingRowHost] component takes care of that.
*/
focus(): void {
this.isFocused = true;
this.expandingRowHost.handleRowFocus(this);
// Summary child is not present currently. We need to NG2 to update the
// template.
setTimeout(() => {
this.summaryViewChild.focus();
});
}
/**
* We listen for TAB press here to make sure we trap the focus on the
* expanded
* row. If the row is not expanded, we don't care about this event since focus
* trap should work for expanded rows only.
*/
@HostListener('keydown', ['$event'])
handleKeyDown(event: KeyboardEvent) {
const charCode = event.which || event.keyCode;
switch (charCode) {
case 9:
if (!this.isExpanded) {
return;
}
this.trapFocus(event);
break;
default:
break;
}
}
/**
* When this row is expanded, this function traps the focus between focusable
* elements contained in this row.
*/
private trapFocus(event: KeyboardEvent): void {
const rowElement: HTMLElement = this.expandingRowMainElement.nativeElement;
const focusableEls: HTMLElement[] = [];
let lastFocusableEl: HTMLElement = rowElement;
if (focusableEls.length) {
lastFocusableEl = focusableEls[focusableEls.length - 1];
}
if (event.target === lastFocusableEl && !event.shiftKey) {
rowElement.focus();
event.preventDefault();
} else if (event.target === rowElement && event.shiftKey) {
lastFocusableEl.focus();
event.preventDefault();
}
}
}

View File

@ -0,0 +1,19 @@
/**
* @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 {Directive} from '@angular/core';
/**
* This directive is used to flag an element to NOT trigger collapsing an
* expanded row
*/
@Directive({
selector: '[cfcExpandingRowBlacklist]',
})
export class ExpandingRowBlacklist {}

View File

@ -0,0 +1,87 @@
/**
* @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
*/
export const expanding_row_css = `
::ng-deep [cfcExpandingRowHost] {
display: block;
margin-bottom: 2;
}
:host(cfc-expanding-row),
:host(cfc-expanding-row-summary),
:host(cfc-expanding-row-details-caption),
:host(cfc-expanding-row-details-content) {
display: block;
}
.cfc-expanding-row {
background: white;
border-top: 1 solid black;
box-shadow: 0 1 1 gray;
transition: margin 1 1;
will-change: margin;
}
.cfc-expanding-row.cfc-expanding-row-is-expanded {
margin: 1 (-1);
}
.cfc-expanding-row:focus {
outline: none;
}
.cfc-expanding-row-summary {
display: flex;
border-left: 6 solid transparent;
cursor: pointer;
padding: 6 2;
}
.cfc-expanding-row-summary:focus {
outline: none;
border-left-color: $cfc-color-active;
}
// Adjust icons to be positioned correctly in the row.
.cfc-expanding-row-summary::ng-deep cfc-icon {
margin-top: 3;
}
.cfc-expanding-row-details-caption {
display: flex;
cursor: pointer;
padding: 4 2;
}
.cfc-expanding-row-details-caption::ng-deep a,
.cfc-expanding-row-details-caption::ng-deep a:visited,
.cfc-expanding-row-details-caption::ng-deep a .cfc-external-link-content {
border-color: $cfc-color-text-primary-inverse;
color: $cfc-color-text-primary-inverse;
}
// Adjust icons to be positioned correctly in the row.
::ng-deep cfc-icon {
margin-top: 3;
}
.cfc-expanding-row-details-content {
padding: 2;
}
.cfc-expanding-row-details-content::ng-deep .ace-kv-list.cfc-full-bleed {
width: 200px;
}
.cfc-expanding-row-accessibility-text {
display: none;
}`;

View File

@ -0,0 +1,58 @@
/**
* @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 {ChangeDetectionStrategy, ChangeDetectorRef, Component, Host, Input, OnDestroy} from '@angular/core';
import {Subject} from 'rxjs';
import {takeUntil} from 'rxjs/operators';
import {ExpandingRow} from './expanding_row';
import { expanding_row_css } from './expanding_row_css';
/**
* This component should be within cfc-expanding-row component. The caption
* is only visible when the row is expanded.
*/
@Component({
selector: 'cfc-expanding-row-details-caption',
styles: [expanding_row_css],
template: `
<div *ngIf="expandingRow.isExpanded"
(click)="expandingRow.handleCaptionClick($event)"
[style.backgroundColor]="color"
class="cfc-expanding-row-details-caption">
<ng-content></ng-content>
</div>`,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ExpandingRowDetailsCaption implements OnDestroy {
/** The background color of this component. */
@Input() color: string = 'blue';
/** This is triggered when this component is destroyed. */
private readonly onDestroy = new Subject();
/**
* We need a reference to parent cfc-expanding-row component here to hide
* this component when the row is collapsed. We also need to relay clicks
* to the parent component.
*/
constructor(
@Host() public expandingRow: ExpandingRow,
changeDetectorRef: ChangeDetectorRef) {
this.expandingRow.isExpandedChange.pipe(takeUntil(this.onDestroy))
.subscribe(() => {
changeDetectorRef.markForCheck();
});
}
/** When component is destroyed, unlisten to isExpanded. */
ngOnDestroy(): void {
this.onDestroy.next();
}
}

View File

@ -0,0 +1,50 @@
/**
* @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 {ChangeDetectionStrategy, ChangeDetectorRef, Component, Host, OnDestroy} from '@angular/core';
import {Subscription} from 'rxjs';
import {ExpandingRow} from './expanding_row';
import { expanding_row_css } from './expanding_row_css';
/**
* This component should be within cfc-expanding-row component. Note that the
* content is visible only when the row is expanded.
*/
@Component({
styles: [expanding_row_css],
selector: 'cfc-expanding-row-details-content',
template: `
<div class="cfc-expanding-row-details-content"
*ngIf="expandingRow.isExpanded">
<ng-content></ng-content>
</div>`,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ExpandingRowDetailsContent implements OnDestroy {
/** Used for unsubscribing to changes in isExpanded parent property. */
private isExpandedChangeSubscription: Subscription;
/**
* We need a reference to parent cfc-expanding-row component to make sure we
* hide this component if the row is collapsed.
*/
constructor(
@Host() public expandingRow: ExpandingRow,
changeDetectorRef: ChangeDetectorRef) {
this.isExpandedChangeSubscription =
this.expandingRow.isExpandedChange.subscribe(() => {
changeDetectorRef.markForCheck();
});
}
/** Unsubscribe from changes in parent isExpanded property. */
ngOnDestroy(): void {
this.isExpandedChangeSubscription.unsubscribe();
}
}

View File

@ -0,0 +1,519 @@
/**
* @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 {AfterContentInit, AfterViewInit, ChangeDetectionStrategy, Component, ContentChildren, ElementRef, EventEmitter, forwardRef, HostListener, Input, OnDestroy, Output, QueryList, ViewChild} from '@angular/core';
import {Subscription} from 'rxjs';
import {EXPANDING_ROW_HOST_INJECTION_TOKEN, ExpandingRow, ExpandingRowHostBase} from './expanding_row';
/**
* We use this class in <cfc-expanding-row/> template to identify the row.
* The [cfcExpandingRowHost] directive also uses this class to check if a given
* HTMLElement is within an <cfc-expanding-row/>.
*/
const EXPANDING_ROW_CLASS_NAME = 'cfc-expanding-row';
/** Throttle duration in milliseconds for repeated key presses. */
export const EXPANDING_ROW_KEYPRESS_THORTTLE_MS = 50;
/**
* This type union is created to make arguments of handleUpOrDownPress*
* methods in ExpandingRowHost class more readable.
*/
type UpOrDown = 'up'|'down';
/**
* This is the wrapper directive for the cfc-expanding-row components. Note that
* we wanted to make this a directive instead of component because child
* cfc-expanding-row components does not have to be a direct child.
*/
@Component({
selector: 'cfc-expanding-row-host',
template: `
<div #firstFocusable
(focus)="focusOnLastFocusedRow()"
tabindex="0">
</div>
<ng-content></ng-content>
<div #lastFocusable
(focus)="focusOnLastFocusedRow()"
tabindex="0">
</div>`,
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [
{provide: EXPANDING_ROW_HOST_INJECTION_TOKEN, useExisting: ExpandingRowHost}
],
})
export class ExpandingRowHost implements AfterViewInit, OnDestroy,
ExpandingRowHostBase {
/**
* An HTML selector (e.g. "body") for the scroll element. We need this to
* make some scroll adjustments.
*/
@Input() scrollElementSelector = '.cfc-panel-body-scrollable';
/**
* An HTML selector (e.g. "body") for the click root. While the row is
* expanded, and user clicks outside of the expanded row, we collapse this row
* But to do this, we need to know the clickable area.
*/
@Input() clickRootElementSelector = 'cfc-panel-body';
/**
* The @Output will be triggered when the user wants to focus on the
* previously expanded row, and we are already at the first row. The logs team
* will use this to prepend data on demand.
*/
@Output() onPrepend = new EventEmitter<void>();
/** A reference to the last focusable element in list of expanding rows. */
@ViewChild('lastFocusable', {static: true}) lastFocusableElement!: ElementRef;
/** A reference to the first focusable element in list of expanding rows. */
@ViewChild('firstFocusable', {static: true})
firstFocusableElement!: ElementRef;
/**
* A reference to all child cfc-expanding-row elements. We will need for
* keyboard accessibility and scroll adjustments. For example, we need to know
* which row is previous row when user presses "left arrow" on a focused row.
*/
@ContentChildren(forwardRef(() => ExpandingRow), {descendants: true})
contentRows!: QueryList<ExpandingRow>;
/**
* Keeps track of the last row that had focus before focus left the list
* of expanding rows.
*/
lastFocusedRow?: ExpandingRow = undefined;
/**
* Focused rows just show a blue left border. This node is not expanded. We
* need to keep a reference to the focused row to unfocus when another row
* is focused.
*/
private focusedRow?: ExpandingRow = undefined;
/**
* This is the expanded row. If there is an expanded row there shouldn't be
* any focused rows. We need a reference to this. For example we need to
* collapse the currently expanded row, if another row is expanded.
*/
private expandedRow?: ExpandingRow = undefined;
/**
* This is just handleRootMouseUp.bind(this). handleRootMouseUp handles
* click events on root element (defined by clickRootElementSelector @Input)
* Since we attach the click listener dynamically, we need to keep this
* function around. This enables us to detach the click listener when
* component is destroyed.
*/
private handleRootMouseUpBound: EventListenerObject =
this.handleRootMouseUp.bind(this);
/**
* 16px is the margin animation we have on cfc-expanding-row component.
* We need this value to compute scroll adjustments.
*/
private static rowMargin = 16;
/** Subscription to changes in the expanding rows. */
// TODO(b/109816955): remove '!', see go/strict-prop-init-fix.
private rowChangeSubscription!: Subscription;
/**
* When component initializes we need to attach click listener to the root
* element. This click listener will allows us to collapse the
* currently expanded row when user clicks outside of it.
*/
ngAfterViewInit(): void {
const clickRootElement: HTMLElement = this.getClickRootElement();
if (!clickRootElement) {
return;
}
clickRootElement.addEventListener('mouseup', this.handleRootMouseUpBound);
this.rowChangeSubscription = this.contentRows.changes.subscribe(() => {
this.recalcRowIndexes();
});
this.recalcRowIndexes();
}
/**
* Detaches the click listener on the root element. Note that we are attaching
* this listener on ngAfterViewInit function.
*/
ngOnDestroy(): void {
const clickRootElement: HTMLElement = this.getClickRootElement();
if (!clickRootElement) {
return;
}
clickRootElement.removeEventListener(
'mouseup', this.handleRootMouseUpBound);
if (this.rowChangeSubscription) {
this.rowChangeSubscription.unsubscribe();
}
}
/**
* Handles caption element click on a cfc-expanding-row component. Note
* that caption element is visible only when the row is expanded. So this
* means we will collapse the expanded row. The scroll adjustment below
* makes sure that the mouse stays under the summary of the expanded row
* when the row collapses.
*/
handleRowCaptionClick(row: ExpandingRow): void {
const scrollAdjustment: number = -ExpandingRowHost.rowMargin;
const scrollElement: HTMLElement = this.getScrollElement() as HTMLElement;
if (!scrollElement) {
return;
}
scrollElement.scrollTop += scrollAdjustment;
}
/**
* Handles summary element click on a cfc-expanding-row component. Note
* that summary element is visible only when the row is collapsed. So this
* event will fired prior to expansion of a collapsed row. Scroll adjustment
* below makes sure mouse stays on the caption element when the collapsed
* row expands.
*/
handleRowSummaryClick(row: ExpandingRow): void {
const hadPreviousSelection: boolean = !!this.expandedRow;
const previousSelectedRowIndex: number =
this.getRowIndex(this.expandedRow as ExpandingRow);
const newSelectedRowIndex: number = this.getRowIndex(row);
const previousCollapsedHeight: number =
this.getSelectedRowCollapsedHeight();
const previousExpansionHeight = this.getSelectedRowExpandedHeight();
if (this.expandedRow) {
return;
}
let scrollAdjustment = 0;
const scrollElement: HTMLElement = this.getScrollElement() as HTMLElement;
if (!scrollElement) {
return;
}
if (previousExpansionHeight > 0 && previousCollapsedHeight >= 0) {
scrollAdjustment = previousExpansionHeight - previousCollapsedHeight;
}
const newSelectionIsInfrontOfPrevious: boolean =
newSelectedRowIndex > previousSelectedRowIndex;
const multiplier = newSelectionIsInfrontOfPrevious ? -1 : 0;
scrollAdjustment =
scrollAdjustment * multiplier + ExpandingRowHost.rowMargin;
scrollElement.scrollTop += scrollAdjustment;
}
/**
* Handles expansion of a row. When a new row expands, we need to remove
* previous expansion and collapse. We also need to save the currently
* expanded row so that we can collapse this row once another row expands.
*/
handleRowExpand(row: ExpandingRow): void {
this.removePreviousFocus();
this.removePreviousExpansion();
this.expandedRow = row;
}
/**
* Handles focus on a row. When a new row gets focus (note that this is
* different from expansion), we need to remove previous focus and expansion.
* We need to save the reference to this focused row so that we can unfocus
* this row when another row is focused.
*/
handleRowFocus(row: ExpandingRow): void {
// Do not blur then refocus the row if it's already selected.
if (row === this.focusedRow) {
return;
}
this.removePreviousFocus();
this.removePreviousExpansion();
this.focusedRow = row;
}
/**
* Called when shift+tabbing from the first focusable element after the list
* of expanding rows or tabbing from the last focusable element before.
*/
focusOnLastFocusedRow(): void {
if (!this.lastFocusedRow) {
this.lastFocusedRow = this.contentRows.toArray()[0];
}
this.lastFocusedRow.focus();
}
/**
* Function that is called by expanding row summary to focus on the last
* focusable element before the list of expanding rows.
*/
focusOnPreviousFocusableElement(): void {
this.lastFocusedRow = this.focusedRow;
}
/**
* Function that is called by expanding row summary to focus on the next
* focusable element after the list of expanding rows.
*/
focusOnNextFocusableElement(): void {
this.lastFocusedRow = this.focusedRow;
}
/**
* Handles keydown event on the host. We are just concerned with up,
* down arrow, ESC, and ENTER presses here. Note that Up/Down presses
* can be repeated.
*
* - Up: Focuses on the row above.
* - Down: Focuses on the row below.
* - Escape: Collapses the expanded row.
* - Enter: Expands the focused row.
*/
@HostListener('keydown', ['$event'])
handleKeyDown(event: KeyboardEvent) {
}
/**
* Recursively returns true if target HTMLElement is within a
* cfc-expanding-row component. It will return false otherwise.
* We need this function in handleRootMouseUp to collapse the expanded row
* when user clicks outside of all expanded rows.
*/
private isTargetInRow(target: HTMLElement): boolean {
return target.classList.contains(EXPANDING_ROW_CLASS_NAME);
}
/**
* Gets the click root element that is described by clickRootElementSelector
* @Input value.
*/
private getClickRootElement(): HTMLElement {
return document.querySelector(this.clickRootElementSelector) as HTMLElement;
}
/**
* Handles all of the mouseup events on the click root. When user clicks
* outside of an expanded row, we need to collapse that row.
* We trigger collapse by calling handleCaptionClick() on the expanded row.
*/
private handleRootMouseUp(event: MouseEvent): void {
if (!this.expandedRow) {
return;
}
if (!this.isTargetInRow(event.target as {} as HTMLElement)) {
this.expandedRow.handleCaptionClick(event);
}
}
/**
* Check if element is blacklisted. Blacklisted elements will not collapse an
* open row when clicked.
*/
isBlacklisted(element: HTMLElement|null): boolean {
const clickRoot = this.getClickRootElement();
while (element && element !== clickRoot) {
if (element.hasAttribute('cfcexpandingrowblacklist')) {
return true;
}
element = element.parentElement;
}
return false;
}
/**
* Removes focus state from a previously focused row. We blur this row and
* set the focusedRow to undefined in this method. This usually happens when
* another row is focused.
*/
private removePreviousFocus(): void {
if (this.focusedRow) {
this.focusedRow.blur();
this.focusedRow = undefined;
}
}
/**
* Removes the expanded state from a previously expanded row. We collapse this
* row and set the expandedRow to undefined in this method. This usually
* happens when another row is expanded.
*/
private removePreviousExpansion(): void {
if (this.expandedRow) {
this.expandedRow.collapse();
this.expandedRow = undefined;
}
}
/**
* Gets the collapsed height of the currently expanded row. We need this for
* scroll adjustments. Note that collapsed height of a cfc-expanding-row
* component is equal to height of cfc-expanding-row-summary component within
* the row.
*/
private getSelectedRowCollapsedHeight(): number {
if (this.expandedRow) {
return this.expandedRow.collapsedHeight;
} else {
return -1;
}
}
/**
* Gets the current height of the expanded row. We need this value for the
* scroll adjustment computation.
*/
private getSelectedRowExpandedHeight(): number {
if (this.expandedRow) {
return this.expandedRow.getHeight();
} else {
return -1;
}
}
/**
* Gets the HTML element described by scrollElementSelector @Input value.
* We need this value for scroll adjustments.
*/
private getScrollElement(): HTMLElement|undefined {
if (!this.scrollElementSelector) {
return undefined;
}
return document.querySelector(this.scrollElementSelector) as HTMLElement;
}
/**
* Handles escape presses on the host element. Escape removes previous focus
* if there is one. If there is an expanded row, escape row collapses this
* row and focuses on it. A subsequent escape press will blur this row.
*/
private handleEscapePress(): void {
this.removePreviousFocus();
if (this.expandedRow) {
this.expandedRow.collapse();
this.expandedRow.focus();
this.expandedRow = undefined;
}
}
/**
* Handles enter keypress. If there is a focused row, an enter key press on
* host element will expand this row.
*/
private handleEnterPress(): void {
if (document.activeElement !== this.focusedRowSummary()) {
return;
}
if (this.focusedRow) {
this.focusedRow.expand();
}
}
/** Returns the HTMLElement that is the currently focused row summary. */
private focusedRowSummary(): HTMLElement|undefined {
return this.focusedRow ?
this.focusedRow.summaryViewChild.mainElementRef.nativeElement :
undefined;
}
/**
* Returns the index of a given row. This enables us to figure out the row
* above/below the focused row.
*/
private getRowIndex(rowToLookFor: ExpandingRow): number {
return rowToLookFor ? rowToLookFor.index : -1;
}
/**
* Handles up/down arrow presses on the host element. Up arrow press will
* focus/expand on the row above. Down arrow press will focus/expand the row
* below. If we have a focus on the current row, this function will focus on
* the computed (the one above or below) row. If host has an expanded row,
* this function will expand the computed row.
*/
private handleUpOrDownPressOnce(upOrDown: UpOrDown, event: KeyboardEvent):
void {
event.preventDefault();
// If row is expanded but focus is inside the expanded element, arrow
// key presses should not do anything.
if (this.expandedRow &&
document.activeElement !==
this.expandedRow.expandingRowMainElement.nativeElement) {
return;
}
// If focus is inside a collapsed row header, arrow key presses should not
// do anything.
if (this.focusedRow &&
document.activeElement !== this.focusedRowSummary()) {
return;
}
// We only want screen reader to read the message the first time we enter
// the list of expanding rows, so we must reset the variable here
this.lastFocusedRow = undefined;
const rowToLookFor: ExpandingRow|undefined =
this.expandedRow || this.focusedRow;
if (!rowToLookFor) {
return;
}
const isFocus: boolean = (rowToLookFor === this.focusedRow);
const rowIndex: number = this.getRowIndex(rowToLookFor);
const contentRowsArray: ExpandingRow[] = this.contentRows.toArray();
if (rowIndex < 0) {
return;
}
const potentialIndex: number = (upOrDown === 'up' ? -1 : +1) + rowIndex;
if (potentialIndex < 0) {
this.onPrepend.emit();
return;
}
if (potentialIndex >= contentRowsArray.length) {
return;
}
const potentialRow: ExpandingRow = contentRowsArray[potentialIndex];
if (isFocus) {
potentialRow.focus();
} else {
potentialRow.expand();
}
}
// Updates all of the rows with their new index.
private recalcRowIndexes() {
let index = 0;
setTimeout(() => {
this.contentRows.forEach((row: ExpandingRow) => {
row.index = index++;
});
});
}
}

View File

@ -0,0 +1,42 @@
/**
* @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 {CommonModule} from '@angular/common';
import {NgModule} from '@angular/core';
import {ExpandingRow} from './expanding_row';
import {ExpandingRowBlacklist} from './expanding_row_blacklist';
import {ExpandingRowDetailsCaption} from './expanding_row_details_caption';
import {ExpandingRowDetailsContent} from './expanding_row_details_content';
import {ExpandingRowHost} from './expanding_row_host';
import {ExpandingRowSummary} from './expanding_row_summary';
/** The main module for the cfc-expanding-row component. */
@NgModule({
declarations: [
ExpandingRow,
ExpandingRowDetailsCaption,
ExpandingRowDetailsContent,
ExpandingRowHost,
ExpandingRowSummary,
ExpandingRowBlacklist,
],
exports: [
ExpandingRow,
ExpandingRowDetailsCaption,
ExpandingRowDetailsContent,
ExpandingRowHost,
ExpandingRowSummary,
ExpandingRowBlacklist,
],
imports: [
CommonModule,
],
})
export class ExpandingRowModule {
}

View File

@ -0,0 +1,219 @@
/**
* @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 {ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, Host, HostListener, OnDestroy, ViewChild} from '@angular/core';
import {Subscription} from 'rxjs';
import {ExpandingRow} from './expanding_row';
import { expanding_row_css } from './expanding_row_css';
const KEY_CODE_TAB = 9;
/**
* This component should be used within cfc-expanding-row component. Note that
* summary is visible only when the row is collapsed.
*/
@Component({
selector: 'cfc-expanding-row-summary',
styles: [expanding_row_css],
template: `
<div *ngIf="!expandingRow.isExpanded"
#expandingRowSummaryMainElement
class="cfc-expanding-row-summary"
tabindex="-1"
(click)="expandingRow.handleSummaryClick()"
(focus)="handleFocus()">
<ng-content></ng-content>
<div class="cfc-expanding-row-accessibility-text">.</div>
<div class="cfc-expanding-row-accessibility-text"
i18n="This is the label used to indicate that the user is in a list of expanding rows.">
Row {{expandingRow.index + 1}} in list of expanding rows.
</div>
<div *ngIf="isPreviouslyFocusedRow()"
class="cfc-expanding-row-accessibility-text"
i18n="This is the label used for the first row in list of expanding rows.">
Use arrow keys to navigate.
</div>
</div>`,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ExpandingRowSummary implements OnDestroy {
/**
* A reference to the main element. This element should be focusable. We need
* reference to compute collapsed height of the row. We also use this
* reference for focus and blur methods below.
*/
@ViewChild('expandingRowSummaryMainElement', {static: false})
mainElementRef!: ElementRef;
/** Subscription for changes in parent isExpanded property. */
private isExpandedSubscription: Subscription;
/** Subscription for changes in parent index property. */
private indexSubscription: Subscription;
/**
* We need the parent cfc-expanding-row component here to hide this element
* when the row is expanded. cfc-expanding-row-details-caption element
* will act as a header for expanded rows. We also need to relay tab-in and
* click events to the parent.
*/
constructor(
@Host() public expandingRow: ExpandingRow,
changeDetectorRef: ChangeDetectorRef) {
this.expandingRow.summaryViewChild = this;
this.isExpandedSubscription =
this.expandingRow.isExpandedChange.subscribe(() => {
changeDetectorRef.markForCheck();
});
this.indexSubscription = this.expandingRow.indexChange.subscribe(() => {
changeDetectorRef.markForCheck();
});
}
/** When component is destroyed, unlisten to isExpanded. */
ngOnDestroy(): void {
if (this.isExpandedSubscription) {
this.isExpandedSubscription.unsubscribe();
}
if (this.indexSubscription) {
this.indexSubscription.unsubscribe();
}
}
/**
* Handles focus event on the element. We basically want to detect any focus
* in this component and relay this information to parent cfc-expanding-row
* component.
*/
handleFocus(): void {
// Clicking causes a focus event to occur before the click event. Filter
// out click events using the cdkFocusMonitor.
//
// TODO(b/62385992) Use the KeyboardFocusService to detect focus cause
// instead of creating multiple monitors on a page.
if (this.expandingRow.expandingRowMainElement.nativeElement.classList
.contains('cdk-mouse-focused')) {
return;
}
if (!this.expandingRow.isFocused && !this.expandingRow.isExpanded) {
this.expandingRow.handleSummaryFocus();
}
}
/**
* Handles tab & shift+tab presses on expanding row summaries in case there
* are tabbable elements inside the summaries.
*/
@HostListener('keydown', ['$event'])
handleKeyDown(event: KeyboardEvent) {
const charCode = event.which || event.keyCode;
if (charCode === KEY_CODE_TAB) {
this.handleTabKeypress(event);
}
}
/**
* Handles tab and shift+tab presses inside expanding row summaries;
*
* From inside collapsed row summary:
* - Tab: If focus was on the last focusable child, should shift focus to
* the next focusable element outside the list of expanding rows.
* - Shift+tab: If focus was on first focusable child, should shift focus to
* the main collapsed row summary element
* If focus was on main collapsed row summary element, should
* shift focus to the last focusable element before the list of
* expanding rows.
*/
handleTabKeypress(event: KeyboardEvent): void {
const focusableChildren = this.getFocusableChildren();
if (focusableChildren.length === 0) {
return;
}
// Shift+tab on expanding row summary should focus on last focusable element
// before expanding row list. Otherwise, if shift+tab is pressed on first
// focusable child inside expanding row summary, it should focus on main
// expanding row summary element.
if (event.shiftKey &&
document.activeElement === this.mainElementRef.nativeElement) {
event.preventDefault();
this.expandingRow.expandingRowHost.focusOnPreviousFocusableElement();
return;
} else if (
event.shiftKey && document.activeElement === focusableChildren[0]) {
event.preventDefault();
this.expandingRow.focus();
}
// If tab is pressed on the last focusable element inside an expanding row
// summary, focus should be set to the next focusable element after the list
// of expanding rows.
if (!event.shiftKey &&
document.activeElement ===
focusableChildren[focusableChildren.length - 1]) {
event.preventDefault();
this.expandingRow.expandingRowHost.focusOnNextFocusableElement();
}
}
/**
* Finds the row that had focus before focus left the list of expanding rows
* and checks if the current row summary is that row.
*/
isPreviouslyFocusedRow(): boolean {
if (!this.expandingRow.expandingRowHost.contentRows) {
return false;
}
const expandingRowHost = this.expandingRow.expandingRowHost;
if (!this.mainElementRef || !expandingRowHost.lastFocusedRow) {
return false;
}
if (!expandingRowHost.lastFocusedRow.summaryViewChild.mainElementRef) {
return false;
}
// If the current expanding row summary was the last focused one before
// focus exited the list, then return true to trigger the screen reader
if (this.mainElementRef.nativeElement ===
expandingRowHost.lastFocusedRow.summaryViewChild.mainElementRef
.nativeElement) {
return true;
}
return false;
}
/** Puts the DOM focus on the main element. */
focus(): void {
if (this.mainElementRef &&
document.activeElement !== this.mainElementRef.nativeElement) {
this.mainElementRef.nativeElement.focus();
}
}
/** Removes the DOM focus on the main element. */
blur(): void {
if (!this.mainElementRef) {
return;
}
this.mainElementRef.nativeElement.blur();
}
/** Returns array of focusable elements within this component. */
private getFocusableChildren(): HTMLElement[] {
return [];
}
}

View File

@ -0,0 +1,22 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
/**
* This interface is used to send toggle (expand/collapse) events to the user
* code.
*/
export interface ExpandingRowToggleEvent {
/** The identifier of the row that was toggled. */
rowId: string;
/**
* A boolean indicating whether or not this row was expanded. This is set to
* false if the row was collapsed.
*/
isExpand: boolean;
}

View File

@ -0,0 +1,35 @@
<!doctype html>
<html>
<head>
<!-- Prevent the browser from requesting any favicon. -->
<link rel="icon" href="data:,">
</head>
<body>
<h1>Change Detection Benchmark</h1>
<div id="rendererMode">...</div>
<benchmark-root>loading...</benchmark-root>
<script>
addEventListener('DOMContentLoaded', () => {
window.ngDevMode = location.hash == '#ngDevMode';
// DevServer has automatic bootstrap code, so if we already have <scripts> than we don't need to bootstrap
var alreadyBootstraped = document.querySelectorAll('script').length > 1; // 1 for ourselves
if (!alreadyBootstraped) {
function loadScript(url) {
var script = document.createElement('script');
script.src = url;
document.body.append(script);
}
loadScript('/npm/node_modules/zone.js/dist/zone.js');
loadScript(document.location.search.endsWith('debug') ? 'bundle.min_debug.js' : 'bundle.min.js');
}
});
</script>
</body>
</html>

View File

@ -0,0 +1,19 @@
/**
* @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 {platformBrowser} from '@angular/platform-browser';
import {ExpandingRowBenchmarkModule} from './benchmark';
import {ExpandingRowBenchmarkModuleNgFactory} from './benchmark.ngfactory';
setMode(ExpandingRowBenchmarkModule.hasOwnProperty('ngModuleDef') ? 'Ivy' : 'ViewEngine');
platformBrowser().bootstrapModuleFactory(ExpandingRowBenchmarkModuleNgFactory);
function setMode(name: string): void {
document.querySelector('#rendererMode') !.textContent = `Render Mode: ${name}`;
}