Skip to content

Commit

Permalink
Merge pull request #37 from jaredmcateer/support-global-components
Browse files Browse the repository at this point in the history
  • Loading branch information
jaredmcateer authored Jun 13, 2023
2 parents eea62c7 + 0a5f468 commit f2e8d2e
Show file tree
Hide file tree
Showing 9 changed files with 119 additions and 15 deletions.
5 changes: 5 additions & 0 deletions .changeset/famous-kids-mate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@jaredmcateer/ngvue3": minor
---

Adds support for global components
31 changes: 31 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: Tests
on:
push:
branches:
- "*"
- "*/*"
- "**"
- "!main"
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout 🛎️
uses: actions/checkout@v3

- name: Setup pnpm 💫
uses: pnpm/action-setup@v2
with:
version: 7.0.0

- name: Use Node.js 16.xa 💻
uses: actions/setup-node@v3
with:
version: 16
cache: pnpm

- name: Install Dependencies 🗂️
run: pnpm install --frozen-lockfile

- name: Test 🔬️
run: pnpm test
6 changes: 3 additions & 3 deletions packages/ngVue3/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ ngVue 3 is a fork of the [ngVue](https://github.com/ngVue/ngVue) project which i
- [Notes](#notes)
- [`v-on-*`](#v-on-)
- [Handling HTML Attributes](#handling-html-attributes)
- [Plugins, Injectables and Directives](#plugins-injectables-and-directives)
- [Plugins, Injectables, Directives and Global Components](#plugins-injectables-directives-and-global-components)
- [Directives usage](#directives-usage)
- [TODO](#todo)

Expand Down Expand Up @@ -254,11 +254,11 @@ Keep in mind that hwne you pass down literal strings for anything other than `cl
</template>
```

## Plugins, Injectables and Directives
## Plugins, Injectables, Directives and Global Components

Due to the architectural changes introduced by Vue 3 in most cases if you need access to lifecycle hooks you can simply create a composable and use it directly in your vue components. However, there are instances you need to install plugins (e.g., Pinia/Vuex, VueRouter, etc), directives or you want access to shared logic in your angular app. Plugins in ngVue 3 have been revamped to be simpler, access to "root" props is no longer possible, however you can now pass through Plugins, Injectables and Directive easily as well as still create your own custom ngVue Plugin.

[Plugin, Injectables and Directives Documentation](./docs/plugins.md)
[Plugin, Injectables, Directives and Global Components Documentation](./docs/plugins.md)

### Directives usage

Expand Down
10 changes: 9 additions & 1 deletion packages/ngVue3/docs/plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,15 @@ angular.module("mainApp", [useNgVue(), useNgVuePlugins()]);

`useNgVuePlugins` creates an Angular service `$ngVue`. This service implements a custom plugins system and a means to pass through Injectables and Plugins to the Vue app instance.

### Provide/Use/Directives
### Provide/Use/Directives/Global Components

Sometimes you simply need to add a plugin, injectable or directive to the app instance, you don't have any specific need for angular but due to ngVue's architecture the app instance isn't readily available, `$ngVueProvider` has pass through function to help you with that.

_Example using TypeScript_

```ts
import { type NgVueProvider } from "@jaredmcateer/ngvue3";
import MyGlobalComponent from "./components/MyGlobalComponent.vue";
const fooKey: InjectionKey<"bar"> = new Symbol();

angular
Expand All @@ -41,12 +42,16 @@ angular
el.focus();
},
});

// Register global component
$ngVueProvider.component("myGlobalComponent", MyGlobalComponent);
});
```

<details><summary><strong>Equivalent using JavaScript</strong></summary>

```javascript
import MyGlobalComponent from "./components/MyGlobalComponent.vue";
export const fooKey = Symbol();

angular.module("mainApp", [useNgVue(), useNgVuePlugins()]).config(($ngVueProvider) => {
Expand All @@ -60,6 +65,9 @@ angular.module("mainApp", [useNgVue(), useNgVuePlugins()]).config(($ngVueProvide
el.focus();
},
});

// Register global component
$ngVueProvider.component("myGlobalComponent", MyGlobalComponent);
});
```

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<script setup lang="ts">
import { ref } from "vue";
const name = ref("Jane");
</script>

<template>
<my-global-component :name="name"></my-global-component>
</template>
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<script setup lang="ts">
defineProps<{ name: string }>();
</script>

<template>
<div class="my-global-component">Hello {{ name }}!</div>
</template>
30 changes: 30 additions & 0 deletions packages/ngVue3/lib/angular/__tests__/ngVueProvider.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { NgVueProvider, useNgVuePlugins } from "../ngVueProvider";
import { CustomNgVuePluginConfig, useCustomNgVuePlugin } from "./__fixtures__/NgVuePlugin";
import Button from "./__fixtures__/Button.vue";
import { MyService, MyServiceKey } from "./__fixtures__/MyService";
import GlobalComponentConsumer from "./__fixtures__/GlobalComponentConsumer.vue";
import MyGlobalComponent from "./__fixtures__/MyGlobalComponent.vue";

interface TestScope extends angular.IScope {
handleButtonClick: (val: number) => number;
Expand Down Expand Up @@ -67,13 +69,18 @@ describe("NgVueProvider", () => {
},
});

ngVueProvider.component("myGlobalComponent", MyGlobalComponent);

(ngVueProvider.plugins.customNgVuePlugin as CustomNgVuePluginConfig).register([
"make",
"colour",
"quantity",
]);

$compileProvider.directive(...ngVueComponent("myButton", Button));
$compileProvider.directive(
...ngVueComponent("globalComponentConsumer", GlobalComponentConsumer)
);
}
)
);
Expand Down Expand Up @@ -173,5 +180,28 @@ describe("NgVueProvider", () => {
expect(element.find("span")[2].textContent).toEqual("HELLO!?");
});
});

describe("global components", () => {
beforeEach(
angular.mock.inject(
(_$rootScope_: angular.IRootScopeService, _$compile_: angular.ICompileService) => {
compile = ngHtmlCompiler(_$compile_);
scope = (function (): TestScope {
const tmpScope: any = _$rootScope_.$new();
tmpScope.handleButtonClick = jest.fn();
return tmpScope;
})();

element = compile(`<global-component-consumer></global-component-consumer>`, scope);
}
)
);

it("should render a global component", () => {
expect(element.find("div")[0]).toBeDefined();
expect(element.find("div")[0].classList.contains("my-global-component")).toBeTruthy();
expect(element.find("div")[0].textContent).toEqual("Hello Jane!");
});
});
});
});
24 changes: 14 additions & 10 deletions packages/ngVue3/lib/angular/ngVueLinker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,22 +125,26 @@ function createAppInstance(
}

