Giter VIP home page Giter VIP logo

angular-plugin-architecture's Introduction

AngularPluginArchitecture

For Angular 11 see https://github.com/alexzuza/angular-plugin-architecture-with-module-federation

Example of building AOT compiled Angular 7 plugin that can be consumed on client and server sides(SSR)

For Angular 8 see cli8 branch

Setup

npm install

Building shared plugin

npm run build:shared

Building plugins

npm run build:plugin1
npm run build:plugin2

Run

Dev mode

npm start

Server-side

npm run build:ssr
npm run serve:ssr

License

MIT

angular-plugin-architecture's People

Contributors

alexzuza avatar dependabot[bot] avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

angular-plugin-architecture's Issues

License

Alex,

Any chance that you could provide a license for this? (BSD, MIT, etc?)

Cheers,

:w

Angular EntryComponent in shared library

Hi,

I've created shared library which contains entryComponent.
Unfortunatelly when I'll try to use it in dynamically loaded module then I'll receive following exception:

Uncaught (in promise): TypeError: Cannot read property 'componentType' of undefined

Did you see such error? I can mention that when I'm using my library in non-dynamic plugins then everything works fine.

How will this change with Ivy?

Firstly, thanks for creating and sharing this. I'm going to try using it, as I've been unsuccessful with all other approaches, usually due to lack of AoT support.

Do you have any thoughts or insights on how much the solution will have to change with Angular 9 once Ivy is the default?

Thanks again!

Advice or plugin recommendation

Hello guys,
I'm very interesting in your project.
In fact i'm building for my company a large application.
This app is based on asp core and angular 7.
But we want this app to be extensible on the backend part AND on the front (angular) part.
Because we will publish this app compiled and give it to our integrators that will want to do their own specific development around our app. I'm not sure if i'm understandable.
Ask me if not.

But anyway in our study case would you recommand we use your framework for the front end part ?
We are looking for the Extcore extension for the backend part, are you compatible with it ?
Also we will migrate to asp core 3.0 once it will be in release. Do you have plans for supporting it ?

In your example, would it be possible to build a plugin without rebuilding the core project ?

Like not generating it in src/assets but in anb already compiled folder ?
DO you have a documentation / documentation or details step for integrating your architecture in an already existing application ?
Do you suggest to rebuitl from scratch my app inside your project as a template or should i try to integrate your plugin build tasks inside my app ?

Thanks in advance,
Alexandre MOULAY

Error: Cannot find module when importing with SystemJs

Hi, thank you for this, it looks promising!

I'm trying to integrate this into my project, by following the repo code and article, but I'm getting an error when trying to import the module using systemjs

The plugin is there and accessible, I tried using the file path and also using http with no luck.

The error I'm getting:
ERROR Error: Uncaught (in promise): Error: Cannot find module 'http://localhost:4200/assets/plugins/plugin1.js'

Any clue about what can I be missing?

Thanks!

How can I use HttpClient in shared and plugin(s)

Hi,

I am trying to use the HttpClient from '@angular/common/http';. I want to use it in my Shared and in my Plugins. However I am not really figuring out what I am missing to make it work. I am getting this error.

image

What I currently have in my main app.

plugin-externals.ts

import * as core from '@angular/core';
import * as common from '@angular/common';
import * as forms from '@angular/forms';
import * as router from '@angular/router';
import * as HttpClient from '@angular/common/http';
import * as rxjs from 'rxjs';
import * as tslib from 'tslib';

// Setup to share libraries

export const PLUGIN_EXTERNALS_MAP = {
  'ng.core': core,
  'ng.common': common,
  'ng.forms': forms,
  'ng.router': router,
  'ng.HttpClient': HttpClient,
  rxjs,
  tslib
};

My shared.module.ts

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';


import { SharedComponent } from './shared.component';
import { TabsComponent } from './tabs/tabs.component';
import { TabComponent } from './tabs/tab.component';
import { ButtonComponent } from './button/button.component';
import { HttpClientModule } from '@angular/common/http';


const sharedComponents = [SharedComponent, ButtonComponent, TabComponent, TabsComponent];

