jaredmcateer / ngVue3

Use Vue 3 components in your Angular 1.x app
https://jaredmcateer.github.io/ngVue3/
MIT License
20 stars 2 forks source link

Using Plugins With Options as a Second Arg #41

Open Aston13 opened 11 months ago

Aston13 commented 11 months ago

Hi,

Firstly, I would just like to say that I am very appreciative of this library, it has enabled me to upgrade very smoothly from Vue 2 to Vue 3.

I have found a issue when trying to register a plugin that has a second options argument.

Vue's app.use function (interface App<HostElement = any>) has some overloads for passing options.

use<Options extends unknown[]>(plugin: Plugin<Options>, ...options: Options): this;
use<Options>(plugin: Plugin<Options>, options: Options): this;

However, I can't see how to do this in ngVue3 as there is only 1 argument for the use function. For example with PrimeVue:

$ngVueProvider.use(PrimeVue, { unstyled: true }); // Doesn't work
jaredmcateer commented 11 months ago

I'll look into adding this as it's definitely a common enough use case. However, you should be able to work around this in the meantime by creating your own custom ngVue plugins (oof looking at the docs I clearly need to update them).

import angular from "angular";
import { useNgVue, useNgVuePlugins, NgVueProvider } from "@jaredmcateer/ngvue3";
import { App } from "vue";
import PrimeVue from 'primevue/config';

angular.module("myModule", [useNgVue(), useNgVuePlugin()])
  // ...
  .config(($ngVueProvider: NgVueProvider) => {
    $ngVueProvider.installNgVuePlugin(() => ({
      $name: "ngVuePrimeVue",
      $plugin: (_$injector: ng.auto.IInjectorService, app: App<any>) => {
        app.use(PrimeVue, { unstyled: true });
      }
    }));
  });
jaredmcateer commented 11 months ago

Just for additional reference since the docs are inaccurate right now.

export type PluginHook = ($injector: ng.auto.IInjectorService, app: App<Element>) => void;

export interface NgVuePlugin {
  /** Plugin name */
  $name: string;
  /** Plugin install function */
  $plugin: PluginHook;
  /** 
   * Methods/properties to expose on the angular side as part
   * of the ngVueProvider.plugins.<$name>.<$config.key> 
   */
  $config: Record<string, unknown>;
}

/**
 * Installs an ngVue plugin, this gives access to configuration via the
 * ngVueProvider and gives the Vue plugin install method access to the angular
 * injector.
 *
 * @param plugin a function that returns a ngVue plugin config
 * @member NgVueProvider
 */
installNgVuePlugin(plugin: () => NgVuePlugin) {
  const { $name, $config, $plugin } = plugin();
  this.pluginHooks.push($plugin);
  this.pluginConfig[$name] = $config;
}
Aston13 commented 10 months ago

Just wanted to say thanks for the fast response! I went for this in the end with your help.

...
import NgVueModules, { type NgVuePlugin } from './NgVueModules';
import PrimeVue, { PrimeVueConfiguration } from 'primevue/config';

const primeVueConfig: PrimeVueConfiguration = { unstyled: false, pt: Bootstrap_PT, ripple: true };

const ngVueInjectables: NgVuePlugin[] = [
    { injectable: i18n },
    { injectable: HighchartsVue },
    { injectable: PrimeVue, context: { name: "ngVuePrimeVue", config: primeVueConfig }}
];
NgVueModules.create(angular, ngVueInjectables).registerComponents()
...

...
export type NgVuePlugin = {
    injectable: any;
    context?: {
        name: string;
        config: any;
    }
}

export default class NgVueModules {
    angularInstance: IAngularStatic;
    vuePlugins?: NgVuePlugin[];

    private constructor(angularInstance: IAngularStatic, vuePlugins?: NgVuePlugin[]) {
        this.angularInstance = angularInstance;
        this.vuePlugins = vuePlugins;
    }

    static create(angularInstance: IAngularStatic, vuePlugins?: NgVuePlugin[]): NgVueModules {
        return new NgVueModules(angularInstance, vuePlugins);
    }

    registerComponents() {
        this.angularInstance // Angular module that uses the ngVue3 lib to enable the usage of Vue 3 components in Angular 1.x.
        .module('vue.components', [
            useNgVue(),
            useNgVuePlugins()
        ])
        .config(['$ngVueProvider', ($ngVueProvider: NgVueProvider) => { // Syntax supports minimisation of $ngVueProvider
            if(this.vuePlugins){

                // Pass through for native Vue Plugins to the app instance.
                this.vuePlugins.forEach(vuePlugin => {
                    if(vuePlugin.context){
                        $ngVueProvider.installNgVuePlugin(() => ({
                            $name: vuePlugin.context.name,
                            $plugin: (_$injector/*: ng.auto.IInjectorService */, app: App<any>) => {
                              app.use(vuePlugin.injectable, vuePlugin.context.config);
                            },
                            $config: {}
                        }))
                    } else {
                        $ngVueProvider.use(vuePlugin.injectable);
                    }
                })
                $ngVueProvider.component('valueType', <unknown>ValueType as Component)
            }
        }])
...
rbanks54 commented 10 months ago

Leaving this here to potentially help others:

I had some "fun" getting Pinia working as a plugin.

Setting it up with the ngvue3 example apps was a breeze. Call app.use(createPinia()) as the $plugin function, and use I can use a store as per usual. i.e. const.store = useXyzStore(); in the vue component.

However, in my large angularjs app, this didn't work. The vue component couldn't find the root store (no active pinia) so it threw an exception (see this code here)

I haven't been able to track down the root cause, but I suspect either the injection context is being rewritten or the Symbol used with the provide/inject approach is no longer aligned.

My work around (for now) is as follows.

//in the angularjs app
.config(($ngVueProvider) => {
    $ngVueProvider.installNgVuePlugin(() => ({
        $name: "pinia",
        $plugin: (_$injector, app) => {
            const pinia = createPinia();
            app.use(pinia);
            //add the rootStore to the injection context.
            app.provide('piniaInstance', pinia);
        }
    }))
})

and when a vue component needs to use a store, inject the rootStore instance.

import { inject } from 'vue';
const store = useXyzStore(inject('piniaInstance'));
jaredmcateer commented 9 months ago

This seem eerily similar to a problem people are also having on the other version of ngVue, https://github.com/ngVue/ngVue/issues/158, and $store being missing. It would be super helpful if we could get a minimal reproducible example.

sirSayed98 commented 2 months ago

you can check this solution