Skip to content

Commit d8aa571

Browse files
committed
refactor(VueLoader): convert load method to async/await
add route example vue-page
1 parent 83673ab commit d8aa571

13 files changed

Lines changed: 90 additions & 64 deletions

File tree

README.md

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,6 @@ Ensure you have initialized the Vue module before use.
1111

1212
---
1313

14-
## 🛣️ Route Configuration
15-
16-
When using with routing, you need to **initialize the Vue module** before loading the Angular component.
17-
18-
```typescript
19-
{
20-
path: '',
21-
loadComponent: async () =>
22-
VueLoader.initVueModule().then(() => Home),
23-
}
24-
```
2514
## 💡 Example
2615
```typescript
2716
import { Component, ViewChild } from '@angular/core';
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11

22
export interface VueModule {
3-
createPreview: ()=> void;
3+
createPreview: ()=> void; // Function that creates and returns a Vue instance
4+
createVueApp: ()=> void;
45
[key: string]: any;
56
}

projects/shell/src/app/app.html

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@ <h3 class="logo">Angular Microfrontends</h3>
99
Home
1010
</a>
1111
</li>
12+
<li>
13+
<a class="nav-link" routerLink="/vue-page" routerLinkActive="active" [routerLinkActiveOptions]="{ exact: true }">
14+
Vue page
15+
</a>
16+
</li>
1217
<li>
1318
<a class="nav-link" routerLink="/first" routerLinkActive="active">
1419
First MF

projects/shell/src/app/app.routes.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,26 @@
11
import { Routes } from '@angular/router';
22
import { loadRemoteModule } from '@angular-architects/native-federation';
3+
import { loadRemoteModule as loadRemoteModuleCustom } from 'shared/helpers';
34
import { Home } from './pages/home/home';
45
import { VueLoader } from './services/vue-loader';
6+
import { createVueWrapperComponent } from '../helpers';
7+
import { environment } from '../environments/environment';
8+
import { VueModule } from 'shared/types';
59

610
export const routes: Routes = [
711
{
812
path: '',
9-
loadComponent: async () => VueLoader.initVueModule().then((_) => Home),
13+
component: Home,
14+
},
15+
{
16+
path: 'vue-page',
17+
loadComponent: () =>
18+
loadRemoteModuleCustom({
19+
remoteEntry: environment.customRemotes.templateEditor,
20+
exposedModule: './VueEntry',
21+
}).then((m: VueModule) =>
22+
createVueWrapperComponent(m.createVueApp)
23+
),
1024
},
1125
{
1226
path: 'first',

projects/shell/src/app/components/template-editor/template-editor.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ export class TemplateEditor implements OnInit, OnChanges, OnDestroy {
2020

2121
constructor(private vueLoader: VueLoader) {}
2222

23-
ngOnInit(): void {
24-
this.app = this.vueLoader.createPreview();
23+
async ngOnInit() {
24+
this.app = await this.vueLoader.createPreview();
2525
this.vm = this.app.mount('#template-editor');
2626
this.vm.data = this.data;
2727
this.vm.template = this.template;

projects/shell/src/app/pages/home/home.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import { FormsModule } from '@angular/forms';
99
styleUrl: './home.scss'
1010
})
1111
export class Home{
12-
@ViewChild(TemplateEditor) templateEditor!: TemplateEditor;
1312
template = `<PageA4 style="padding: 3mm 15mm 3mm 15mm;">
1413
<div>{{ data.name }}</div>
1514
<p>Tuổi: {{ data.age }}</p>

