@jaredmcateer/ngvue3
v0.5.0
Published
Use Vue 3 components in Angular 1.x
Downloads
855
Maintainers
Readme
ngVue 3 is a fork of the ngVue project which is still recommended for Vue 2
Table of Contents
Install
via npm:
npm install @jaredmcateer/ngvue3
via yarn:
yarn add @jaredmcateer/ngvue3
via pnpm:
pnpm install @jaredmcater/ngvue3
Usage:
ngVue3 comes in both UMD and ESM packages and is compatible with CommonJS, AMD and ES Module as well as supporting browser global variable definition.
First of all, load AngularJS 1.x, Vue 3 and ngVue 3:
<!-- load on the page with `script` tag -->
<script src="./node_modules/angular/angular.min.js"></script>
<script src="./node_modules/vue/dist/vue.global.js"></script>
<script src="./node_modules/ngVue/dist/main.js"></script>
<!-- or if using modules -->
<script type="module" src="./node_modules/ngVue/dist/main.mjs"></script>
or:
CommonJs Style
const ng = require("angular");
const vue = require("vue");
const { useNgVue } = require("@jaredmcateer/ngvue3");
UMD Style
// the AMD style
require(["angular", "vue", "ngVue3"], function (ng, Vue, ngVue3) {
const useNgVue = ngVue3.useNgVue();
});
ECMAScript module style
import angular from "angular";
import vue from "vue";
import { useNgVue } from "@jaredmcateer/ngvue3";
And then require ngVue
as a dependency in your app:
const ngVue = useNgVue();
angular.module("yourApp", [ngVue]);
Features
ngVue 3 is composed of a factory createVueComponent
. Unlike the previous version there is no plain vue-component
directive nor plans to implement it, however, if you rely on this we are happy to accept a PR.
The createVueComponent
factory
The createVueComponent
factory creates a reusable Angular directive which is bound to a specific Vue component. This allows for writing components in your angular templates with a similar API to your native vue components.
The factory returns a plain $compile
options object which describe how to compile the Angular directive with Vue 3, and it takes the Vue component as the first argument. The directive wraps the Vue component into an angular directive so that the component can be created and initialized while angular is compiling the templates.
An Angular controller needs to be created to declare view data
const ngVue = useNgVue();
const app = angular.module("vue.components", [ngVue]).controller(
"MainController",
class MainController {
constructor() {
this.person = {
firstName: "Barb",
lastName: "Ara",
};
}
}
);
Then declare a Vue component:
import { h } from "vue";
export default defineComponent({
props: {
firstName: String,
lastName: String,
},
setup(props) {
return () => h("span", `Hello, ${props.firstName} ${props.lastName}`);
},
});
Note: It doesn't have to be exactly like this you can use any flavour of component declaration you want, Options API, Composition API, and Script Setup, SFC or rendering with (j|t)sx all should work.
And finally regsiter the Vue component in the Angular module with the createVueComponent
factory like this:
import SomeComponent from "./SomeComponent.vue";
app.directive("SomeComponent", (createVueComponent) => createVueComponent(SomeComponent));
The ngVueComponent
helper
The previous example contains a lot of boilerplate for creating a vue component, especially when you couple it with minification support you end up with
import SomeComponent from "./SomeComponent.vue";
app.directive("SomeComponent", [
"createVueComponent",
(createVueComponent) => createVueComponent(SomeComponent),
]);
ngVue 3 provides a helper function, ngVueComponent
, to DRY this code
import { ngVueComponent } from "@jaredmcateer/ngvue3";
// ...
app.directive(...ngVueComponent("SomeComponent", SomeComponent));
Directive attributes
The directive created by createVueComponent
provides two main attributes:
v-props
/ v-props-*
v-props
is a string expression evaluated to an object as the data exposed to the Vue component
v-props-*
allows you to name the partial data extract from angular scope. The directive then wraps them into a new object and passes it to the Vue component. E.g., v-props-first-name
and v-props-last-name
will create two properties firstName
and lastName
in a new object as the component data.
watch-depth
This attribute indicates which watch strategy AngularJs will use to detect changes on the scope object. The possible values are as follows:
| value | description |
| ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| reference | (default) watches the object reference |
| collection | (rarely used) same as angular $watchCollection
, shallow watches the properties of the object: for arrays, it watches the array items; for object maps, it watches the properties |
| value | (rarely used) deep watches every property inside the object |
Notes
watch-depth
cannot propagate all the changes on the scope object to Vue due to the limitation of the reactivity system. (TODO this may be somewhat outdated as it comes from Vue 2, the new reactivity system is somewhat improved, need to check the veracity of this statement)- The
colleciton
strategy andvalue
strategy are rarely used. The scope object will be converted into a reactive object by Vue and so any changes on the reactive scope object will trigger the Vue updates. - The
value
strategy is not recommended because it causes heavy computation. To detect the change, Angular must copy the entire object and traverse every property inside for each digest cycle.
v-on-*
v-on-*
takes a string representation of a function handler. The directive wraps them into a new object and passes it to the Vue component similar to v-props-*
. E.g., v-on-button-clicked
will create a property onButtonClicked
in a new object as the component events.
app.controller("MainController", function () {
this.handleHelloEvent = (greetings) => console.log(greetings);
});
<some-component v-on-hello-world="ctrl.handleHelloEvent"></some-component>
import { h } from "vue";
export default defineComponent({
events: ["hello-world"],
methods: {
helloFromVue() {
this.$emit("hello-world", "Hello World!");
},
},
render() {
return h("button", { onClick: () => this.helloFromVue() });
},
});
WARNING: If the event handler uses this
you must provide a bound function to the value of a v-on-*
attribute. It is a common case that in class based controllers you will have a method on the class instance that handles events
e.g.,
class MainController {
handleClick() {
this.buttonClicked = true;
}
}
The methods on the prototype of the class instance are not bound so when you provide them to the attribute v-on-button-clicked="ctrl.handleClick"
the scope will be lost (Cannot set property 'buttonClicked' of undefined).
A simple solution to this is to bind the event in the attribute value v-on-button-clicked="ctrl.handleClick.bind(ctrl)"
, alternatively you can create an instance property on the class with an arrow function.
Handling HTML Attributes
Just like regular Vue components, you can pass HTML attributes from the parent Angular component to your Vue component. The parent's class
and style
attributes will be merged with the corresponding Vue component attributes, while other will be passed down unless they conflict with attributes in the Vue component's own template.
Keep in mind that hwne you pass down literal strings for anything other than class
and style
attributes, they must be surrounded by quotes. e.g., data-values="'enabled'"
, as they will be evaluated by angular before being passed on.
<my-custom-button
disabled="ctrl.isDisabled"
class="excellent"
tabindex="3"
type="'submit'"
v-props-button-text="'Click me'"
/>
Note: If your component uses a multi-root template you must bind the $attrs
to an element
<template>
<header>...</header>
<main v-bind="$attrs">...</main>
<footer>...</footer>
</template>
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, Directives and Global Components Documentation
Directives usage
Using a directive on your ngVue component can be done using the v-directives
attribute assigning it a string, directive bindings or array of bindings:
<!-- string -->
<my-component v-directives="'greeting'"></my-component>
<!-- Accepts comma separated string -->
<my-component v-directives="'greeting,focus'"></my-component>
<!-- Binding object -->
<my-component
v-directives="{
name: 'greeting',
value: 'Hello World',
arg: 'foo',
modifiers: { shout: true, blink: true }
}"
></my-component>
<!-- Array of binding objects -->
<my-component
v-directives="[
{name: 'greeting', value: 'Hello World'},
{name: 'focus'}
]"
></my-component>
The bindings passed to the ngVue are the same bindings passed to the directives lifecycle hooks.
| Property | Type | Equivalent | Description |
| ----------- | ------------------------- | -------------------------- | ------------------------------------------------- |
| name
| string
| v-greeting
| Name of directive. |
| value
| any
| v-greeting="Hello World"
| Optional. The value passed to the directive |
| arg
| string
| v-greeting:foo
| Optional. The argument passed to the directive |
| modifiers
| Record<string, boolean>
| v-greeting.blink.warning
| Optional. An object containing modifiers, if any. |
TODO
- [x] vue components
- [x] vue directives
- [x] unit tests
- [x] docs + examples (they're done just working on deploying them)
- [x] plugins
- ~~[x] Filters (won't do, vue 3 doesn't support global filters)~~
- [ ] support vuex (maybe?)
- [ ] support pinia