Last active
December 4, 2019 05:27
-
-
Save zackschuster/48d77efafd77ae4cfb705bda3e44f756 to your computer and use it in GitHub Desktop.
angular-8-thin-shim-layer
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<html> | |
<head> | |
<title>Angular App</title> | |
</head> | |
<body> | |
<main> | |
<ng-content ng-app>loading</ng-content> | |
</main> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'zone.js'; | |
import { ApplicationRef, Compiler, Component, ComponentFactory, Injector, NgModule, NgZone, ViewContainerRef } from '@angular/core'; | |
import { CommonModule } from '@angular/common'; | |
import { FormsModule } from '@angular/forms'; | |
import { BrowserModule } from '@angular/platform-browser'; | |
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; | |
declare module '@angular/core' { | |
interface ViewContainerRef { | |
/** @private */ | |
_embeddedViews: { nodes: { renderElement: Element }[] }[] | |
} | |
} | |
// for those who must afflict themselves with the burden of the `@` sigil, a less wearisome path prevails; a warning, though, and beware, for the fruits will yet taste bitter all the same | |
@Function() // silence me if you wish to know my power | |
class NgAppDirective { | |
constructor(vcr: ViewContainerRef) {} | |
} | |
export class NgAppComponents { | |
protected readonly $componentFactories: ComponentFactory<any>[] = []; | |
/** | |
* Returns a shallow copy of the current `ViewContainerRef` embedded views array. | |
*/ | |
protected get $viewStack() { | |
return this.$vcr._embeddedViews.slice(); | |
} | |
constructor(protected $compiler: Compiler, protected $vcr: ViewContainerRef) { } | |
public add(...declarations: any[]) { | |
this.$componentFactories.push( | |
...this.$compiler.compileModuleAndAllComponentsSync( | |
NgModule({ declarations, imports: [CommonModule, FormsModule] })(class ThrowawayModule {}) | |
).componentFactories | |
); | |
return this; | |
} | |
public view(selector: string) { | |
const component = this.$componentFactories.find(x => x.selector === selector); | |
return component == null ? this : this.attach(component); | |
} | |
/** | |
* Remove the first instance of a component on the view stack. | |
* | |
* Does nothing if no component is found. | |
*/ | |
public removeFirst(selector: string) { | |
const index = this.$viewStack.findIndex(x => { | |
return x.nodes | |
.filter(y => y.renderElement != null) | |
.some(y => y.renderElement.localName === selector); | |
}); | |
return index === -1 ? this : this.remove(index); | |
} | |
/** | |
* Remove the last instance of a component on the view stack. | |
* | |
* Does nothing if no component is found. | |
*/ | |
public removeLast(selector: string) { | |
const component = this.$viewStack.reverse().find(x => { | |
return x.nodes | |
.filter(y => y.renderElement != null) | |
.some(y => y.renderElement.localName === selector); | |
}); | |
if (component == null) { | |
return this; | |
} | |
const index = this.$viewStack.findIndex(x => x === component); | |
return index === -1 ? this : this.remove(index); | |
} | |
public attach<T>(component: ComponentFactory<T>) { | |
return NgApp.Zone.run(() => { | |
this.$vcr.createComponent(component); | |
return this; | |
}); | |
} | |
/** | |
* Remove the component at the given zero-based index on the view stack. | |
* | |
* An out-of-bounds index will remove the component at the nearest view stack boundary. | |
* | |
* @example `-1` or `0` will remove the first component | |
* @example `2` will remove the third component | |
* @example `Infinity` or `$viewStack.length` or `$viewStack.length - 1` will remove the last component | |
*/ | |
public remove(index: number) { | |
return NgApp.Zone.run(() => { | |
this.$vcr.remove(index); | |
return this; | |
}); | |
} | |
public removeAll() { | |
this.$vcr.clear(); | |
return this; | |
} | |
} | |
export class NgApp { | |
public static readonly Zone = new NgZone({ enableLongStackTrace: false }); | |
public static bootstrap() { | |
const declarations = [NgAppDirective]; | |
const entryComponents = [NgAppDirective]; | |
NgModule({ imports: [BrowserModule], declarations, entryComponents })(NgApp); | |
Component({ selector: '[ng-app]', template: '' })(NgAppDirective); | |
return platformBrowserDynamic().bootstrapModule(NgApp, { ngZone: NgApp.Zone }); | |
} | |
public components: NgAppComponents; | |
public get injector() { | |
return this.$injector; | |
} | |
protected $injector: Injector; | |
public async ngDoBootstrap(app: ApplicationRef) { | |
try { | |
this.$injector = app.bootstrap(NgAppDirective).injector; | |
this.components = new NgAppComponents(this.$injector.get(Compiler), this.$injector.get(ViewContainerRef)); | |
} catch (err) { | |
if (err.message.includes('The selector "[ng-app]" did not match any elements') ) { | |
throw new Error('Must mark the root element with the attribute [ng-app]'); | |
} | |
throw err; | |
} | |
} | |
} | |
NgApp.bootstrap() | |
.then(ref => { | |
if (window['ngRef']) { | |
console.info('hot reload detected'); | |
window['ngRef'].destroy(); | |
} | |
window['ngRef'] = ref; | |
function $compile(controller: { new(...args: any[]): any }, template: string) { | |
Reflect.defineProperty(controller.prototype, '$selector', { get() { return selector; }}); | |
Reflect.defineProperty(controller.prototype, '$template', { get() { return template; }}); | |
const selector = controller.name.split(/(?=[A-Z])/g).join('-').replace(/_/g, '-').toLowerCase(); | |
template = template | |
.replace(/\$ctrl\./g, '') | |
.replace(/ng-if/g, '*ngIf') | |
.replace(/ng-model/g, '[(ngModel)]'); | |
Component({ selector, template })(controller); | |
return controller; | |
} | |
// behold the ashen rot ascend! let it glisten in thine eye as snow in a desert, yet curdle in thy belly as milk among maggots! | |
@Function() | |
class DynamicCmp { | |
constructor(vcr: ViewContainerRef) { | |
console.log({ | |
LookDynamicCmpHasVcr: vcr != null, | |
ButAtWhatCost: 'https://www.youtube.com/watch?v=KFZ3Qhn5dW8' | |
}); | |
} | |
} | |
class DynamicCmp2 { | |
private thing: string; | |
ngOnInit() { | |
this.thing = 'initial-thing'; | |
setTimeout(() => { | |
this.thing = 'second-thing'; | |
}); | |
} | |
} | |
ref | |
.instance | |
.components | |
.add( | |
$compile(DynamicCmp, '<div ng-if="true">I am {{$ctrl.$selector}}. I include dynamic-cmp2. <dynamic-cmp2></dynamic-cmp2></div>'), | |
$compile(DynamicCmp2, '<div ng-if="true">I am {{$ctrl.$selector}}. <input ng-model="$ctrl.thing" /> <span ng-if="$ctrl.thing">{{$ctrl.thing}}</span></div>'), | |
) | |
.view('dynamic-cmp') | |
.view('class-1') // does nothing | |
.removeFirst('dynamic-cmp2') // does nothing | |
.add( | |
$compile(class {}, '<div ng-if="true">I am the first anonymous component, {{$ctrl.$selector}}.</div>'), | |
$compile(class {}, '<div ng-if="true">I am the second anonymous component, {{$ctrl.$selector}}.</div>'), | |
$compile(class {}, '<div ng-if="false">I am {{$ctrl.$selector}}. I should instantiate, but not be visible.</div>'), | |
) | |
.view('class-1') | |
.view('class-2') | |
.view('class-3') | |
.view('class-1') | |
.view('class-2') | |
.removeLast('class-2') | |
.removeFirst('class-1') | |
.view('dynamic-cmp2') | |
// .removeFirst('dynamic-cmp2') | |
// .removeAll() | |
.view('class-3'); | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"name": "angular", | |
"version": "0.0.0", | |
"private": true, | |
"dependencies": { | |
"@angular/common": "8.2.14", | |
"@angular/compiler": "8.2.14", | |
"@angular/core": "8.2.14", | |
"@angular/forms": "8.2.14", | |
"@angular/platform-browser": "8.2.14", | |
"@angular/platform-browser-dynamic": "8.2.14", | |
"rxjs": "6.4.0", | |
"zone.js": "0.10.2" | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment