Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tested difference between MFE component and Regular usage. #3101

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion angular15-microfrontends-lazy-components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"start:profile": "ng serve mdmf-profile",
"build:shared": "ng build mdmf-shared",
"build:shell": "ng build mdmf-shell",
"build:profile": "ng build mdmf-profile --prod",
"build:profile": "ng build mdmf-profile",
"test": "ng test mdmf-shared & ng test mdmf-profile & ng test mdmf-shell",
"lint": "ng lint",
"e2e": "ng e2e mdmf-profile & ng e2e mdmf-shell",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<br />
<h2>List users from the shared application state</h2>
<h1>{{ label }}</h1>
<h1>{{ foo }}</h1>
<div *ngIf="users">
<table class="table table-striped">
<thead class="bg-primary text-white">
Expand All @@ -13,10 +15,10 @@ <h2>List users from the shared application state</h2>
<tr *ngFor="let user of users | async">
<td>{{ user.name }}</td>
<td>{{ user.email }}</td>
<td>
<td>
<button (click)="removeUser(user)" class="btn btn-danger">Remove User</button>
</td>
</td>
</tr>
</tbody>
</table>
</div>
</div>
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { Component, OnInit } from '@angular/core';
import {
Component,
Input,
OnInit,
} from '@angular/core';
import { Select, Store } from '@ngxs/store';
import { RemoveUser } from 'projects/mdmf-shared/src/lib/app-state/actions/user.action';
import { User } from 'projects/mdmf-shared/src/lib/app-state/models/User';
Expand All @@ -14,7 +18,11 @@ import { Observable } from 'rxjs';
export class ListUserComponent implements OnInit {
@Select(UserState.getUsers) users: Observable<User[]>;

constructor(private store: Store) {}
@Input() label = '';
@Input() foo = '';
constructor(private store: Store) {
console.log('Federated component Created');
}

ngOnInit(): void {}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
ɵcreateInjector,
} from '@angular/core';
import { loadRemoteModule } from '../../../utils/federation-utils';
import { ListUserComponent } from '../list-user/list-user.component';