projects/shell/src/app/services/vue-loader.ts

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,29 +4,27 @@ import { environment } from '../../environments/environment';
44
import { VueModule } from 'shared/types';
55
@Injectable({ providedIn: 'root' })
66
export class VueLoader {
7-
static vueModule: VueModule | null = null;
8-
static async initVueModule() {
9-
if (this.vueModule) return;
7+
module: VueModule | null = null;
8+
private async _initVueModule() {
9+
if (this.module) return;
1010
try {
11-
this.vueModule = await loadRemoteModule({
11+
this.module = await loadRemoteModule({
1212
remoteEntry: environment.customRemotes.templateEditor,
1313
exposedModule: './VueEntry',
1414
});
1515
} catch (error) {
1616
console.error('[VueLoader] Failed to load Vue remote module:', error);
17-
this.vueModule = null;
17+
this.module = null;
1818
}
1919
}
20-
get module() {
21-
return VueLoader.vueModule;
22-
}
2320

24-
createPreview() {
21+
async createPreview() {
22+
await this._initVueModule();
2523
try {
2624
if (!this.module?.createPreview) {
2725
throw new Error('Vue module does not export createPreview.');
2826
}
29-
return this.module?.createPreview();
27+
return this.module.createPreview();
3028
} catch(error) {
3129
console.log(error);
3230
return null;

projects/shell/src/environments/environment.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@ export const environment = {
22
production: false,
33
customRemotes: {
44
templateEditor: 'http://localhost:4202/assets/remoteEntry.js',
5+
// templateEditor: 'https://duynndu.github.io/mfe-angular/template-editor/assets/remoteEntry.js'
56
},
67
federationRemotes: {
7-
firstMf: "http://localhost:4201/remoteEntry.json",
8+
// firstMf: "http://localhost:4201/remoteEntry.json",
9+
firstMf: "https://duynndu.github.io/mfe-angular/first-mf/remoteEntry.json"
810
}
911
};
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { Component, OnInit, OnDestroy } from '@angular/core';
2+
3+
// A helper function to create an Angular component that wraps a Vue component
4+
export function createVueWrapperComponent(createVueInstance: Function, props: any = {}) {
5+
@Component({
6+
template: '<div id="vueContainer"></div>'
7+
})
8+
class VueWrapperComponent implements OnInit, OnDestroy {
9+
private vueApp: any;
10+
private vm: any;
11+
12+
ngOnInit() {
13+
this.vueApp = createVueInstance('#vueContainer');
14+
this.vm = this.vueApp.mount('#vueContainer');
15+
Object.entries(props).forEach(([key, value]) => {
16+
this.vm[key] = value;
17+
});
18+
}
19+
20+
ngOnDestroy() {
21+
this.vueApp?.unmount();
22+
}
23+
}
24+
return VueWrapperComponent;
25+
}
Lines changed: 24 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,28 @@
1+
<script setup lang="ts">
2+
</script>
3+
14
<template>
2-
<Preview v-model:template="template" :data="data"/>
3-
<!-- <Codemirror v-model:value="template" :options="{ mode: 'text/html', theme: 'default', tabSize: 2 }" height="400px" :border="true" /> -->
5+
<div style="height: 100vh; display: flex; justify-content: center; align-items: center;">
6+
<a href="https://vite.dev" target="_blank">
7+
<img src="./assets/vite.svg" class="logo" alt="Vite logo" />
8+
</a>
9+
<a href="https://vuejs.org/" target="_blank">
10+
<img src="./assets/vue.svg" class="logo vue" alt="Vue logo" />
11+
</a>
12+
</div>
413
</template>
514

6-
<script lang="ts">
7-
import Preview from './components/preview/Preview.vue';
8-
import "codemirror/mode/htmlmixed/htmlmixed.js"
9-
import '@imengyu/vue3-context-menu/lib/vue3-context-menu.css'
10-
11-
export default {
12-
name: 'App',
13-
components: { Preview },
14-
data() {
15-
return {
16-
data: { name: 'Nguyen Van A', age: "30" },
17-
template: `<PageA4 style="padding: 3mm 15mm 3mm 15mm;">
18-
<div>{{ data.name }}</div>
19-
<p>Tuổi: {{ data.age }}</p>
20-
<Textarea
21-
v-model="data.name"
22-
label="Họ và tên:"
23-
:line="true"
24-
:suffix="{ length: 1, char: '❤️' }"
25-
/>
26-
<Textarea
27-
v-model="data.age"
28-
label="Tuổi:"
29-
:line="true"
30-
/>
31-
<InputOTP
32-
v-model="data.age"
33-
:maskLength="[1,1,1]"
34-
pad-start="0"
35-
/>
36-
37-
</PageA4>`
38-
}
39-
}
15+
<style scoped>
16+
.logo {
17+
height: 6em;
18+
padding: 1.5em;
19+
will-change: filter;
20+
transition: filter 300ms;
4021
}
41-
</script>
22+
.logo:hover {
23+
filter: drop-shadow(0 0 2em #646cffaa);
24+
}
25+
.logo.vue:hover {
26+
filter: drop-shadow(0 0 2em #42b883aa);
27+
}
28+
</style>

0 commit comments

Comments
 (0)