├── .gitignore ├── LICENSE ├── README.md ├── index.html ├── package.json ├── src ├── custom_browser_platform.ts └── main.ts ├── tsconfig.json └── webpack.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | /dist/ 3 | .idea -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2010-2016 Google, Inc. http://angularjs.org 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Experiments with Angular Renderers: Example App 2 | 3 | ## How to Run 4 | ``` 5 | clone this repo 6 | npm install 7 | npm run start 8 | open localhost:8080 9 | ``` -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Document 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "CustomPlatform", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "webpack-dev-server", 9 | "build": "webpack -p" 10 | }, 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "@angular/common": "2.4.0", 15 | "@angular/compiler": "2.4.0", 16 | "@angular/core": "2.4.0", 17 | "@angular/platform-browser": "2.4.0", 18 | "core-js": "^2.4.1", 19 | "rxjs": "5.0.0-rc.1", 20 | "ts-loader": "^1.3.3", 21 | "zone.js": "^0.6.26" 22 | }, 23 | "devDependencies": { 24 | "clang-format": "^1.0.32", 25 | "typescript": "2.0.7", 26 | "webpack": "2.1.0-beta.25", 27 | "webpack-dev-server": "2.1.0-beta.10", 28 | "@angular/platform-browser-dynamic": "2.4.0", 29 | "@angular/platform-server": "2.4.0", 30 | "@angular/compiler-cli": "2.4.0" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/custom_browser_platform.ts: -------------------------------------------------------------------------------- 1 | import {Renderer, RootRenderer, RenderComponentType, NgModule, APP_INITIALIZER, NgZone} from "@angular/core"; 2 | import {BrowserModule} from "@angular/platform-browser"; 3 | 4 | 5 | // We render everything into a forest of Nodes. 6 | // View, Element, and Text are different types of nodes. 7 | class View { 8 | children: Node[] = []; 9 | } 10 | 11 | class Element { 12 | constructor(public name: string) {} 13 | attributes: {[n:string]:string} = {}; 14 | properties: {[n:string]:any} = {}; 15 | children: Node[] = []; 16 | view: View = null; 17 | } 18 | 19 | class Text { 20 | constructor(public value: string) {} 21 | } 22 | 23 | type Node = Element | View | Text; 24 | 25 | 26 | // We define a custom in-memory renderer. 27 | // To make thing simpler we only support a subset of all the operations. 28 | // We support element creation, text creation, and setting attributes and properties. 29 | class InMemoryRootRenderer implements RootRenderer { 30 | public roots: any[] = []; 31 | 32 | renderComponent(componentProto: RenderComponentType): Renderer { 33 | return new InMemoryRenderer(this.roots); 34 | } 35 | } 36 | 37 | class InMemoryRenderer implements Renderer { 38 | constructor(private roots: any[]) {} 39 | 40 | selectRootElement(selectorOrNode: string|any, debugInfo?: any): Element { 41 | const root = new Element(selectorOrNode); 42 | this.roots.push(root); 43 | return root; 44 | } 45 | 46 | createElement(parentElement: any, name: string, debugInfo?: any): Element { 47 | const element = new Element(name); 48 | parentElement.children.push(element); 49 | return element; 50 | } 51 | 52 | createViewRoot(hostElement: Element): View { 53 | const view = new View(); 54 | hostElement.view = view; 55 | return view; 56 | } 57 | 58 | createText(parentElement: Element, value: string, debugInfo?: any): Text { 59 | const text = new Text(value); 60 | parentElement.children.push(text); 61 | return text; 62 | } 63 | 64 | setElementProperty(renderElement: Element, propertyName: string, propertyValue: any): void { 65 | renderElement.properties[propertyName] = propertyValue; 66 | } 67 | 68 | setElementAttribute(renderElement: Element, attributeName: string, attributeValue: string): void { 69 | renderElement.attributes[attributeName] = attributeValue; 70 | } 71 | 72 | setText(renderNode: Text, text: string): void { 73 | renderNode.value = text; 74 | } 75 | 76 | setBindingDebugInfo(renderElement: any, propertyName: string, propertyValue: string): void { 77 | } 78 | 79 | createTemplateAnchor(parentElement: any): any { 80 | throw new Error("not implemented"); 81 | } 82 | 83 | projectNodes(parentElement: any, nodes: any[]): void { 84 | throw new Error("not implemented"); 85 | } 86 | 87 | attachViewAfter(node: any, viewRootNodes: any[]): void { 88 | throw new Error("not implemented"); 89 | } 90 | 91 | detachView(viewRootNodes: any[]): void { 92 | throw new Error("not implemented"); 93 | } 94 | 95 | destroyView(hostElement: any, viewAllNodes: any[]): void { 96 | throw new Error("not implemented"); 97 | } 98 | 99 | listen(renderElement: any, name: string, callback: Function): Function { 100 | throw new Error("not implemented"); 101 | } 102 | 103 | listenGlobal(target: string, name: string, callback: Function): Function { 104 | throw new Error("not implemented"); 105 | } 106 | 107 | setElementClass(renderElement: any, className: string, isAdd: boolean): void { 108 | throw new Error("not implemented"); 109 | } 110 | 111 | setElementStyle(renderElement: any, styleName: string, styleValue: string): void { 112 | throw new Error("not implemented"); 113 | } 114 | 115 | invokeElementMethod(renderElement: any, methodName: string, args?: any[]): void { 116 | throw new Error("not implemented"); 117 | } 118 | 119 | animate(element: any, startingStyles: any, keyframes: any[], duration: number, delay: number, easing: string): any { 120 | throw new Error("not implemented"); 121 | } 122 | } 123 | 124 | // We print a new snapshot every time the zone gets stable. 125 | // That would be the moment when the browse would modify the DOM. 126 | function setUpRenderFlushing(zone: NgZone, renderer: InMemoryRootRenderer) { 127 | return () => { 128 | zone.onStable.subscribe(() => { 129 | console.group("--"); 130 | console.log(renderer.roots); 131 | console.log(JSON.stringify(renderer.roots, null, 2)); 132 | console.groupEnd(); 133 | }); 134 | }; 135 | } 136 | 137 | // Instead of defining the whole platform from scratch, we essentially extend 138 | // the browser platform (hence the import and the export). 139 | @NgModule({ 140 | imports: [BrowserModule], 141 | exports: [BrowserModule], 142 | providers: [ 143 | {provide: RootRenderer, useClass: InMemoryRootRenderer}, 144 | {provide: APP_INITIALIZER, multi: true, useFactory: setUpRenderFlushing, deps: [NgZone, RootRenderer]} 145 | ] 146 | }) 147 | export class CustomBrowserPlatform {} -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | // import polyfills 2 | import 'core-js/es7/reflect' 3 | import 'zone.js/dist/zone' 4 | 5 | // import Angular 6 | import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; 7 | import {NgModule, Component, Renderer, ElementRef} from "@angular/core"; 8 | import {CustomBrowserPlatform} from "./custom_browser_platform"; 9 | 10 | @Component({ 11 | selector: 'child', 12 | template: ` 13 | ChildCmp 14 | ` 15 | }) 16 | class ChildCmp { 17 | constructor(renderer: Renderer, ref: ElementRef) { 18 | renderer.setElementProperty(ref.nativeElement, "someProp", 123); 19 | } 20 | } 21 | 22 | @Component({ 23 | selector: 'app', 24 | template: ` 25 | AppCmp [ 26 | 27 | ] 28 | ` 29 | }) 30 | class AppCmp {} 31 | 32 | @NgModule({ 33 | imports: [CustomBrowserPlatform], 34 | declarations: [ChildCmp, AppCmp], 35 | bootstrap: [AppCmp] 36 | }) 37 | class AppModule {} 38 | 39 | platformBrowserDynamic().bootstrapModule(AppModule); -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "es2015", 4 | "moduleResolution": "node", 5 | "target": "es5", 6 | "noImplicitAny": false, 7 | "sourceMap": true, 8 | "mapRoot": "", 9 | "emitDecoratorMetadata": true, 10 | "experimentalDecorators": true, 11 | "lib": [ 12 | "es2015", 13 | "dom" 14 | ], 15 | "outDir": "lib", 16 | "skipLibCheck": true, 17 | "rootDir": "." 18 | } 19 | } -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | resolve: { 5 | extensions: ['.ts', '.js'], 6 | }, 7 | entry: './src/main.ts', 8 | output: { 9 | path: path.join(process.cwd(), 'dist'), 10 | publicPath: 'dist/', 11 | filename: "bundle.js" 12 | }, 13 | module: { 14 | loaders: [ 15 | { 16 | test: /\.ts$/, 17 | loader: 'ts-loader' 18 | } 19 | ] 20 | }, 21 | 22 | devServer: { 23 | historyApiFallback: true 24 | } 25 | }; 26 | --------------------------------------------------------------------------------