在Angular 2,4,5,6

时间:2017-01-03 07:09:47

标签: angular plugins angular-cli angular-universal

更新5/24/2018:我们现在从原始帖子中获得了+3版本的Angular,但仍然没有最终可行的解决方案。 Lars Meijdam(@LarsMeijdam)提出了一个有趣的方法,当然值得一看。 (由于专有问题,他不得不暂时删除他最初发布样本的GitHub存储库。但是,如果您需要副本,可以直接给他发消息。请参阅下面的评论以获取更多信息。)

Angular 6中最近的架构变化确实使我们更接近解决方案。另外,Angular Elements(https://angular.io/guide/elements)提供了一些组件功能 - 虽然不是我最初在这篇文章中描述的内容。

如果来自令人惊奇的Angular团队的任何人碰巧遇到过这个问题,请注意,似乎还有很多其他人也对此功能非常感兴趣。积压可能值得考虑。

我想在Angular 2Angular 4Angular 5Angular 6应用程序中实现可插入(插件)框架。

(我开发这个可插拔框架的具体用例是我需要开发一个微型内容管理系统。由于这里不一定详述的一些原因,Angular 2/4/5/6几乎完全适合大多数需求那个系统。)

通过可插拔框架(或插件架构),我特别指的是一个允许第三方开发人员通过使用可插拔组件创建或扩展主应用程序功能而无需直接访问或知识的系统主要应用程序的源代码或内部工作

(如果没有直接访问或了解应用程序的源代码或内部工作原理"那就是" ,这是一个核心目标。)

可插入框架的示例包括常见的内容管理系统,如WordPressDrupal

理想情况(与Drupal一样)是简单地将这些可插入组件(或插件)放入文件夹,让应用程序自动检测或发现它们,并让它们神奇地#34 ;工作&#34。以某种热插拔方式发生这种情况,这意味着在应用程序运行时,这将是最佳的。

我目前正在尝试确定以下五个问题的答案(在您的帮助下)。

  1. 实用性Angular 2/4/5/6应用程序的插件框架是否实用? (到目前为止,我还没有找到任何用Angular2/4/5/6创建真正可插拔框架的实用方法。)
  2. 预期的挑战:在为Angular 2/4/5/6应用程序实施插件框架时可能会遇到哪些挑战?
  3. 实施策略:可以采用哪些具体技术或策略来实施Angular 2/4/5/6应用程序的插件框架?
  4. 最佳做法:为Angular 2/4/5/6应用程序实施插件系统的最佳做法是什么?
  5. 替代技术 如果插件框架在Angular 2/4/5/6应用程序中实用,那么相对等效< / em>技术(例如React)可能适合现代高度反应性的Web应用程序
  6. 一般来说,使用Angular 2/4/5/6是非常理想的,因为:

    我非常希望将Angular 2/4/5/6用于我当前的项目。如果我能够使用Angular 2/4/5/6,我还将使用Angular-CLI,可能还会使用Angular Universal(用于服务器端呈现。)

    到目前为止,关于上述问题,我的想法如下。 请查看并提供您的反馈和启示。

    • Angular 2/4/5/6个应用程序使用包 - 但这不一定与允许应用程序中的插件相同。其他系统中的插件(例如Drupal)可以通过将插件文件夹放入公共模块目录中来实际添加,在该目录中它自动被拾取&#34;由系统。在Angular 2/4/5/6中,一个包(可能是插件)通常通过npm安装,添加到package.json,然后手动导入到应用中 - 如app.module中所示}。这比删除文件夹并让系统自动检测包的Drupal方法复杂得多。 安装插件越复杂,人们使用它们的可能性就越小。如果有Angular 2/4/5/6方法可以自动检测和安装插件,那会好得多。我非常有兴趣找到一种方法,允许非开发人员安装Angular 2/4/5/6应用程序并安装任何选定的插件,而无需了解所有应用程序的架构。

    • 通常,提供可插拔体系结构的好处之一是,第三方开发人员可以非常轻松地扩展系统的功能。显然,这些开发人员不熟悉他们所插入的应用程序的所有代码复杂性。一旦开发了插件,其他技术用户就可以简单地安装应用程序和任何选定的插件。但是,Angular 2/4/5/6相对复杂,学习曲线非常冗长。为了进一步复杂化,大多数生产Angular 2/4/5/6应用程序也使用Angular-CLIAngular UniversalWebPack。实现插件的人可能必须至少掌握一些基本知识,了解所有这些是如何组合在一起的 - 以及TypeScript的强大工作知识和对NodeJS的合理熟悉。知识要求是否如此极端以至于没有第三方想要开发插件?

    • 大多数插件可能都有一些服务器端组件(例如用于存储/检索插件相关数据)以及一些客户端输出。 Angular 2/4/5专门(强烈地)阻止开发人员在运行时注入自己的模板 - 因为这会带来严重的安全风险。为了处理插件可以容纳的许多类型的输出(例如图形的显示),似乎允许用户创建以一种形式注入到响应流中的内容可能是必要的。我想知道如何能够在没有象征性地粉碎Angular 2/4/5/6安全机制的情况下满足这种需求。

    • 大多数生产Angular 2/4/5/6应用程序都是使用Ahead of TimeAOT)编译预编译的。 (可能一切都应该是。)我不确定如何将插件添加到预编译的应用程序(或与之集成)。最好的方案是将插件与主应用程序分开编译。但是,我不确定如何使这项工作。回退可能是使用任何包含的插件重新编译整个应用程序,但这对于只想安装应用程序(在他自己的服务器上)以及任何选定插件的管理用户来说有点复杂。

    • Angular 2/4/5/6应用程序中,尤其是预编译的应用程序中,单个错误或冲突的代码可能会破坏整个应用程序。 Angular 2/4/5/6应用程序并不总是最容易调试。应用不良插件可能会导致非常不愉快的体验。我目前还没有意识到一种优雅地处理不良插件的机制。

