mirror of
https://github.com/Redocly/redoc.git
synced 2025-02-07 13:30:33 +03:00
Dynamic component instantiator from html
This commit is contained in:
parent
c39166266c
commit
f786955442
88
lib/services/component-parser.service.ts
Normal file
88
lib/services/component-parser.service.ts
Normal file
|
@ -0,0 +1,88 @@
|
|||
'use strict';
|
||||
|
||||
import {
|
||||
Injectable,
|
||||
Renderer,
|
||||
ComponentRef,
|
||||
Type,
|
||||
Injector,
|
||||
Inject,
|
||||
ComponentFactoryResolver
|
||||
} from '@angular/core';
|
||||
|
||||
type NodesOrComponents = HTMLElement | ComponentRef<any>;
|
||||
export const COMPONENT_PARSER_ALLOWED = 'COMPONENT_PARSER_ALLOWED';
|
||||
|
||||
const COMPONENT_REGEXP = '^\\s*<!-- ReDoc-Inject:\\s+?{component}\\s+?-->\\s*$';
|
||||
|
||||
@Injectable()
|
||||
export class ComponentParser {
|
||||
private renderer: Renderer;
|
||||
private allowedComponents: any;
|
||||
|
||||
static contains(content: string, componentSelector: string) {
|
||||
let regexp = new RegExp(COMPONENT_REGEXP.replace('{component}', `<${componentSelector}.*>`), 'mi');
|
||||
return regexp.test(content);
|
||||
}
|
||||
|
||||
static build(componentSelector) {
|
||||
return `<!-- ReDoc-Inject: <${componentSelector}> -->`;
|
||||
}
|
||||
|
||||
constructor(
|
||||
private resolver: ComponentFactoryResolver,
|
||||
@Inject(COMPONENT_PARSER_ALLOWED) allowedComponents
|
||||
) {
|
||||
this.allowedComponents = allowedComponents;
|
||||
}
|
||||
|
||||
setRenderer(_renderer: Renderer) {
|
||||
this.renderer = _renderer;
|
||||
}
|
||||
|
||||
splitIntoNodesOrComponents(content: string, injector: Injector):NodesOrComponents[] {
|
||||
let componentDefs = [];
|
||||
let match;
|
||||
let anyCompRegexp = new RegExp(COMPONENT_REGEXP.replace('{component}', '(.*?)'), 'gmi');
|
||||
while (match = anyCompRegexp.exec(content)) {
|
||||
componentDefs.push(match[1]);
|
||||
}
|
||||
|
||||
let splitCompRegexp = new RegExp(COMPONENT_REGEXP.replace('{component}', '.*?'), 'mi');
|
||||
let htmlParts = content.split(splitCompRegexp);
|
||||
let res = [];
|
||||
for (let i = 0; i < htmlParts.length; i++) {
|
||||
let node = this.renderer.createElement(null, 'div');
|
||||
this.renderer.setElementProperty(node, 'innerHTML', htmlParts[i]);
|
||||
if (htmlParts[i]) res.push(node);
|
||||
if (componentDefs[i]) {
|
||||
let componentRef = this.createComponentByHtml(componentDefs[i], injector);
|
||||
res.push(componentRef);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
createComponentByHtml(htmlTag: string, injector:Injector):ComponentRef<any>| null {
|
||||
let {componentType, options} = this._parseHtml(htmlTag);
|
||||
if (!componentType) return null;
|
||||
|
||||
let factory = this.resolver.resolveComponentFactory(componentType);
|
||||
return factory.create(injector);
|
||||
}
|
||||
|
||||
private _parseHtml(htmlTag: string):{componentType: Type<any> | null, options: any} {
|
||||
// TODO: for now only primitive parsing by tagname
|
||||
let match = /<([\w_-]+).*?>/.exec(htmlTag);
|
||||
if (match.length <= 1) return { componentType: null, options: null };
|
||||
let componentName = match[1];
|
||||
|
||||
let componentType = this.allowedComponents[componentName];
|
||||
// TODO parse options
|
||||
let options = {};
|
||||
return {
|
||||
componentType,
|
||||
options
|
||||
};
|
||||
}
|
||||
}
|
39
lib/services/content-projector.service.ts
Normal file
39
lib/services/content-projector.service.ts
Normal file
|
@ -0,0 +1,39 @@
|
|||
'use strict';
|
||||
|
||||
import {
|
||||
Injectable,
|
||||
ComponentFactory,
|
||||
ComponentRef,
|
||||
ViewContainerRef
|
||||
} from '@angular/core';
|
||||
|
||||
@Injectable()
|
||||
export class ContentProjector {
|
||||
instantiateAndProject<T>(componentFactory: ComponentFactory<T>,
|
||||
parentView:ViewContainerRef, projectedNodesOrComponents: any[]):ComponentRef<T> {
|
||||
let contextInjector = parentView.parentInjector;
|
||||
|
||||
let projectedNodes = [];
|
||||
let componentRefs:ComponentRef<any>[] = [];
|
||||
|
||||
for (let i=0; i < projectedNodesOrComponents.length; i++) {
|
||||
let nodeOrCompRef = projectedNodesOrComponents[i];
|
||||
if (nodeOrCompRef instanceof ComponentRef) {
|
||||
projectedNodes.push(nodeOrCompRef.location.nativeElement);
|
||||
componentRefs.push(nodeOrCompRef);
|
||||
} else {
|
||||
projectedNodes.push(nodeOrCompRef);
|
||||
}
|
||||
}
|
||||
|
||||
let parentCompRef = parentView.createComponent(componentFactory, null, contextInjector, [projectedNodes]);
|
||||
let appElement = (<any>parentView)._element;
|
||||
appElement.nestedViews = appElement.nestedViews || [];
|
||||
for (let i=0; i < componentRefs.length; i++) {
|
||||
let compRef = componentRefs[i];
|
||||
appElement.nestedViews.push((<any>compRef.hostView).internalView);
|
||||
(<any>compRef.hostView).internalView.addToContentChildren(appElement);
|
||||
}
|
||||
return parentCompRef;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
'use strict';
|
||||
|
||||
import {
|
||||
Component,
|
||||
EventEmitter,
|
||||
Output,
|
||||
Input,
|
||||
OnInit,
|
||||
ViewContainerRef,
|
||||
ComponentFactoryResolver,
|
||||
Renderer
|
||||
} from '@angular/core';
|
||||
|
||||
import {
|
||||
ComponentParser,
|
||||
ContentProjector
|
||||
} from '../../../services/';
|
||||
|
||||
@Component({
|
||||
selector: 'dynamic-ng2-viewer',
|
||||
template: ''
|
||||
})
|
||||
export class DynamicNg2Viewer implements OnInit {
|
||||
@Input() html: string;
|
||||
|
||||
constructor(
|
||||
private view: ViewContainerRef,
|
||||
private projector: ContentProjector,
|
||||
private parser: ComponentParser,
|
||||
private resolver: ComponentFactoryResolver,
|
||||
private renderer: Renderer) {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.parser.setRenderer(this.renderer);
|
||||
let nodesOrComponents = this.parser.splitIntoNodesOrComponents(this.html, this.view.injector);
|
||||
let wrapperFactory = this.resolver.resolveComponentFactory(DynamicNg2Wrapper);
|
||||
let ref = this.projector.instantiateAndProject(wrapperFactory, this.view, nodesOrComponents);
|
||||
ref.changeDetectorRef.markForCheck();
|
||||
}
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'dynamic-ng2-wrapper',
|
||||
template: '<ng-content></ng-content>'
|
||||
})
|
||||
export class DynamicNg2Wrapper {}
|
|
@ -5,9 +5,10 @@ import { Tabs, Tab } from './Tabs/tabs';
|
|||
import { Zippy } from './Zippy/zippy';
|
||||
import { CopyButton } from './CopyButton/copy-button.directive';
|
||||
import { SelectOnClick } from './SelectOnClick/select-on-click.directive';
|
||||
import { DynamicNg2Viewer, DynamicNg2Wrapper } from './DynamicNg2Viewer/dynamic-ng2-viewer.component';
|
||||
|
||||
export const REDOC_COMMON_DIRECTIVES = [
|
||||
DropDown, StickySidebar, Tabs, Tab, Zippy, CopyButton, SelectOnClick
|
||||
DropDown, StickySidebar, Tabs, Tab, Zippy, CopyButton, SelectOnClick, DynamicNg2Viewer, DynamicNg2Wrapper
|
||||
];
|
||||
|
||||
export { DropDown, StickySidebar, Tabs, Tab, Zippy, CopyButton, SelectOnClick }
|
||||
export { DropDown, StickySidebar, Tabs, Tab, Zippy, CopyButton, SelectOnClick, DynamicNg2Viewer, DynamicNg2Wrapper }
|
||||
|
|
Loading…
Reference in New Issue
Block a user