@NgModule({
  imports: [CommonModule, HttpClientModule],
  declarations: [...sharedComponents],
  exports: [...sharedComponents]
})
export class SharedModule { }

This is my config.service (that I want to have in my shared) where I want to use the HttpClient to get a config file (same file that I use in my main app) where I save some API stuff.

import { Injectable } from '@angular/core';
import { Location } from '@angular/common';
import { HttpClient } from '@angular/common/http';
import { environment } from './enviroment';

@Injectable({
    providedIn: 'any'
})
export class ConfigService {

    private config: any;

    constructor(private location: Location, private http: HttpClient) {
    }

    public load() {
        return this.getConfig();
    }

    get apiUrl(): string {
        return this.config.apiUrl;
    }

    private async getConfig(): Promise<any> {
        if (!this.config) {
            this.config = (await this.http.get(this.location.prepareExternalUrl(environment.configfile)).toPromise());
        }
        return Promise.resolve(this.config);
    }
}

My public-api.ts

/*
 * Public API Surface of shared
 */

// NOTE: NG9 require to export components exported by a module, also named exports is preferred if you were able to preserve aource-map of a plugin (shared lib could retain source-map) ;)
export { ButtonComponent } from './lib/button/button.component';
export { TabComponent } from './lib/tabs/tab.component';
export { TabsComponent } from './lib/tabs/tabs.component';
export { SharedComponent } from './lib/shared.component';
export { SharedModule } from './lib/shared.module';
export { ConfigService } from './lib/service/config.service'

And finally I want to create services in my Plugins that use the config.service from the shared to get the API URL that I need and make API calls from that service. Currently this is my Plugin3Service. Currently not using the HttpClient, but first solving one problem at the time.

import { Injectable } from '@angular/core';
import { ConfigService } from 'shared'

@Injectable({
    providedIn: 'root'
})

export class Plugin3Service {

    constructor(private config: ConfigService) {
    }
    configSutff() {

        console.log(this.config.apiUrl);
    }
} 

TLDR;
How to add HttpClient in Shared and in Plugins?

*Edit: Added my public-api.ts

How to resolve plugin if it has sub route?

Hi, Thanks for this great work.
Right now PluginLoaderService needs "entry" to resolve the entryComponent but I come to a situation where a plugin has routes, in this case, I don't know how to identify entryComponent. Please help me to load the route enable module via PluginLoaderService.

Monaco Editor is not working

I have tried to import monaco-editor as a shared component.

When I try to load the monaco using the below code , require is not loaded as a global function and gives the error saying config of undefined error

const onGotAmdLoader = (e?) => {
      // Load monaco
      (window).require.config({ paths: { 'vs': `assets/monaco-editor/min/vs` } });
      (window).require(['vs/editor/editor.main'], () => {
        setTimeout(() => {
          (window).monaco = monaco;
        this.monacoLoaded = true;
        this.monacoLoadedSubject.next(true);
        });
      });
    };

    // Load AMD loader if necessary
    if (!(window).require) {
      const loaderScript = document.createElement('script');
      loaderScript.type = 'text/javascript';
      loaderScript.src = '/assets/monaco-editor/min/vs/loader.js';
      loaderScript.addEventListener('load', onGotAmdLoader);
      document.body.appendChild(loaderScript);
    } else {
      onGotAmdLoader();
    }

plugin in separate repository

Hello and thanks for your article and repo.

Is there a way to extend this architecture such that plugin code can exist in a different Angular library/repository, and build against the core app as an NPM dependency? Would this require that the core app be broken into separate projects, something like a library of injectable services and shared libraries that plugins can inject and use, as well as the main app entry point? The main app would then use the core library, just as the plugins would. A plugin's package.json file might then look something like