12 个答案:

答案 0 :(得分:13)

我在github上创建了一个存储库,其中包含一个可能有用的解决方案。它使用Angular 6库和1个基本应用程序,它们懒洋洋地加载UMD捆绑库; https://github.com/lmeijdam/angular-umd-dynamic-example

如果您有任何建议,请随时添加!

答案 1 :(得分:11)

我刚刚为我的书“Developing with Angular”发布了一个新篇章,该书解决了Angular 2+中插件的主题,对于那些试图构建外部插件的人来说应该是非常感兴趣的。

关键点:

  • 插件
  • 根据字符串名称构建组件
  • 从外部来源加载配置
  • 动态更改应用程序路径
  • 外部插件
  • 创建插件库
  • 将插件加载到应用程序中
  • 包含插件内容的动态路由

这本书是免费的,并且“付出你想要的”模式。 随意拿一份副本,希望有所帮助。

答案 2 :(得分:6)

带有工作插件系统的示例应用程序(感谢Gijs创建github repo!)https://github.com/PacktPublishing/Mastering-Angular-2-Components/tree/master/angular-2-components-chapter-10基于电子书Mastering Angular 2 Components

  • 插件架构以扩展核心应用程序组件
  • 文件插件系统(仅用于添加插件目录/文件而无需编辑任何核心配置文件或需要重新编译应用程序!)
  • 加载并动态使用插件
  • 构建一个基本的插件管理器,以便即时启动/停用插件

干杯, 尼古拉斯

答案 3 :(得分:5)

您正在寻找的是延迟模块加载。 这是一个例子: http://plnkr.co/edit/FDaiDvklexT68BTaNqvE?p=preview

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;
    }
  }
}

...最佳汤姆

答案 4 :(得分:4)

️Github演示angular-plugin-architecture

也许Ivy可以更改某些内容,但是暂时我使用的是使用Angular CLI Custom Builder并满足以下要求的解决方案:

  • AOT
  • 避免重复代码(@ angular / core {common,forms,router},rxjs,tslib这样的软件包)
  • 在所有插件中使用共享库,但请勿从每个插件中的共享库中生成工厂,而应重用库代码和工厂
  • 与Angular CLI相同的优化级别
  • 对于导入外部模块,我们只需要知道一件事:它们的捆绑文件路径
  • 我们的代码应识别模块并将插件放入页面
  • 支持服务器端渲染
  • 仅在需要时加载模块

用法很简单:

ng build --project plugins --prod --modulePath=./plugin1/plugin1.module#Plugin1Module 
         --pluginName=plugin1 --sharedLibs=shared --outputPath=./src/assets/plugins

有关我的文章的更多信息:

答案 5 :(得分:2)

我做了一个hack for load并在bootstrap时编译其他模块,但我还没有解决循环依赖的问题

 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));

然后在AppModule中添加:

{
        provide: APP_INITIALIZER,
        useFactory: AppsLoaderFactory,
        deps: [AppsLoader],
        multi: true
},

答案 6 :(得分:2)

我正在寻找角度为2/4的插件系统,以便为工作中的企业应用程序开发RAD环境。经过一些研究,我决定实现一个数据库存储集合(但可能在文件系统中)伪角度组件。

存储在数据库数据库中的组件基于ng-dynamic 主要组件实现类似于:

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();
                }

            });

        });

    }

外部javascript代码类似于角度组件:

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>

虽然它并不完美,但自定义组件的开发人员具有与angular2 / 4类似的框架。