@Component({
selector: 'federated-component',
Expand All @@ -21,6 +22,16 @@ export class FederatedComponent implements OnInit {
@Input() remoteName: string;
@Input() exposedModule: string;
@Input() componentName: string;
@Input() set props(props: Record<string, any>) {
if (this.componentRef) {
this.updateComponentProps(props);
}

this._props = props;
};

private componentRef = null;
private _props = null;

constructor(private injector: Injector) {}
ngOnInit(): void {
Expand All @@ -29,10 +40,24 @@ export class FederatedComponent implements OnInit {
remoteName: this.remoteName,
exposedModule: this.exposedModule,
}).then(federated => {
const { instance } = this.federatedComponent.createComponent(
const componentRef = this.federatedComponent.createComponent(
federated[this.exposedModule].exports.find(e => e.ɵcmp?.exportAs[0] === this.componentName),
{ injector: ɵcreateInjector(federated[this.exposedModule], this.injector) },
);

this.componentRef = componentRef;

if (this._props) {
this.updateComponentProps(this._props);
}
});
}

private updateComponentProps(props: Record<string, any>) {
if (props && this.componentRef) {
Object.entries(this._props).forEach(([key, value]) => {
this.componentRef.setInput(key, value);
});
}
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,22 @@
<div class="container">
<h2>Microfrontend Shell</h2>
<button (click)="someEvent()">Microfrontend Shell</button>
<p>Welcome to the Angular 15 Microfrontend demo using Webpack 5 Module Federation</p>
<p>This component is part of the shell application, the Profile component that is linked from the `Profile` link at
the top is a Microfrontend that is remotely loaded into the application. Check the network settings to see the
remote being loaded.</p>
<br />

<federated-component remoteEntry="http://localhost:4201/remoteEntry.js" remoteName="profile" exposedModule="ProfileModule" componentName="ListUserComponent"></federated-component>
Federated Component
<federated-component remoteEntry="http://localhost:4201/remoteEntry.js"
remoteName="profile"
exposedModule="ProfileModule"
componentName="ListUserComponent"
[props]="{ label: federatedLabel }"
></federated-component>
</div>

<div class="container">
Regular Component
<shell-profile-list-user [label]="'Regular Label 1'" foo="foo"></shell-profile-list-user>
<shell-profile-list-user [label]="federatedLabel" foo="foo"></shell-profile-list-user>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@ import { Component, OnInit } from '@angular/core';
styleUrls: ['./home.component.scss'],
})
export class HomeComponent implements OnInit {
federatedLabel = 'Federated Label';
counter = 0;
constructor() {}
ngOnInit(): void {}

someEvent() {
this.counter += 1;
this.federatedLabel = 'Federated Label' + this.counter;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<br />
<h1>{{ label }}</h1>
<h1>{{ foo }}</h1>
<h2>List users from the shared application state</h2>
<div *ngIf="users">
<table class="table table-striped">
<thead class="bg-primary text-white">
<tr>
<th>Name</th>
<th>Email</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let user of users | async">
<td>{{ user.name }}</td>
<td>{{ user.email }}</td>
<td>
<button (click)="removeUser(user)" class="btn btn-danger">Remove User</button>
</td>
</tr>
</tbody>
</table>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { NgxsModule } from '@ngxs/store';
import { ProfileComponent } from 'projects/mdmf-profile/src/app/profile/components/profile/profile.component';
import { User } from 'projects/mdmf-shared/src/lib/app-state/models/User';
import { UserState } from 'projects/mdmf-shared/src/lib/app-state/state/user.state';
import { MdmfSharedModule } from 'projects/mdmf-shared/src/lib/modules/mdmf-shared.module';
import { ListUserComponent } from './list-user.component';

describe('ListUserShellComponent', () => {
let component: ListUserComponent;
let fixture: ComponentFixture<ListUserComponent>;

beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [
ReactiveFormsModule,
FormsModule,
MdmfSharedModule,
NgxsModule.forRoot([UserState]),
],
declarations: [ListUserComponent],
}).compileComponents();
});

beforeEach(() => {
fixture = TestBed.createComponent(ListUserComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create the component', () => {
expect(component).toBeTruthy();
});

it('should render h2 element', () => {
const element = fixture.debugElement.nativeElement.querySelector('h2');
expect(element.textContent).toContain('List users from the shared application state');
});

it('should remove an User from the store', () => {
const user: User = { name: 'Mr. A', email: '[email protected]' };

// add User into the store
const profileComponent = TestBed.createComponent(ProfileComponent).componentInstance;
profileComponent.addUser(user.name, user.email);
expect(
component.getUsers().filter(u => u.name === user.name && u.email === user.email)[0],
).toEqual(user);

// remove the User from the store
component.removeUser(user);
expect(component.getUsers().length).toEqual(0);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import {
Component,
Input,
OnInit,
} from '@angular/core';
import { Select, Store } from '@ngxs/store';
import { RemoveUser } from 'projects/mdmf-shared/src/lib/app-state/actions/user.action';
import { User } from 'projects/mdmf-shared/src/lib/app-state/models/User';
import { UserState } from 'projects/mdmf-shared/src/lib/app-state/state/user.state';
import { Observable } from 'rxjs';

@Component({
selector: 'shell-profile-list-user',
templateUrl: './list-user.component.html',
styleUrls: ['./list-user.component.css'],
})
export class ListUserComponent implements OnInit {
@Select(UserState.getUsers) users: Observable<User[]>;

@Input() label = '';
@Input() foo = '';

constructor(private store: Store) {
console.log('Regular component Created');
}

ngOnInit(): void {}

/**
* Handle the remove user when the "Remove User" button is clicked
* @param user: the user info
*/
removeUser(user: User): void {
this.store.dispatch(new RemoveUser(user));
}

/**
* Get the users for unit testing purposes
*/
getUsers(): User[] {
return this.store.selectSnapshot<User[]>(state => state.users.users);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ import { ProfileRoutingModule } from './shell-routing.module';
import { HomeComponent } from './components/home/home.component';
import { FederatedComponent } from './components/federated/federated.component';
import { MdmfSharedModule } from 'projects/mdmf-shared/src/lib/modules/mdmf-shared.module';
import { ListUserComponent } from './components/list-user/list-user.component';

@NgModule({
declarations: [HomeComponent, FederatedComponent],
declarations: [HomeComponent, FederatedComponent, ListUserComponent],
imports: [CommonModule, ProfileRoutingModule, FormsModule, ReactiveFormsModule, MdmfSharedModule],
})
export class ShellModule {}