<html>
<head>
<title>Angular 2 ag-Grid starter</title>
<script>document.write('<base href="' + document.location + '" />');</script>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"/>
<!-- Polyfills -->
<script src="https://unpkg.com/core-js/client/shim.min.js"></script>
<script src="https://unpkg.com/zone.js@0.8.17?main=browser"></script>
<script src="https://unpkg.com/systemjs@0.19.39/dist/system.src.js"></script>
<script>
var appLocation = '';
var boilerplatePath = '';
var systemJsMap = {"ag-grid":"https:\/\/unpkg.com\/ag-grid@13.2.0\/dist\/ag-grid.js","ag-grid\/main":"https:\/\/unpkg.com\/ag-grid@13.2.0\/dist\/ag-grid.js","ag-grid-enterprise":"https:\/\/unpkg.com\/ag-grid-enterprise@13.2.0\/main.js","ag-grid-react":"npm:ag-grid-react@13.2.0\/main.js","ag-grid-angular":"npm:ag-grid-angular@13.2.0\/main.js"};
</script>
<script src="systemjs.config.js"></script>
<script>
System.import('main.ts').catch(function(err){ console.error(err); });
</script>
</head>
<body>
<my-app>Loading ag-Grid example…</my-app>
</body>
</html>
import {NgModule} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
import {FormsModule} from '@angular/forms'; // <-- NgModel lives here
// ag-grid
import {AgGridModule} from "ag-grid-angular";
import {NgReduxModule} from '@angular-redux/store';
import {CurrentReportActions} from './current-report-actions';
import {MessagesActions} from './messages-actions';
import {RichGridComponent} from "./rich-grid.component";
@NgModule({
imports: [
BrowserModule,
FormsModule, // <-- import the FormsModule before binding with [(ngModel)]
AgGridModule.withComponents([]),
NgReduxModule
],
declarations: [
RichGridComponent
],
providers: [
CurrentReportActions,
MessagesActions
],
bootstrap: [RichGridComponent]
})
export class AppModule {}
<div class="main">
<div class="report">
<button class="btn btn-primary" [hidden]="showGrid" (click)="runReport()">
Run Report
</button>
<ag-grid-angular class="grid ag-dark" *ngIf="showGrid"
[gridOptions]="gridOptions"
[columnDefs]="columnDefs"
[rowData]="rowData"
[icons]="icons"
enableColResize
enableSorting
enableFilter
groupHeaders
suppressRowClickSelection
toolPanelSuppressGroups
toolPanelSuppressValues
rowHeight="22"
rowSelection="multiple"
(gridReady)="onReady($event)">
</ag-grid-angular>
</div>
<div class="console">
<h3>Console</h3>
<div class="message" *ngFor="let message of messages">
{{message}}
</div>
</div>
</div>
import {Component, OnInit} from "@angular/core";
import {ColumnApi, GridApi, GridOptions} from "ag-grid/main";
// only import this if you are using the ag-Grid-Enterprise
import "ag-grid-enterprise";
import {NgRedux} from '@angular-redux/store';
import {combineReducers} from 'redux';
import {AppState} from './app-state';
import {columnDefs} from './dataset';
import {DatasourceService} from './datasource';
import {ReportService} from './report.service';
import {CurrentReportActions} from './current-report-actions';
import {currentReportReducer} from './current-report-reducer';
import {messagesReducer} from './messages-reducer';
@Component({
selector: 'my-app',
templateUrl: './rich-grid.component.html',
styles: [`
.main {
display: flex;
flex-direction: column;
}
.btn {
padding: 24px;
font-size: 20px;
background-color: lightblue;
}
.report, .console {
flex: 1 0 50%;
height: 50% !important;
}
.grid {
width: 100%;
}
.console {
background-color: black;
color: darkgreen;
overflow-y: auto;
}
.console h3 {
margin: 0;
padding: 12px 18px;
background-color: #333;
color: white;
}
.message {
margin: 4px;
padding: 8px;
border: 1px solid darkgreen;
border-radius: 5px;
}
`],
providers: [DatasourceService, ReportService]
})
export class RichGridComponent implements OnInit {
public gridOptions: GridOptions;
public rowData: any[];
public columnDefs: any[];
public messages: string[] = [];
public showGrid: boolean;
public api: GridApi;
public columnApi: ColumnApi;
constructor(
private ngRedux: NgRedux<AppState>,
private datasource: DatasourceService,
private currentReportActions: CurrentReportActions
) {
this.ngRedux.configureStore(combineReducers<AppState>({
currentReport: currentReportReducer,
messages: messagesReducer
}), {});
this.gridOptions = <GridOptions>{
columnDefs: columnDefs,
rowModelType: 'enterprise',
cacheBlockSize: 20,
groupSuppressAutoColumn: true
};
}
public ngOnInit() {
this.ngRedux.select((state: AppState) => state.messages).subscribe((messages) => {
this.messages = messages;
});
}
public runReport() {
this.currentReportActions.set(123);
this.showGrid = true;
}
public onReady(params) {
this.api = params.api;
this.api.setEnterpriseDatasource(this.datasource);
this.columnApi = params.columnApi;
console.error('ready');
this.api.forEachNode((rowNode) => {
setTimeout(() => {
rowNode.setExpanded(true);
}, 5000);
});
}
}
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from 'app/app.module';
platformBrowserDynamic().bootstrapModule(AppModule);
var templateUrlRegex = /templateUrl\s*:(\s*['"`](.*?)['"`]\s*)/gm;
var stylesRegex = /styleUrls *:(\s*\[[^\]]*?\])/g;
var stringRegex = /(['`"])((?:[^\\]\\\1|.)*?)\1/g;
module.exports.translate = function(load){
if (load.source.indexOf('moduleId') != -1) return load;
var url = document.createElement('a');
url.href = load.address;
var basePathParts = url.pathname.split('/');
basePathParts.pop();
var basePath = basePathParts.join('/');
var baseHref = document.createElement('a');
baseHref.href = this.baseURL;
baseHref = baseHref.pathname;
if (!baseHref.startsWith('/base/')) { // it is not karma
basePath = basePath.replace(baseHref, '');
}
load.source = load.source
.replace(templateUrlRegex, function(match, quote, url){
var resolvedUrl = url;
if (url.startsWith('.')) {
resolvedUrl = basePath + url.substr(1);
}
return 'templateUrl: "' + resolvedUrl + '"';
})
.replace(stylesRegex, function(match, relativeUrls) {
var urls = [];
while ((match = stringRegex.exec(relativeUrls)) !== null) {
if (match[2].startsWith('.')) {
urls.push('"' + basePath + match[2].substr(1) + '"');
} else {
urls.push('"' + match[2] + '"');
}
}
return "styleUrls: [" + urls.join(', ') + "]";
});
return load;
};
/**
* WEB ANGULAR VERSION
* (based on systemjs.config.js in angular.io)
* System configuration for Angular samples
* Adjust as necessary for your application needs.
*/
(function (global) {
System.config({
// DEMO ONLY! REAL CODE SHOULD NOT TRANSPILE IN THE BROWSER
transpiler: 'ts',
typescriptOptions: {
// Copy of compiler options in standard tsconfig.json
"target": "es5",
"module": "system", //gets rid of console warning
"moduleResolution": "node",
"sourceMap": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"lib": ["es2015", "dom"],
"noImplicitAny": true,
"suppressImplicitAnyIndexErrors": true
},
meta: {
'typescript': {
"exports": "ts"
}
},
paths: {
// paths serve as alias
'npm:': 'https://unpkg.com/'
},
// RxJS makes a lot of requests to unpkg. This guy addressed it:
// https://github.com/OasisDigital/rxjs-system-bundle.
bundles: {
"npm:rxjs-system-bundle@5.1.1/Rx.system.js": [
"rxjs",
"rxjs/*",
"rxjs/operator/*",
"rxjs/observable/*",
"rxjs/scheduler/*",
"rxjs/symbol/*",
"rxjs/add/operator/*",
"rxjs/add/observable/*",
"rxjs/util/*"
]
},
// map tells the System loader where to look for things
map: Object.assign({
// angular bundles
'@angular/animations': 'npm:@angular/animations/bundles/animations.umd.js',
'@angular/animations/browser': 'npm:@angular/animations/bundles/animations-browser.umd.js',
'@angular/core': 'npm:@angular/core/bundles/core.umd.js',
'@angular/common': 'npm:@angular/common/bundles/common.umd.js',
'@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js',
'@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js',
'@angular/platform-browser/animations': 'npm:@angular/platform-browser/bundles/platform-browser-animations.umd.js',
'@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js',
'@angular/http': 'npm:@angular/http/bundles/http.umd.js',
'@angular/router': 'npm:@angular/router/bundles/router.umd.js',
'@angular/router/upgrade': 'npm:@angular/router/bundles/router-upgrade.umd.js',
'@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js',
'@angular/upgrade': 'npm:@angular/upgrade/bundles/upgrade.umd.js',
'@angular/upgrade/static': 'npm:@angular/upgrade/bundles/upgrade-static.umd.js',
'@angular-redux/store': 'npm:@angular-redux/store@6.5.7',
'angular-in-memory-web-api': 'npm:angular-in-memory-web-api/bundles/in-memory-web-api.umd.js',
'ts': 'npm:plugin-typescript@5.2.7/lib/plugin.js',
'typescript': 'npm:typescript@2.3.2/lib/typescript.js',
// for some of the examples
'lodash':'npm:lodash@4.17.4/lodash.js',
'redux': 'npm:redux@3.7.2',
// our app is within the app folder, appLocation comes from index.html
'app': appLocation + 'app'
}, systemJsMap),
// packages tells the System loader how to load when no filename and/or no extension
packages: {
"app": {
main: './main.ts',
defaultExtension: 'ts',
meta: {
'./*.ts': {
loader: boilerplatePath + 'systemjs-angular-loader.js'
}
}
},
rxjs: {
defaultExtension: false
},
'@angular-redux/store': {
main: 'lib/src/index.js',
defaultExtension: 'js'
},
redux: {
main: 'dist/redux.min.js',
defaultExtension: 'js'
}
}
});
})(this);
export const seriesNames = {
ABC: [
'The Sopranos',
'The Twilight Zone',
'All in the Family',
'M*A*S*H',
'The Mary Tyler Moore Show',
'Mad Men',
'Cheers',
'The Wire',
'The West Wing',
'The Simpsons',
'I Love Lucy',
'Breaking Bad',
'The Dick Van Dyke Show',
'Hill Street Blues',
'Arrested Development',
'The Daily Show With Jon Stewart',
'Six Feet Under',
'Taxi',
'The Larry Sanders Show',
'30 Rock',
'Friday Night Lights',
'Frasier',
'Friends',
'Saturday Night Live'
],
CBS: [
'The X-Files',
'Lost',
'ER',
'The Cosby Show',
'Curb Your Enthusiasm',
'The Honeymooners',
'Deadwood',
'Star Trek',
'Modern Family',
'Twin Peaks',
'NYPD Blue',
'The Carol Burnett Show',
'Battlestar Galactica',
'Sex and the City',
'Game of Thrones',
'The Bob Newhart Show',
'Your Show of Shows',
'Downton Abbey',
'Thirtysomething',
'Law & Order',
'Homicide: Life on the Street',
'St. Elsewhere',
'Homeland',
'Buffy the Vampire Slayer',
'The Colbert Report',
'The Office (UK)',
'The Good Wife',
'Northern Exposure',
'The Wonder Years',
'L.A. Law',
'Sesame Street',
'Columbo',
'Fawlty Towers'
],
NBC: [
'The Rockford Files',
'Moonlighting',
'Freaks and Geeks',
'Roots',
'South Park',
'Everybody Loves Raymond',
'Playhouse 90',
'Dexter',
'The Office (U.S.)',
'My So-called Life',
'The Golden Girls',
'The Andy Griffith Show ',
'24',
'The Shield',
'Roseanne',
'Murphy brown',
'House',
'I, Claudius',
'Barney Miller',
'The Odd Couple',
'Star Trek: The Next Generation',
'Alfred Hitchcock Presents',
'Upstairs, Downstairs',
'Monty Python\'s Flying Circus',
'Get Smart',
'The Defenders',
'Gunsmoke',
'Justified',
'Sgt. Bilko (The Phil Silvers Show)',
'Band of Brothers',
'Rowan & Martin\'s Laugh-in',
'The Prisoner',
'The Muppet Show',
'Absolutely Fabulous (U.K.)',
'Boardwalk Empire',
'Will & Grace',
'Family Ties',
'Lonesome Dove',
'Soap',
'The Fugitive',
'Late Night with David Letterman',
'Louie'
],
FOX: [
'Oz'
]
};
import {seriesNames} from './series-names';
export const columnDefs = [
{ headerName: '', children: [
{ headerName: '', children: [
{ headerName: 'Network Group', field: 'network_name', rowGroup: true, hide: true },
{ headerName: 'Series Group', field: 'series_name', rowGroup: true, hide: true },
{ headerName: 'Net/Series/Telecast', field: 'series_name', showRowGroup: true, cellRenderer: 'group' },
{ headerName: 'Day', field: 'day_of_week', width: 50 },
{ headerName: 'Air Date', field: 'day', width: 70 },
{ headerName: 'National Time', field: 'distinct_start_times', width: 100 },
{ headerName: 'Run Time', field: 'distinct_duration_minutes', width: 100 }
] }
] },
{ headerName: 'HH Live', children: [
{ headerName: '', children: [
{ headerName: 'Rtg', field: 'rating_live' },
{ headerName: 'Sh', field: 'share_live' },
{ headerName: 'AA', field: 'avg_aud_live' }
] }
] }
];
const daysOfWeek = ['M', 'T', 'W', 'R', 'F', 'S', 'U'];
export const getDatasets = () => {
const networkNames = Object.keys(seriesNames);
const network = networkNames.map((networkName) => {
return {
network_name: networkName,
rating_live: randomNumber(0.1, 0.9),
share_live: randomNumber(0.1, 99.9),
avg_aud_live: randomInteger(10000, 3000000)
};
});
const series = networkNames.reduce((seriesAccum, networkName) => {
const seriesForNetwork = seriesNames[networkName].map((seriesName) => {
return {
network_name: networkName,
series_name: seriesName,
day_of_week: randomItem(daysOfWeek),
distinct_start_times: randomTime(),
distinct_duration_minutes: randomItem([30, 60]),
rating_live: randomNumber(0.1, 0.9),
share_live: randomNumber(0.1, 99.9),
avg_aud_live: randomInteger(300, 300000)
};
});
return {
...seriesAccum,
[networkName]: seriesForNetwork
};
}, {});
const telecast = networkNames.reduce((telecastAccum, networkName) => {
const seriesTelecastsForNetwork = seriesNames[networkName].reduce((networkTelecastAccum, seriesName) => {
const numTelecasts = randomInteger(1, 24);
const telecasts: any[] = [];
while (telecasts.length < numTelecasts) {
telecasts.push({
network_name: networkName,
series_name: seriesName,
day_of_week: randomItem(daysOfWeek),
day: randomDate(),
distinct_start_times: randomTime(),
distinct_duration_minutes: randomItem([30, 60]),
rating_live: randomNumber(0.1, 0.9),
share_live: randomNumber(0.1, 99.9),
avg_aud_live: randomInteger(600, 30000)
});
}
return {
...networkTelecastAccum,
[seriesName]: telecasts
};
}, {});
return {
...telecastAccum,
...seriesTelecastsForNetwork
};
}, {});
return {
network,
series,
telecast
};
}
function randomItem(items) {
return items[Math.floor(Math.random()*items.length)];
}
function randomNumber(min, max) {
return Math.random() * (max - min) + min;
}
function randomInteger(min, max) {
return Math.floor(Math.random() * (max - min) + min);
}
function randomDate() {
const month = randomInteger(1, 12);
const day = randomInteger(1, 29);
return `${month}/${day}/2012`;
}
function randomTime() {
const hour = randomInteger(1, 12);
const min = randomItem(['00', '30']);
return `${hour}:${min}`;
}
import {Injectable} from '@angular/core';
import 'rxjs/add/operator/switchMap';
import {NgRedux} from '@angular-redux/store';
import {IEnterpriseDatasource, IEnterpriseGetRowsParams} from './enterprise-datasource';
import {AppState} from './app-state';
import {MessagesActions} from './messages-actions';
import {ReportService} from './report.service';
@Injectable()
export class DatasourceService implements IEnterpriseDatasource {
constructor(
private ngRedux: NgRedux<AppState>,
private messages: MessagesActions,
private reportService: ReportService
) {}
public getRows(params: IEnterpriseGetRowsParams): void {
this.messages.addMessage('datasource request ' + JSON.stringify(params.request, null, 4));
this.ngRedux.select((state: AppState) => state.currentReport).switchMap((currentReport) => {
this.messages.addMessage(`currentReport ${currentReport}`);
let level: string;
switch (params.request.groupKeys.length) {
case 0:
level = 'network';
break;
case 1:
level = 'series';
break;
case 2:
level = 'telecast';
break;
}
const key = params.request.groupKeys[params.request.groupKeys.length-1];
const startRow = params.request.startRow;
const endRow = params.request.endRow;
return this.reportService.get({ level, key, startRow, endRow });
}).subscribe((report) => {
this.messages.addMessage(`datasource response { rows: ${report.data.length}, lastRow: ${report.rowCount} }`)
params.successCallback(report.data, report.rowCount);
});
}
}
export const messagesReducer = (state: string[] = [], action: any): string[] => {
if (action.type === 'ADD_MESSAGE') {
return [
...state,
action.payload
];
}
return state;
};
// datasource for enterprise row model
export interface IEnterpriseDatasource {
// just one method, to get the rows
getRows(params: IEnterpriseGetRowsParams): void;
}
export interface IEnterpriseGetRowsRequest {
startRow: number;
endRow: number;
// row group columns
rowGroupCols: ColumnVO[];
// value columns
valueCols: ColumnVO[];
// pivot columns
pivotCols: ColumnVO[];
// true if pivot mode is one, otherwise false
pivotMode: boolean;
// what groups the user is viewing
groupKeys: string[];
// if filtering, what the filter model is
filterModel: any;
// if sorting, what the sort model is
sortModel: any;
}
// we pass a VO (Value Object) of the column and not the column itself,
// so the data can be converted to a JSON string and passed to server side
export interface ColumnVO {
id: string;
displayName: string;
field: string;
aggFunc: string;
}
export interface IEnterpriseGetRowsParams {
// details for the request
request: IEnterpriseGetRowsRequest;
// success callback, pass the rows back the grid asked for.
// if the total row count is known, provide it via lastRow, so the
// grid can adjust the scrollbar accordingly.
successCallback(rowsThisPage: any[], lastRow: number): void;
// fail callback, tell the grid the call failed so it can adjust its state
failCallback(): void;
}
import {Injectable} from '@angular/core';
import {NgRedux} from '@angular-redux/store';
import {AppState} from './app-state';
@Injectable()
export class MessagesActions {
constructor(
private ngRedux: NgRedux<AppState>
) {}
public addMessage(message: string) {
this.ngRedux.dispatch({ type: 'ADD_MESSAGE', payload: message });
}
}
export type AppState = {
currentReport?: number;
messages?: string[];
};
export const currentReportReducer = (state: number = null, action: any): string[] => {
if (action.type === 'SET_CURRENT_REPORT') {
return action.payload;
}
return state;
};
import {Injectable} from '@angular/core';
import {NgRedux} from '@angular-redux/store';
import {AppState} from './app-state';
@Injectable()
export class CurrentReportActions {
constructor(
private ngRedux: NgRedux<AppState>
) {}
public set(report: number) {
this.ngRedux.dispatch({ type: 'SET_CURRENT_REPORT', payload: report });
}
}
import {Injectable} from '@angular/core';
import {Observable} from 'rxjs/Observable';
import 'rxjs/add/observable/of';
import 'rxjs/add/operator/delay';
import {getDatasets} from './dataset';
import {Report} from './report';
type ReportParams = {
level: string;
key: string;
startRow: number;
endRow: number;
};
type Datasets = {
network: any[];
series: { [net: string]: any[] };
telecast: { [series: string]: any[] };
};
@Injectable()
export class ReportService {
private datasets: Datasets = {
network: [],
series: {},
telecast: {}
};
constructor() {
this.datasets = getDatasets();
}
public get(params: ReportParams): Observable<Report> {
let rows: any[];
if (params.level === 'network') {
rows = this.datasets.network;
} else {
rows = this.datasets[params.level][params.key];
}
return Observable.of({
data: rows.slice(params.startRow, params.endRow),
rowCount: rows.length
}).delay(2000);
}
}
export type Report = {
data: any[];
rowCount: number;
};