答案 7 :(得分:2)

可以“手动”完成。由于webpack对外部(插件)模块一无所知,因此不能将它们包含在bundle中。所以我做的是查看webpack生成的代码,我在main.bundle.js中找到了这些代码:

var map = {
"./dashboard/dashboard.module": ["../../../../../src/app/dashboard/dashboard.module.ts","dashboard.module"]}; 

让我们检查一下该数组包含的内容:

  1. “./ dashboard / dashboard.module” - 这是我们想要延迟加载的模块的路由网址,例如: {path:'dashboard',loadChildren:'。 /dashboard/dashboard.module#DashboardModule'}
  2. “../../../../../ src / app / dashboard / dashboard.module.ts” - 这是入口点(contructor)取自
  3. “dashboard.module” - 没有chunk.js的实际文件名(例如: dashboard.module.chunk.js
  4. 所以从理论上讲,如果你在map属性中添加条目来配置路由并遵循模式,你就可以拥有一个插件系统。现在的挑战是如何在该地图属性中添加或删除条目。显然它不能用角度代码完成,它应该用于外部工具。

答案 8 :(得分:2)

我尝试使用ABP,Angular和ASP.NET Core实现插件架构:https://github.com/chanjunweimy/abp_plugin_with_ui

基本上,我使用不同的角度应用程序开发角度插件,然后我动态地将它们加在一起。

有关我如何实现它的更多信息:

我有2个angular-cli应用程序,1个是主角度cli应用程序,另一个是插件角度cli应用程序。我们在Angular-cli插件架构方法中遇到的问题是我们如何集成它们。

现在,我所做的是,我在两个应用程序上运行ng-build,并将它们放入“wwwroot”文件夹,然后托管在ASP.NET核心2.0服务器中。一个更简单的存储库显示了这个想法是Angular Multiple App:https://github.com/chanjunweimy/angular-multiple-app

abp_plugin_with_ui是一个存储库,用于开发包含后端和Angular cli的插件。对于后端,我使用了aspnetboilerplate框架,前端是使用多个angular-cli应用程序开发的。

要将主应用程序与插件应用程序集成,我们必须在两个应用程序上运行“ng-build”(注意我们也必须更改为插件应用程序的href),然后我们移动构建插件angular cli应用程序的内容,到主应用程序“wwwroot”文件夹。完成所有这些后,我们可以运行“dotnet run”来为ASP.NET Core 2.0 Web应用程序提供服务,以托管“ng build”生成的静态文件。

希望它有所帮助。欢迎任何评论! ^^

答案 9 :(得分:2)

有点偏离主题,但UI组件库可能对搜索插件的一些读者感兴趣:
https://medium.com/@nikolasleblanc/building-an-angular-4-component-library-with-the-angular-cli-and-ng-packagr-53b2ade0701e

NativeScript具有内置UI插件:
 https://docs.nativescript.org/plugins/building-plugins
这些插件需要Angular Wrapper:
 https://docs.nativescript.org/plugins/angular-plugin

答案 10 :(得分:2)

我目前正与您在同一个任务中,尝试制作Angular的可插入/可移植版本,这不是一个小问题。

我实际上找到了很好的解决方案,阅读了Genius Developing with Angular的书Denys Vuyika,他实际上在书上解释了一个很好的解决方案,他在书的第356页上谈到了外部插件然后使用Rollup.js来实现解决方案,然后他开始动态加载以前在您的应用程序外部构建的外部插件。

还有两个其他库/项目可帮助您实现此结果 ng-packagrNx extensions for Agnular (from Nrwl)我们想实现后者,我想说它并不像我们预期的那么顺利,没有为此而创建角度的,所以我们必须解决一些Angular和NX ppls是其中最好的之一。

我们只是在开源项目的开始,我们使用的是Django + Mongo + Angular,(我们称WebDjangular,而我们可能的解决方法之一就是Django必须编写每次安装和激活新的插件或主题时,一些JSON配置文件并构建应用程序。

我们已经完成的工作是,可以从数据库中为插件使用组件的标签,组件将显示在屏幕上!同样,该项目处于非常早期的阶段,我们将架构略微基于Wordpress,并且我们还需要做更多的测试来实现我们的梦想:D

我希望本书能够为您提供帮助,并且使用Rollup.js我知道您将能够解决这个不重要的问题。

答案 11 :(得分:0)

我从Paul Ionescu找到了一篇很好的文章,内容涉及如何在angular中构建可扩展的插件应用程序。

https://itnext.io/how-to-build-a-plugin-extensible-application-architecture-in-angular5-736890278f3f

他还在github上引用了一个示例应用程序: https://github.com/ionepaul/angular-plugin-architecture