Ask about micro frontend with angular by native federation

Discussão em 'Angular' iniciado por xnlc262, Junho 12, 2025.

  1. xnlc262

    xnlc262 Guest

    I have just study micro frontend with Angular by native federation. I 've just done in create host app (shell) and remote app (vehicle, ...); configured port for host and remote apps . I was configuring routes to make host app approach remote app but I have some problems. When I enter host link (http://localhost:4200), I can't see remote app that I want to appear, but when I enter remote app link (http://localhost:4201/), It worked smoothly. I use console.log to check what happened and I saw that when I use host link, It went to app.ts of remote app, went to html file of that remote app but couldn't use of that remote app. As I said, when I ran by remote app link, everything worked. Here are some file in my project. Please help me do this. Thanks.

    There are some files in host app (shell):

    federation.manifest.json file:

    {
    "vehicle": "http://localhost:4201/remoteEntry.json",
    "manufacturer": "http://localhost:4202/remoteEntry.json",
    "management-unit": "http://localhost:4203/remoteEntry.json",
    "driver": "http://localhost:4204/remoteEntry.json"
    }


    main.ts file:

    import { initFederation } from '@angular-architects/native-federation';
    import { initializeKeycloak } from '@keycloak/keycloak-init';

    initializeKeycloak()
    .then(() => {
    initFederation('federation.manifest.json')
    .catch(err => console.error(err))
    .then(_ => import('./bootstrap'))
    .catch(err => console.error(err));
    });


    bootstrap.ts file:

    import { bootstrapApplication } from '@angular/platform-browser';
    import { appConfig } from './app/app.config';
    import { App } from './app/app';

    bootstrapApplication(App, appConfig)
    .catch((err) => console.error(err));


    index.html file:

    <!doctype html>
    <html lang="en">
    <head>
    <meta charset="utf-8">
    <title>Vehicle Management</title>
    <base href="/">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="icon" type="image/x-icon" href="favicon.ico">
    </head>
    <body>
    <app-shell-root></app-shell-root>
    </body>
    </html>


    app.html file:

    <app-shell-header></app-shell-header>
    <div class="content">
    <router-outlet />
    </div>


    app.ts file:

    import { Component } from '@angular/core';
    import { RouterOutlet } from '@angular/router';
    import { Header } from './header/header';

    @Component({
    selector: 'app-shell-root',
    standalone: true,
    imports: [RouterOutlet, Header],
    templateUrl: './app.html',
    styleUrl: './app.css'
    })
    export class App {
    protected title = 'Vehicle Management';
    }


    app.routes.ts file:

    import { loadRemoteModule } from '@angular-architects/native-federation';
    import { Routes } from '@angular/router';
    import { authGuard } from '@keycloak/keycloak-auth.guard';

    export const routes: Routes = [
    {
    path: '',
    loadComponent: () => loadRemoteModule('vehicle', './Component').then((m) => m.App),
    canActivate: [authGuard],
    },
    {
    path: 'vehicle',
    loadComponent: () => loadRemoteModule('vehicle', './Component').then((m) => m.App),
    },
    {
    path: 'manufacturer',
    loadComponent: () => loadRemoteModule('manufacturer', './Component').then((m) => m.App),
    },
    {
    path: 'management-unit',
    loadComponent: () => loadRemoteModule('management-unit', './Component').then((m) => m.App),
    },
    {
    path: 'driver',
    loadComponent: () => loadRemoteModule('driver', './Component').then((m) => m.App),
    },
    {
    path: '**',
    component: Headers
    },
    ];



    app.config.ts file:

    import { ApplicationConfig, provideBrowserGlobalErrorListeners, provideZoneChangeDetection } from '@angular/core';
    import { provideRouter } from '@angular/router';

    import { routes } from './app.routes';

    export const appConfig: ApplicationConfig = {
    providers: [
    provideBrowserGlobalErrorListeners(),
    provideZoneChangeDetection({ eventCoalescing: true }),
    provideRouter(routes)
    ]
    };


    There are some files in remote app (vehicle):

    vehicle.component.ts file:

    import { Component } from '@angular/core';
    import { Router } from '@angular/router';
    import { ManufacturerItems } from '@vehicle/types/ManufacturerItems';
    import { VehicleItems } from '@vehicle/types/VehicleItems'
    import { CommonModule, DatePipe } from '@angular/common';
    import { FormControl, FormsModule, ReactiveFormsModule } from '@angular/forms';
    import { keycloak } from '@keycloak/keycloak-init';
    import * as XLSX from 'xlsx';
    import { jsPDF } from 'jspdf';
    import autoTable from 'jspdf-autotable';
    import '@vehicle/assets/fonts/roboto-font';
    import { VehicleServices } from '@vehicle/services/VehicleServices';
    import { ManufacturerServices } from '@vehicle/services/ManufacturerServices';
    import { Subject, takeUntil } from 'rxjs';

    @Component({
    selector: 'vehicle-app',
    standalone: true,
    imports: [
    CommonModule,
    ReactiveFormsModule,
    FormsModule
    ],
    templateUrl: './vehicle.component.html',
    styleUrl: './vehicle.component.css'
    })

    export class VehicleComponent {
    private destroy$ = new Subject<void>();

    constructor(
    private vehicleServices: VehicleServices,
    private manufacturerServices: ManufacturerServices,
    private router: Router
    ) { console.log('✅ VehicleComponent đã được load'); }

    vehicles: VehicleItems[] = [];
    manufacturers: ManufacturerItems[] = [];

    licensePlateKey = new FormControl('');
    manufacturerId = new FormControl('');

    currentPage: number = 0;
    pageSize: number = 10;
    totalItems: number = 0;
    totalPages: number = 0;

    ngOnInit(): void {
    this.loadVehicles();

    this.manufacturerServices.getAllManufacturers()
    .pipe(takeUntil(this.destroy$))
    .subscribe((manufacturers: ManufacturerItems[]) => {
    this.manufacturers = manufacturers;
    });
    }

    loadVehicles() {
    this.vehicleServices.getAllVehicles(this.currentPage, this.pageSize)
    .pipe(takeUntil(this.destroy$))
    .subscribe({
    next: (response: any) => {
    this.vehicles = response.content;
    console.log(this.vehicles);
    this.totalItems = response.totalElements;
    this.totalPages = response.totalPages;
    },
    error: (err) => {
    console.error('Error loading vehicles:', err);
    }
    });
    }

    handleSearchVehicle() {
    this.vehicleServices.searchVehicles(
    this.licensePlateKey.value as string,
    Number(this.manufacturerId.value),
    this.currentPage,
    this.pageSize
    )
    .pipe(takeUntil(this.destroy$))
    .subscribe({
    next: (response: any) => {
    this.vehicles = response.content;
    this.totalItems = response.totalElements;
    this.totalPages = response.totalPages;
    },
    error: (err) => {
    console.error('Error searching vehicles:', err);
    }
    });
    }

    goToPage(page: number): void {
    if (page < 0 || page >= this.totalPages) return;

    this.currentPage = page;

    if (this.isSearchActive()) {
    this.handleSearchVehicle();
    } else {
    this.loadVehicles();
    }
    }

    changePageSize(size: number): void {
    this.pageSize = size;
    this.currentPage = 0;
    if (this.isSearchActive()) {
    this.handleSearchVehicle();
    } else {
    this.loadVehicles();
    }
    }

    // Check if search is active
    isSearchActive(): boolean {
    return !!this.licensePlateKey.value || this.manufacturerId.value !== '0';
    }

    // Generate page numbers for pagination controls
    getPagesArray(): number[] {
    const pages = [];
    const maxVisiblePages = 5;

    let startPage = Math.max(0, this.currentPage - Math.floor(maxVisiblePages / 2));
    let endPage = Math.min(this.totalPages - 1, startPage + maxVisiblePages - 1);

    // Adjust if we're at the beginning
    if (endPage - startPage + 1 < maxVisiblePages) {
    startPage = Math.max(0, endPage - maxVisiblePages + 1);
    }

    for (let i = startPage; i <= endPage; i++) {
    pages.push(i);
    }

    return pages;
    }

    viewUpdateForm(vehicleId: number) {
    this.router.navigate(['/vehicles/update', vehicleId]);
    }

    viewAddForm() {
    this.router.navigate(['/vehicles/add']);
    }

    confirmDelete(vehicleId: number) {
    if (confirm('Are you sure?')) {
    this.vehicleServices.deleteVehicles(vehicleId)
    .pipe(takeUntil(this.destroy$))
    .subscribe({
    next: () => {
    alert('Vehicle Deleted !');
    this.loadVehicles();
    },
    error: (err) => {
    if (err.status === 401) {
    alert("Your session has expired. Please log in again.");
    keycloak.logout({ redirectUri: window.location.origin });
    }
    }
    });
    }
    }

    //download file
    exportXLSXFile() {
    this.vehicleServices.getVehiclesList()
    .pipe(takeUntil(this.destroy$))
    .subscribe((data: any[]) => {
    const worksheetData = [
    ['CÔNG TY TNHH ABC XYZ'],
    ['BẢNG THEO DÕI DANH SÁCH XE'],
    [],
    ['ID', 'License Plate', 'Manufacturer', 'Management Unit', 'Driver', 'Created Time', 'Updated Time'],
    ...data.map(v => [
    v.id,
    v.licensePlate,
    v.manufacturerName,
    v.managementUnitName,
    v.driverName,
    v.createdTime,
    v.updatedTime
    ]),
    [],
    ['', '', '', '', '', 'Giám đốc xác nhận']
    ];

    const ws: XLSX.WorkSheet = XLSX.utils.aoa_to_sheet(worksheetData);

    ws['!merges'] = [
    { s: { r: 0, c: 0 }, e: { r: 0, c: 6 } },
    { s: { r: 1, c: 0 }, e: { r: 1, c: 6 } }
    ];

    ws['!cols'] = [
    { wch: 5 }, // ID
    { wch: 15 }, // License Plate
    { wch: 20 }, // Manufacturer
    { wch: 25 }, // Management Unit
    { wch: 20 }, // Driver
    { wch: 20 }, // Created Time
    { wch: 20 }, // Updated Time
    ];

    const wb: XLSX.WorkBook = XLSX.utils.book_new();
    XLSX.utils.book_append_sheet(wb, ws, 'Vehicle Report');
    XLSX.writeFile(wb, 'vehicle-report.xlsx');
    });
    }



    exportPDFFile() {
    this.vehicleServices.getVehiclesList()
    .pipe(takeUntil(this.destroy$))
    .subscribe((vehicles: VehicleItems[]) => {
    const doc = new jsPDF();
    doc.setFont('Roboto', 'normal');

    doc.setFontSize(14);
    doc.text('CÔNG TY TNHH ABC XYZ', 105, 15, { align: 'center' });

    doc.setFontSize(16);
    doc.text('BẢNG THEO DÕI DANH SÁCH XE', 105, 25, { align: 'center' });

    const date = new DatePipe('en-US').transform(new Date(), 'dd/MM/yyyy');
    doc.setFontSize(12);
    doc.text(`Ngày xuất: ${date}`, 14, 35);

    autoTable(doc, {
    startY: 40,
    head: [['ID', 'License Plate', 'Manufacturer', 'Management Unit', 'Driver', 'Created Time', 'Updated Time']],
    body: vehicles.map(v => [
    v.id,
    v.licensePlate,
    v.manufacturerName,
    v.managementUnitName,
    v.driverName,
    v.createdTime,
    v.updatedTime
    ]),
    styles: { font: 'Roboto', fontSize: 10, fontStyle: 'normal' },
    headStyles: { fillColor: [41, 128, 185] },
    });

    const finalY = (doc as any).lastAutoTable.finalY + 20;
    doc.text('Giám đốc xác nhận', 180, finalY, { align: 'right' });

    doc.save('vehicle-report.pdf');
    });
    }

    ngOnDestroy(): void {
    this.destroy$.next();
    this.destroy$.complete();
    console.log('Component destroyed and subscriptions cleaned up');
    }
    }


    app.routes.ts file:

    import { Routes } from '@angular/router';
    import { authGuard } from '@keycloak/keycloak-auth.guard';
    import { VehicleComponent } from './pages/vehicle/vehicle.component';
    import { UpdateVehicleComponent } from './pages/updateVehicle/updateVehicle.component';
    import { AddVehicleComponent } from './pages/addVehicle/addVehicle.component';
    import { App } from './app';

    export const routes: Routes = [
    {
    path: '',
    canActivate:[authGuard],
    component: VehicleComponent,
    },
    { path: 'vehicles/update/:id', component: UpdateVehicleComponent },
    { path: 'vehicles/add', component: AddVehicleComponent }
    ];


    app.ts file:

    import { Component } from '@angular/core';
    import { RouterOutlet } from '@angular/router';

    @Component({
    selector: 'app-vehicle-root',
    standalone: true,
    imports: [RouterOutlet],
    templateUrl: './app.html',
    styleUrl: './app.css',
    })
    export class App {
    protected title = 'Vehicle';

    constructor() {
    console.log('✅ AppComponent load');
    }

    }
    `

    federation.config.js file:

    `
    const { withNativeFederation, shareAll } = require('@angular-architects/native-federation/config');

    module.exports = withNativeFederation({

    name: 'vehicle',

    exposes: {
    './Component': './projects/vehicle/src/app/app.ts',
    },

    shared: {
    ...shareAll({ singleton: true, strictVersion: true, requiredVersion: 'auto' }),
    },

    skip: [
    'rxjs/ajax',
    'rxjs/fetch',
    'rxjs/testing',
    'rxjs/webSocket',
    // Add further packages you don't need at runtime
    ]

    // Please read our FAQ about sharing libs:
    // https://shorturl.at/jmzH0

    });


    bootstrap.ts file:

    import { bootstrapApplication } from '@angular/platform-browser';
    import { appConfig } from './app/app.config';
    import { App } from './app/app';

    bootstrapApplication(App, appConfig)
    .catch((err) => console.error(err));


    app.config.ts file:

    import { ApplicationConfig, provideBrowserGlobalErrorListeners, provideZoneChangeDetection } from '@angular/core';
    import { provideRouter } from '@angular/router';
    import { AuthInterceptor } from '@keycloak/auth.interceptor'
    import { routes } from './app.routes';
    import { provideHttpClient, withInterceptors } from '@angular/common/http';

    console.log('app.config.ts của remote !!!!');
    export const appConfig: ApplicationConfig = {
    providers: [
    provideBrowserGlobalErrorListeners(),
    provideZoneChangeDetection({ eventCoalescing: true }),
    provideRouter( routes ),
    provideHttpClient(withInterceptors([AuthInterceptor]))
    ]
    };


    main.ts file:

    import { initFederation } from '@angular-architects/native-federation';
    import { initializeKeycloak } from '@keycloak/keycloak-init';

    initializeKeycloak()
    .then(() => {
    initFederation()
    .catch(err => console.error(err))
    .then(_ => import('./bootstrap'))
    .catch(err => console.error(err));
    });


    app.html file:

    <router-outlet></router-outlet>


    host app runs smoothly like what remote app did

    Continue reading...

Compartilhe esta Página