/**
* Loads the globals such as Injectables, Plugins and Directives that has been
* added via the ngVueProvider
* Loads the globals such as Injectables, Plugins, Directives, and Components
* that has been added via the ngVueProvider
*
* @param vueInstance
* @param $injector
*/
function loadNgVueGlobals(vueInstance: App<Element>, $injector: ng.auto.IInjectorService) {
const $ngVue: NgVueService | null = $injector.has("$ngVue") ? $injector.get("$ngVue") : null;
if ($ngVue) {
$ngVue.initNgVuePlugins(vueInstance);
$ngVue.getVuePlugins().forEach((plugin) => vueInstance.use(plugin));
$ngVue.getInjectables().forEach(([key, value]) => vueInstance.provide(key, value));
Object.entries($ngVue.getVueDirectives()).forEach(([name, directive]) =>
vueInstance.directive(name, directive)
);
}

if (!$ngVue) return;

$ngVue.initNgVuePlugins(vueInstance);
$ngVue.getVuePlugins().forEach((plugin) => vueInstance.use(plugin));
$ngVue.getInjectables().forEach(([key, value]) => vueInstance.provide(key, value));
Object.entries($ngVue.getVueDirectives()).forEach(([name, directive]) =>
vueInstance.directive(name, directive)
);
Object.entries($ngVue.getVueComponents()).forEach(([name, component]) =>
vueInstance.component(name, component)
);
}

function getInnerHtml(element: HTMLElement) {
Expand Down
13 changes: 12 additions & 1 deletion packages/ngVue3/lib/angular/ngVueProvider.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import angular from "angular";
import { App, Directive, InjectionKey, Plugin } from "vue";
import { App, Directive, InjectionKey, Plugin, Component } from "vue";

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

Expand All @@ -20,6 +20,7 @@ export interface NgVueService {
getInjectables(): NgVueInjectable[];
getVuePlugins(): Plugin[];
getVueDirectives(): Record<string, Directive>;
getVueComponents(): Record<string, Component>;
}

export class NgVueProvider {
Expand All @@ -31,6 +32,7 @@ export class NgVueProvider {
private injectables: NgVueInjectable[] = [];
private nativeVuePlugins: Plugin[] = [];
private nativeVueDirectives: Record<string, Directive> = {};
private nativeVueComponents: Record<string, Component> = {};

constructor(private $injector: ng.auto.IInjectorService) {
this.$get = [
Expand All @@ -49,6 +51,7 @@ export class NgVueProvider {
getInjectables: () => this.injectables,
getVuePlugins: () => this.nativeVuePlugins,
getVueDirectives: () => this.nativeVueDirectives,
getVueComponents: () => this.nativeVueComponents,
};
},
];
Expand Down Expand Up @@ -86,6 +89,14 @@ export class NgVueProvider {
this.nativeVueDirectives[name] = vueDirective;
}

/**
* Acts as a pass through for native vue components to the app instance.
* @param vueComponent
*/
component(name: string, vueComponent: Component) {
this.nativeVueComponents[name] = vueComponent;
}

/**
* Installs an ngVue plugin, this gives access to configuration via the
* ngVueProvider and gives the Vue plugin install method access to the angular
Expand Down

0 comments on commit f2e8d2e

Please sign in to comment.