在Angular 2,4,5,6中实现插件架构/插件系统/可插拔框架
2018年5月24日更新:我们现在是我最初发布的+3版Angular,仍然没有最终可行的解决方案。Lars Meijdam(@LarsMeijdam)提出了一个有趣的方法,当然值得一看。(由于专有问题,他不得不临时删除他最初发布样本的GitHub存储库。但是,如果您想要副本,您可以直接给他发消息。有关更多信息,请参阅下面的评论。) Angular 6最近的架构变化确实让我们更接近解决方案。此外,Angular Elements()提供了一些组件功能——虽然与我在本文中最初描述的不完全相同 如果来自惊人的Angular团队的任何人碰巧遇到这个问题,请注意,似乎还有许多其他人也对这个功能非常感兴趣。对于积压工作来说,这很值得考虑在Angular 2,4,5,6中实现插件架构/插件系统/可插拔框架,angular,plugins,angular-cli,angular-universal,Angular,Plugins,Angular Cli,Angular Universal,2018年5月24日更新:我们现在是我最初发布的+3版Angular,仍然没有最终可行的解决方案。Lars Meijdam(@LarsMeijdam)提出了一个有趣的方法,当然值得一看。(由于专有问题,他不得不临时删除他最初发布样本的GitHub存储库。但是,如果您想要副本,您可以直接给他发消息。有关更多信息,请参阅下面的评论。) Angular 6最近的架构变化确实让我们更接近解决方案。此外,Angular Elements()提供了一些组件功能——虽然与我在本文中最初描述的不完全相同 如果来
我想在
angular2
、angular4
、angular5
或angular6
应用程序中实现一个可插入(插件)框架
(我开发这个可插拔框架的具体用例是,我需要开发一个微型内容管理系统。由于这里不必详细阐述的一些原因,Angular 2/4/5/6
几乎完全适合该系统的大多数需求。)
我所说的可插拔框架(或插件体系结构)是指允许第三方开发人员通过使用可插拔组件来创建或扩展主应用程序功能的系统,而无需直接访问或了解主应用程序的源代码或内部工作
(关于“不直接访问或不了解应用程序的源代码或内部工作”的措辞是一个核心目标。)
可插入框架的示例包括常见的内容管理系统,如WordPress
或Drupal
理想的情况(与Drupal一样)是能够简单地将这些可插入组件(或插件)放入文件夹中,让应用程序自动检测或发现它们,并让它们神奇地“工作”。以某种热插拔方式(即在应用程序运行时)实现这一点是最佳的
我目前正试图(在您的帮助下)确定以下五个问题的答案
Angular 2/4/5/6
应用程序的插件框架是否实用?(到目前为止,我还没有找到任何实用的方法来使用Angular2/4/5/6
创建一个真正的可插入框架)Angular 2/4/5/6
应用程序实现插件框架时可能会遇到哪些挑战Angular 2/4/5/6
应用程序实现插件框架可以采用哪些具体技术或策略Angular 2/4/5/6
应用程序实现插件系统的最佳实践是什么Angular 2/4/5/6
应用程序中不实用,那么哪些相对等效的技术(例如React
)可能适用于现代高反应性Web应用程序角度2/4/5/6
是非常可取的,因为:
- 它自然是非常快的——非常快
- 它消耗很少的带宽(初始加载后)
- 它的占地面积相对较小(在
和AOT
树抖动之后),并且占地面积继续缩小
- 它功能强大,团队和社区的生态系统正在持续快速增长
- 它与许多最好和最新的Web技术(如
和TypeScript
Observables
- Angular 5现在支持服务人员()
- 由于有
谷歌的支持,它很可能在未来得到支持和增强
Angular 2/4/5/6
。如果我能够使用Angular 2/4/5/6
,我还将使用Angular CLI
,可能还会使用Angular Universal
(用于服务器端渲染)
以下是我对上述问题的看法请回顾并提供您的反馈和启示。
应用程序使用软件包——但这与允许在应用程序中使用插件不一定相同。其他系统(例如,Angular 2/4/5/6
)中的插件基本上可以通过将插件文件夹放到公共模块目录中来添加,系统会在其中自动“拾取”插件。在Drupal
中,软件包(作为插件)通常通过Angular 2/4/5/6
安装,添加到npm
,然后手动导入到应用程序中,如package.json
中所示。这比Drupal拖放文件夹并让系统自动检测包的方法要复杂得多。安装插件越复杂,人们使用插件的可能性就越小。如果有一种方法让Angular 2/4/5/6自动检测和安装插件会更好。我非常感兴趣的是找到一种方法,允许非开发人员安装app.module
应用程序并安装任何选择的插件,而无需了解应用程序的所有架构Angular 2/4/5/6
- 一般来说,提供可插拔体系结构的好处之一是,对于第三方来说非常容易
import {Component} from '@angular/core'; import {Router} from '@angular/router'; @Component({ selector: 'my-app', template: ` <a [routerLink]="['/']">Home</a> | <a [routerLink]="['/app/home']">App Home</a> | <a [routerLink]="['/app/lazy']">App Lazy</a> <hr> <button (click)="addRoutes()">Add Routes</button> <hr> <router-outlet></router-outlet> ` }) export class App { loaded: boolean = false; constructor(private router: Router) {} addRoutes() { let routerConfig = this.router.config; if (!this.loaded) { routerConfig[1].children.push({ path: `lazy`, loadChildren: 'app/lazy.module#LazyModule' }); this.router.resetConfig(routerConfig); this.loaded = true; } } }
const moduleFile: any = require(`./${app}/${app}.module`), module = moduleFile[Object.keys(moduleFile)[0]]; route.children.push({ path: app, loadChildren: (): Promise<any> => module }); promises.push(this.compiler.compileModuleAndAllComponentsAsync(module));
{ provide: APP_INITIALIZER, useFactory: AppsLoaderFactory, deps: [AppsLoader], multi: true },
declare var ctx: any; @Component({ selector: 'my-template', template: ` <div> <div *dynamicComponent="template; context: { ctx: ctx };"></div> </div> `, providers: [EmitterService], }) export class MyTemplateComponent implements OnMount, AfterViewInit, OnChanges { // name private _name: string; get name(): string { return this._name; } @Input() set name(name: string) { this._name = name; this.initTemplate(); } template: string; ctx: any = null; private initTemplate() { this.templateSvc.getTemplate(this.name).subscribe(res => { // Load external JS with ctx implementation let promise1 = injectScript(res.pathJs); // Load external CCS let promise2 = injectScript(res.pathCss); Promise.all([promise1, promise2]).then(() => { // assign external component code this.ctx = ctx; // // sets the template this.template = res.template; this.injectServices(); if (this.ctx && this.ctx.onInit) { this.ctx.onInit(); } }); }); }
var ctx = { // injected _httpService: {}, _emitterService: null, // properies model: { "title": "hello world!", }, // events onInit() { console.log('onInit'); }, onDestroy() { console.log('onDestroy'); }, onChanges(changes) { console.log('changes', changes); }, customFunction1() { console.log('customFunction1'); }, childTemplateName: string = 'other-component'; };
<a (click)="customFunction1()">{{ ctx.model.title }}</a> <input [(ngModel)]="ctx.model.title" type="text" />
<a (click)="customFunction1()">{{ ctx.model.title }}</a> <my-template [name]="childTemplateName"></my-template>
var map = { "./dashboard/dashboard.module": ["../../../../../src/app/dashboard/dashboard.module.ts","dashboard.module"]};