First create the NX workspace and the initial application named search
:
npx create-nx-workspace@latest ang-pokemon-mfe
Set it up as angular, with the name search
and SCSS for the styling:
✔ What to create in the new workspace · angular
✔ Application name · search
✔ Default stylesheet format · scss
✔ Use Nx Cloud? (It's free and doesn't require registration.) · No
Create another application called home
:
nx generate @nrwl/angular:app home
Don't choose routing (unless you want to):
✔ Would you like to configure routing for this application? (y/N) · false
Add PrimeNG libraries to both applications:
cd apps/home
yarn add primeflex primeicons primeng
cd ../search
yarn add primeflex primeicons primeng
Update the CSS in both applications by removing all content from app.component.scss
and adding this to styles.css
@import "primeng/resources/themes/saga-blue/theme.css";
@import "primeng/resources/primeng.min.css";
@import "primeflex/primeflex.css";
@import "primeicons/primeicons.css";
Change the app.module.ts
in both applications to:
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { FormsModule } from '@angular/forms';
import { PanelModule } from 'primeng/panel';
import { ButtonModule } from 'primeng/button';
import { CarouselModule } from 'primeng/carousel';
import { InputTextModule } from 'primeng/inputtext';
import { AppComponent } from './app.component';
@NgModule({
declarations: [AppComponent],
imports: [
BrowserModule,
FormsModule,
PanelModule,
ButtonModule,
BrowserAnimationsModule,
CarouselModule,
InputTextModule,
],
providers: [],
bootstrap: [AppComponent],
schemas: [],
})
export class AppModule {}
In home
change app.component.html
to:
<h2 class="p-5">Home</h2>
<div class="m-6">
<div class="shadow-8 border-1 border-blue-500 p-5" style="border-radius: 1em;">
<div class="grid">
<div class="col-2">
<img src="https://raw.githubusercontent.com/jherr/fower-pokemon-vue/master/public/pokemon/blastoise.jpg" style="width: 100%" />
</div>
<div class="col-10">
<h2>Blastoise is really cool</h2>
<div>
It crushes its foe under its heavy body to cause fainting. In a pinch, it will withdraw inside its shell.
</div>
</div>
</div>
</div>
</div>
In search
change app.component.html
to:
<h2>Search</h2>
<div class="grid">
<div class="col-2">
<div class="box">
<p-panel header="Search">
<input type="text" pInputText [(ngModel)]="search" (ngModelChange)="onSearchChange($event)" />
</p-panel>
</div>
</div>
<div class="col-10">
<div class="grid">
<div class="col-2" *ngFor="let p of pokemon">
<p-panel [header]="p.name.english">
<img [src]="p.image" style="height: 150px;" />
</p-panel>
</div>
</div>
</div>
</div>
And change app.component.ts
to:
import { Component, OnInit } from '@angular/core';
interface Pokemon {
name: {
english: string;
};
image: string;
}
...
export class AppComponent implements OnInit {
pokemonSource: Pokemon[] = [];
pokemon: Pokemon[] = [];
search = '';
onSearchChange(search: string) {
this.pokemon = this.pokemonSource
.filter((p) =>
p.name.english.toLowerCase().includes(search.toLowerCase())
)
.slice(0, 20);
}
async ngOnInit() {
fetch(
'https://raw.githubusercontent.com/jherr/fower-pokemon-vue/master/public/pokemon.json'
)
.then((resp) => resp.json())
.then((data) => {
this.pokemonSource = data.map((p: Pokemon) => ({
...p,
image: `https://raw.githubusercontent.com/jherr/fower-pokemon-vue/master/public/pokemon/${p.name.english.toLowerCase()}.jpg`,
}));
this.pokemon = this.pokemonSource.slice(0, 20);
});
}
}
Fire up the servers:
nx run home:serve:development
nx run search:serve:development --port 4201
Add a new PokemonCarousel component to the search project.
nx generate @nrwl/angular:component PokemonCarousel --project search
Add this to app.component.html
<ang-pokemon-mfe-pokemon-carousel></ang-pokemon-mfe-pokemon-carousel>
Change pokemon-carousel/pokemon-carousel.component.html
to:
<div class="max-w-min">
<p-carousel *ngIf="images.length > 0" [value]="images" [numVisible]="3" [numScroll]="1">
<ng-template let-item pTemplate="item">
<h2 [innerText]="item.name" class="center"></h2>
<img [src]="item.image" style="width: 100%;" />
</ng-template>
</p-carousel>
</div>
And pokemon-carousel/pokemon-carousel.component.ts
to:
import { Component, OnInit, Input } from '@angular/core';
interface Pokemon {
name: {
english: string;
};
}
interface Image {
image: string;
name: string;
}
...
export class PokemonCarouselComponent implements OnInit {
images: Image[] = [];
@Input() search = '';
ngOnInit(): void {
fetch(
'https://raw.githubusercontent.com/jherr/fower-pokemon-vue/master/public/pokemon.json'
)
.then((resp) => resp.json())
.then((data) => {
this.images = data
.map((p: Pokemon) => ({
image: `https://raw.githubusercontent.com/jherr/fower-pokemon-vue/master/public/pokemon/${p.name.english.toLowerCase()}.jpg`,
name: p.name.english,
}))
.filter((p: Image) => p.name.toLowerCase().indexOf(this.search) > -1)
.slice(0, 10);
});
}
}
Shut down servers and:
ng add @angular-architects/module-federation --project search --port 4201
ng add @angular-architects/module-federation --project home --port 4200
Change apps/search/webpack.config.js
:
// For remotes (please adjust)
name: "search",
filename: "remoteEntry.js",
exposes: {
'./CarouselComponent': './apps/search/src/app/pokemon-carousel/pokemon-carousel.component.ts',
},
Change apps/home/webpack.config.js
:
// For hosts (please adjust)
remotes: {
search: 'search@http://localhost:4201/remoteEntry.js',
},
Start servers:
nx run home:serve:development
nx run search:serve:development --port 4201
nx generate @nrwl/angular:component CarouselHost --project home
Remove everything from carousel-host/carousel-host.component.html
and change carousel-host/carousel-host.component.ts
to:
import {
Component,
OnInit,
ComponentFactoryResolver,
ViewContainerRef,
ComponentRef,
} from '@angular/core';
import { loadRemoteModule } from '@angular-architects/module-federation';
@Component({
selector: 'ang-pokemon-mfe-carousel-host',
templateUrl: './carousel-host.component.html',
styleUrls: ['./carousel-host.component.scss'],
})
export class CarouselHostComponent implements OnInit {
constructor(
private cfr: ComponentFactoryResolver,
private vcref: ViewContainerRef
) {}
async ngOnInit() {
const { PokemonCarouselComponent } = await loadRemoteModule({
remoteEntry: 'http://localhost:4201/remoteEntry.js',
remoteName: 'search',
exposedModule: './CarouselComponent',
});
const componentRef: ComponentRef<{
search: string;
}> = this.vcref.createComponent(
this.cfr.resolveComponentFactory(PokemonCarouselComponent)
);
componentRef.instance.search = 'p';
}
}
Add to app.component.html
in home
:
<ang-pokemon-mfe-carousel-host></ang-pokemon-mfe-carousel-host>
Change anything in pokemon-carousel.component.html
and watch it update automatically.