{
  ...,
  "dependencies": {
    "@myapp/core_services": "~1.0.0"
    ...
  }

Then NPM would enforce version compatibility between the plugins and the core app.

Shared dependencies

My shared library has a lot of dependencies so when it is build, all dependencies are included. OK.

But when i build my plugin that has only a dependency to my shared module, all subdependecies of shared are also included in my plugin. So my plugin reach more or less the same size of shared.

How avoid this?

Sharing service between App and plugins

Your work is amazing. (I've already search for such plugin system without finding a really good architecture.)

I'm relatively new to angular, and I don't master enough depecndencies injection with your example.

I just would like to go further than sharing components with plugins. What I want is to share a service (so my plugins could interact with app; it two ways : having methods to call app features; or being notified from app).

Here are headline of what I would like to do :

The serviceInterface :

export interface ISharedService{
    //the Obersable/event the plugin can register/subscribe to
    notificationEvent: Observable<string>;

   //a method call by plugins and executed on app
    doSometing(): void;
}

A plugin example:


  constructor(private sharedService: ISharedService) {
    this.sharedService.notificationEvent.subscribe( message => {
      
    });
  }

The service must be implemented on app side

Could it be realizable by the plugin architecture ?
I've tried without success, but my skills are limited on such project (I've tested with the branch share-lib-between-app-and-plugins)

Loading assets of plugin components dynamically

Hi,
How can we load assets (image, i18n json files) used in plugin components? While loading them dynamically from client app, it searches in assets folder of client app.
One thing that can be done is to take asset path as input in plugin component and pass that value at the time of loading but this will be overhead for plugin developer to add extra input and bind it.

Is there any other way to change path of assets while component is loaded dynamically?

Any help will be appreciated.
Thanks

Build plugin in watch mode

Hi! First of all, thanks for your brilliant research and solution!
My question is about the "dev" (debug) mode. How do you guys do it?
Are you rebuilding the plugin with AOT every time you change something in the code?
I'd like to at least have the plugins to rebuild on code change automatically, but when I add --watch to build:plugin1, it goes into forever loop...
Any ideas on how to fix that?
Thank you! ๐Ÿ™

Unable to build after npm update

[4/4] Building fresh packages...
success Saved lockfile.
$ tsc -p builders/tsconfig.builders.json
builders/plugin-builder/index.ts:2:3 - error TS2305: Module '"../../node_modules/@an
gular-devkit/build-angular/src"' has no exported member 'BrowserBuilder'.

2   BrowserBuilder,
    ~~~~~~~~~~~~~~

builders/plugin-builder/index.ts:3:3 - error TS2305: Module '"../../node_modules/@angular-devkit/build-angular/src"' has no exported member 'NormalizedBrowserBuilderSchema'.

3   NormalizedBrowserBuilderSchema
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

builders/plugin-builder/index.ts:124:27 - error TS2339: Property 'deleteOutputPath' does not exist on type 'PluginBuilderSchema'.

124     builderConfig.options.deleteOutputPath = false;
                              ~~~~~~~~~~~~~~~~

node_modules/@angular-devkit/build-angular/node_modules/@angular-devkit/core/src/experimental/workspace/workspace.d.ts:48:9 - error TS1086: An accessor cannot be declared in an ambient context.

48     get root(): Path;
           ~~~~

node_modules/@angular-devkit/build-angular/node_modules/@angular-devkit/core/src/experimental/workspace/workspace.d.ts:49:9 - error TS1086: An accessor cannot be declared in an ambient context.

49     get host(): virtualFs.Host<{}>;
           ~~~~

node_modules/@angular-devkit/build-angular/node_modules/@angular-devkit/core/src/experimental/workspace/workspace.d.ts:50:9 - error TS1086: An accessor cannot be declared in an ambient context.

50     get version(): number;
           ~~~~~~~

node_modules/@angular-devkit/build-angular/node_modules/@angular-devkit/core/src/experimental/workspace/workspace.d.ts:51:9 - error TS1086: An accessor cannot be declared in an ambient context.

51     get newProjectRoot(): string | undefined;
           ~~~~~~~~~~~~~~

node_modules/@angular-devkit/build-angular/node_modules/@angular-devkit/core/src/logger/logger.d.ts:36:19 - error TS1086: An accessor cannot be declared in an ambient context.

36     protected get _observable(): Observable<LogEntry>;
                     ~~~~~~~~~~~

node_modules/@angular-devkit/build-angular/node_modules/@angular-devkit/core/src/logger/logger.d.ts:37:19 - error TS1086: An accessor cannot be declared in an ambient context.

37     protected set _observable(v: Observable<LogEntry>);
                     ~~~~~~~~~~~

node_modules/@angular-devkit/build-angular/node_modules/@angular-devkit/core/src/utils/partially-ordered-set.d.ts:20:9 - error TS1086: An accessor cannot be declared in an ambient context.

20     get size(): number;
           ~~~~

node_modules/@angular-devkit/build-angular/node_modules/@angular-devkit/core/src/utils/partially-ordered-set.d.ts:36:26 - error TS2315: Type 'Generator' is not generic.

36     [Symbol.iterator](): Generator<T, void, unknown>;
                            ~~~~~~~~~~~~~~~~~~~~~~~~~~~

node_modules/@angular-devkit/build-angular/node_modules/@angular-devkit/core/src/utils/partially-ordered-set.d.ts:37:9 - error TS1086: An accessor cannot be declared in an ambient context.

37     get [Symbol.toStringTag](): 'Set';
           ~~~~~~~~~~~~~~~~~~~~

node_modules/@angular-devkit/build-angular/node_modules/@angular-devkit/core/src/utils/priority-queue.d.ts:17:9 - error TS1086: An accessor cannot be declared in an ambient context.

17     get size(): number;
           ~~~~

node_modules/@angular-devkit/build-angular/node_modules/@angular-devkit/core/src/virtual-fs/host/alias.d.ts:59:9 - error TS1086: An accessor cannot be declared in an ambient context.

59     get aliases(): Map<Path, Path>;
           ~~~~~~~

node_modules/@angular-devkit/build-angular/node_modules/@angular-devkit/core/src/virtual-fs/host/memory.d.ts:42:9 - error TS1086: An accessor cannot be declared in an ambient context.

42     get capabilities(): HostCapabilities;
           ~~~~~~~~~~~~

node_modules/@angular-devkit/build-angular/node_modules/@angular-devkit/core/src/virtual-fs/host/record.d.ts:50:9 - error TS1086: An accessor cannot be declared in an ambient context.

50     get backend(): ReadonlyHost;
           ~~~~~~~

node_modules/@angular-devkit/build-angular/node_modules/@angular-devkit/core/src/virtual-fs/host/record.d.ts:51:9 - error TS1086: An accessor cannot be declared in an ambient context.

51     get capabilities(): HostCapabilities;
           ~~~~~~~~~~~~

node_modules/@angular-devkit/build-angular/node_modules/@angular-devkit/core/src/virtual-fs/host/resolver.d.ts:19:9 - error TS1086: An accessor cannot be declared in an ambient context.

19     get capabilities(): HostCapabilities;
           ~~~~~~~~~~~~

node_modules/@angular-devkit/build-angular/node_modules/@angular-devkit/core/src/virtual-fs/host/safe.d.ts:18:9 - error TS1086: An accessor cannot be declared in an ambient context.

18     get capabilities(): HostCapabilities;
           ~~~~~~~~~~~~

node_modules/@angular-devkit/build-angular/node_modules/@angular-devkit/core/src/virtual-fs/host/sync.d.ts:22:9 - error TS1086: An accessor cannot be declared in an ambient context.

22     get capabilities(): HostCapabilities;
           ~~~~~~~~~~~~

node_modules/@angular-devkit/build-angular/node_modules/@angular-devkit/core/src/virtual-fs/host/sync.d.ts:23:9 - error TS1086: An accessor cannot be declared in an ambient context.

23     get delegate(): Host<T>;
           ~~~~~~~~

node_modules/@angular-devkit/build-angular/node_modules/@angular-devkit/core/src/virtual-fs/host/test.d.ts:28:13 - error TS1086: An accessor cannot be declared in an ambient context.

28         get records(): TestLogRecord[];
               ~~~~~~~

node_modules/@angular-devkit/build-angular/node_modules/@angular-devkit/core/src/virtual-fs/host/test.d.ts:30:13 - error TS1086: An accessor cannot be declared in an ambient context.

30         get files(): Path[];
               ~~~~~

node_modules/@angular-devkit/build-angular/node_modules/@angular-devkit/core/src/virtual-fs/host/test.d.ts:31:13 - error TS1086: An accessor cannot be declared in an ambient context.

31         get sync(): SyncDelegateHost<{}>;
               ~~~~

node_modules/@angular-devkit/build-angular/node_modules/@angular-devkit/core/src/workspace/definitions.d.ts:35:9 - error TS1086: An accessor cannot be declared in an ambient context.

35     get size(): number;
           ~~~~

node_modules/@angular-devkit/build-angular/src/karma/index.d.ts:13:49 - error TS2307: Cannot find module 'karma'.

13 export declare type KarmaConfigOptions = import('karma').ConfigOptions & {
                                                   ~~~~~~~


Found 26 errors.

error Command failed with exit code 2.
info Visit https://yarnpkg.com/en/docs/cli/install for documentation about this command.

ng build --watch keep rebuilding

Coud someone explain me why I can't build a plugin in --watch mode

when i run: ng build --project plugins --aot --modulePath=./plugin1/plugin1.module#Plugin1Module --pluginName=plugin1 --sharedLibs=shared --outputPath=./src/assets/plugins --watch

without making any changes the plugin keep rebuilding every second :
Screen Shot 2021-03-20 at 12 28 25

Shared InjectionToken in a shared singleton service issue

Using share-lib-between-app-and-plugins branch, when defining an InjectionToken that injected into a shared service and provided in the AppModule's NgModule.providers is not injected into the service called from within the plugins throwing

ERROR Error: Uncaught (in promise): Error: StaticInjectorError(AppModule)[InjectionToken test]:
...
NullInjectorError: No provider for InjectionToken test!

shared:

export const APP_TEST = new InjectionToken<TestValue>("test");
@Injectable({
  providedIn: 'root'
})
export class TestService
{
  constructor(@Inject(APP_TEST) private tv: TestValue...
...
  test() { return this.tv; }
}

AppModule:

providers: [
...
{provide: TestValue, useValue: ...}]
...

AppComponent:

export class AppComponent implements OnInit {
  constructor(
    private injector: Injector,
    private pluginLoader: PluginLoaderService,
    private ts: TestService) {}
  ngOnInit() {
    console.log(this.ts.test()); // <-- Works as expected
...

Plugin 1:

export class Plugin1Component implements OnInit {
  x = false;
  constructor(private ts: TestService) {}
  ngOnInit() {
    console.log(this.ts.test());  // <-- Throws
...

It looks like plugin's injector is trying to create a new instance of the service and when trying to resolve the dependency for the injected token it thinks that it's another token, not the one that was provided in the AppModule... Any ideas how this can be resolved will be very welcomed.

Can I use Router to loading plugins?

Thanks for putting this demo app. I was looking into other example for loading plugins dynamically and had the same issue with buildOptimzer not working. Stumbled across this at the right moment.

Could you comment on how to make router work with this example. Can I wire RouterModule like in https://github.com/lmeijdam/angular-umd-dynamic-example demo with this?

Self contained plug-in included externals

Thank you for a great example. Just wondering whethet we are able to include such external dependencies to a plugin bundle.

That would be great in order to make plugin app responsible for its dependencies (UI frameworks as well). Right now plugins seem are strongly dependent from the host app infrastructure (@angular version, etc.)

I've tried a trick with the bundledDependencies instead of the peer ones but it seems a wrong way since bundledDependencies got deprecated. BTW it didn't work for me.

Can't get ngx-bootstrap to work in plugin

Using the Angular 8 cli branch.

I'm trying to get anything from ngx-bootstrap to simply work in a plugin, even if the plugin imports the entire library.

I was able to get @angular/material working just fine.

Placing an entry linke 'ngx-bootstrap' in externals doesn't do the trick. I get:

ERROR Error: Uncaught (in promise): NullInjectorError: StaticInjectorError(AppModule)[t -> function(){}]:
StaticInjectorError(Platform: core)[t -> function(){}]:
NullInjectorError: No provider for function(){}!
NullInjectorError: StaticInjectorError(AppModule)[t -> function(){}]:
StaticInjectorError(Platform: core)[t -> function(){}]:
NullInjectorError: No provider for function(){}!
at NullInjector.push../node_modules/@angular/core/fesm5/core.js.NullInjector.get

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.