How to share UI state in single-spa using RxJs? - micro-frontend

As per the single-spa official doc, we can share the application's UI state by using RxJs.
Observables / Subjects (RxJs) - one microfrontend emits new values to
a stream that can be consumed by any other microfrontend. It exports
the observable to all microfrontends from its in-browser module, so
that others may import it.
Link: https://single-spa.js.org/docs/recommended-setup/#ui-state
Link: https://single-spa.js.org/docs/faq/#how-can-i-share-application-state-between-applications
I was trying to create an example in React, where I am using single-spa parcel to include my micro-apps in root application. I was trying to share the UI state using RxJs.
When I googled it for single-spa RxJs, I didn't find anything. Can anyone provide me a basic example where I will be able to share UI state for below use cases:
Sharing the UI state from root app to my micro-apps.
Sharing the UI state from micro-apps to root apps.
Sharing the UI state between micro-apps.

Here is a high level overview on how to approach this:
add rxjs as a shared dependency in your import map
"rxjs": 'https://unpkg.com/#esm-bundle/rxjs/system/rxjs.min.js,
"rxjs/operators": 'https://unpkg.com/#esm-bundle/rxjs/system/rxjs-operators.min.js,
consider pinning these to a specific version!
create a utility module (create-single-spa makes this easy!) that sets up and exports the observable with data that you need
include this utility module in importmap too
import and subscribe to observable from the utility module in the apps that need it
don't forget to unsubscribe when your apps unmount.
celebrate 🎉
I have created single-spa-example-rxjs-shared-state as an example repo that shows how to use an Rxjs utility module with cross-frontend imports.

This does the trick
In root html js file add the following
Import { Subject, Subscription } from 'https://dev.jspm.io/rxjs#6/_esm2015';
import { filter, map } from 'https://dev.jspm.io/rxjs#6/_esm2015/operators';
export class EventBusService {
constructor() {this.subject$ = new Subject(); }
emit(event) {
this.subject$.next(event);
}
on(eventName, action) {
return this.subject$.pipe(
filter( (e) => e.name === eventName),
map( (e) => e["data"])).subscribe(action);
}
}
var EventBus= new EventBusService()`enter code here`;
System.import('single-spa').then(function (singleSpa) {
singleSpa.registerApplication(
'app1',
function () {
return System.import('app1');
},
function (location) {
return true;
// return location.pathname.startsWith('/app1');
},
{ EventBus: EventBus }
);
singleSpa.registerApplication(
'app2',
function () {
return System.import('app2');
},
function (location) {
return true
// return location.pathname.startsWith('/app2');
},
{ EventBus: EventBus }
)
singleSpa.start();
})
In component
import { Component,OnInit ,ChangeDetectorRef} from '#angular/core';
import { assetUrl } from 'src/single-spa/asset-url';
import { singleSpaPropsSubject, SingleSpaProps } from 'src/single-spa/single-spa-props';
import { Subscription } from 'rxjs';
#Component({
selector: 'app1-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
singleSpaProps: SingleSpaProps;
subscription: Subscription;
title = 'app1';
yoshiUrl = assetUrl("yoshi.png");
msgFromMicro="";
titleToPass="";
constructor(private ChangeDetectorRef:ChangeDetectorRef){
}
ngOnInit(): void {
this.subscription = singleSpaPropsSubject.subscribe(
props => {
this.singleSpaProps = props;
console.log(props);
this.lookForEvents();
}
);
}
lookForEvents(){
this.singleSpaProps['EventBus'].on('msgFrmMicro2',(data)=>{
this.msgFromMicro=data;
this.ChangeDetectorRef.detectChanges();
});
}
sendMsg(){
// alert(this.titleToPass);
debugger;
this.singleSpaProps['EventBus'].emit({name:'msgFrmMicro1',data:this.titleToPass});
}
ngOnDestroy(): void {
this.subscription.unsubscribe();
}
}
Take look at the following repo, handled the same scenario by passing observable ref to micro apps through customprops of single spa
https://github.com/SENTHILnew/micro_spa_intercom

Related

subscribing a http get call - why do I need to refresh my frontend page to get the data after it got altered?

So I have a web application built with angular on the frontend and node js on the backend. Im building an FAQ app where a user can add categories. See the photo below:
Those categories are stored in a pg database. When navigating to the page "Categories", I make a http call to my node js backend to get all categories from the database. My api looks like this:
app.get('/all-categories', async (req, res) => {
const result = await pool.query('select * from "Category"');
res.json(result.rows);
});
And following is my frontend, the categories.ts:
export class CategoriesComponent implements OnInit, OnDestroy {
categories: Category[] = [];
subscriptions = new Subscription();
constructor(private httpService: HttpService) { }
ngOnInit(): void {
const subscription = this.httpService.getAllCategories().subscribe(allCategories => {
this.categories = allCategories;
});
this.subscriptions.add(subscription);
}
ngOnDestroy(): void {
this.subscriptions.unsubscribe();
}
onDeleteButtonClick(categoryUUID: string) {
this.httpService.deleteCategoryByUUID(categoryUUID).pipe(
take(1)
).subscribe();
}
}
Now, whats the problem?
The problem is, when I navigate to the Categories page, all the categories are shown. But when I delete a category using the red delete button, I need to refresh the page in order to see the updated number of categories.
Why is this so? And what do I need to do to make it work?
I reckon to change the way of how you obtain the data from your service, you could subscribe to the observable in an asynchronous way with async pipe from the template, and when one category will be eliminated, you can update your observable.
on this way your .ts should be in this way:
export class CategoriesComponent implements OnInit {
categories$: Observable<Category[]> = [];
constructor(private httpService: HttpService) { }
ngOnInit(): void {
this.categories$ = this.httpService.getAllCategories();
}
async onDeleteButtonClick(categoryUUID: string) {
const deleteCategory = await this.httpService.deleteCategoryByUUID(categoryUUID).pipe(take(1))
.toPromise();
if(deleteCategory) {
this.categories$ = this.httpService.getAllCategories();
}
}
}
and your template something like this:
<div *ngIf="categories$ | async as categories">
<div *ngFor="let cat of categories">cat</div>
</div>
You can add tap operator in deleteCategoryByUUID, and filter categories:
this.categories = this.categories.filter(c => c !== c.uid);

NestJs: Import service from another module

I'm trying to use the surveyService in the voteOptionRepository, but when I use the route, the console return this: TypeError: this.surveyService.getSurveyById is not a function
This is my SurveyModule
#Module({
imports: [
TypeOrmModule.forFeature([SurveyRepository]),
AuthModule
],
controllers: [SurveyController],
providers: [SurveyService],
exports: [SurveyService]
})
export class SurveyModule{}
This is my voteOptionModule
#Module({
imports: [
TypeOrmModule.forFeature([VoteOptionRepository]),
AuthModule,
SurveyModule
],
controllers: [VoteOptionController],
providers: [VoteOptionService]
})
export class VoteOptionModule{}
And this is how I'm trying to use the service
#EntityRepository(VoteOption)
export class VoteOptionRepository extends Repository<VoteOption>{
constructor(private surveyService: SurveyService){
super();
}
async createVoteOption(createVoteOptionDTO: CreateVoteOptionDTO, surveyId: number, user: User){
const survey = await this.surveyService.getSurveyById(surveyId, user)
const { voteOptionName, image } = createVoteOptionDTO;
const voteOption = new VoteOption();
voteOption.voteOptionName = voteOptionName;
voteOption.image = image;
voteOption.survey = survey;
try{
await voteOption.save()
this.surveyService.updateSurveyVoteOptions(voteOption, surveyId, user)
} catch(error){
throw new InternalServerErrorException();
}
delete voteOption.survey;
return voteOption;
}
}
TypeORM repository classes do not adhere to Nest's Dependency Injection System, due to being tied TypeORM. Repository classes are actually supposed to have connections and entity managers passed to them in the constructor to allow for communication with the database. If you need logic from other services, you should generally be calling those other services from inside the service call, not the repository.
I'm new to NestJS, but I believe this is what you want to do. You need to inject the reference to your table in your service.
export class VoteOptionRepository extends Repository<VoteOption>{
constructor(
private surveyService: SurveyService
#InjectRepository
surveyServiceRepo(SurveyRepository)
){
super();
}
async getSurveyById () {
const survey = await this.surveyServiceRepo.find({id: 'survey-id'})
return survey
}
}
Without seeing your SurveyService though, it may be that getSurveyById has not been defined properly, which is why your getting an error that it's not a function.

Nestjs CQRS - CommandHandlerNotFoundException

I am trying to use events in my nestjs app.
However when I attempt to trigger command, I get CommandHandlerNotFoundException.
I have message-bus.module:
#Module({
imports: [CqrsModule],
providers: [
MessageBusLocalService,
StartWorkflowHandler
],
exports: [MessageBusLocalService]
})
export class MessageBusModule {
}
message-bus-local.service
#Injectable()
export class MessageBusLocalService {
constructor(private readonly commandBus: CommandBus, private eb: EventBus) {
}
startWorkflow(workflowId: string, payload: any) {
return this.commandBus.execute(
new StartWorkflowCommand(workflowId, payload)
);
}
}
and start-workflow.handler
#CommandHandler(StartWorkflowCommand)
export class StartWorkflowHandler implements ICommandHandler<StartWorkflowCommand> {
constructor() {}
async execute(command: StartWorkflowCommand) {
console.log('Workflow started', command.jobId);
return true;
}
}
I am trying to trigger command when app is bootstrapped:
const app = await NestFactory.create(ApplicationModule);
const service = app.get(MessageBusLocalService);
try {
const c = await service.startWorkflow('abcde', {just: "test"});
console.log('And returned', c);
} catch (e) {
console.error(e)
}
and... I get the CommandHandlerNotFoundException there although I believe it is declared... What did I do wrong?
Thanks in advance.
Since you want to use the handler in another module (e.g. appModule ) you need to add the #Injectable() decorator to the StartWorkflowHandler class.
And call app.init() in the main.ts. Before starting the application.
Your MessageBusModule does not re-export handlers, thus they are not "visible" on app.module level (at least this is what I understand on my own)
I got similar scenario like that:
const commands = [NewOrder, ChargeForOrder]
const events = [ChargeOrder, OrderProcessed]
const sagas = [AdjustWalletFunds]
#Module({
imports: [
CqrsModule,
WalletsModule,
TypeOrmModule.forFeature([...]),
],
providers: [...commands, ...events, ...sagas],
exports: [CqrsModule, ...commands, ...events, ...sagas],
})
export class RxModule {}
so, assuming you import your MessageBusModule in the main app.module, try the following:
#Module({
imports: [CqrsModule],
providers: [
MessageBusLocalService,
StartWorkflowHandler
],
exports: [MessageBusLocalService, StartWorkflowHandler]
})
export class MessageBusModule {
}
As it turns out, the method I used in the question does not work.
However it does work as expected if I inject the MessageBusLocalService in controller.
It seems odd to answer my own question but it might help someone eventually.
Ensure you imported have CqrsModule imported. Also import the MessageBusModule into any other module where it is called from.

React-native and Redux healthy way to call actions on props change

I've been using react-native with redux for a while, and the way i learn to call actions when something change on prop is using the componentWillReceiveProps, but when I use it I need to pass between if's and some times it goes to the wrong if, then I need to add more stuff to prevent it.
Here's an example I have done. I know this is not the best way to do it, but it is what I could think of.
componentWillReceiveProps(newProps) {
if(Object.keys(newProps.selected_product).length > 0) {
if(Object.keys(this.props.current_location).length > 0 || Object.keys(newProps.current_location).length > 0) {
this._handleNextPage(2);
this.props.verifyProductById(newProps.selected_product, newProps.current_location, this.props.token);
} else {
this.props.statusScanner(false);
this._handleNextPage(1);
}
} else if(Object.keys(newProps.historic_product_confirm).length > 0) {
if(newProps.historic_product_confirm.location._id == newProps.current_location._id)
this.props.handleModalConfirmPrice(!this.props.modal_confirmPrice_status)
} else if(newProps.scanResult != "") {
this.props.statusScanner(false);
if(Object.keys(newProps.current_location).length > 0) {
this._handleNextPage(2);
} else {
this._handleNextPage(1);
}
} else {
this._handleNextPage(0);
}
}
What I need is a healthy way to call my actions when the props change.
Edit:
Here i have the full OfferScene and an action file example:
OfferScene:
https://gist.github.com/macanhajc/0ac98bbd2974d2f6fac96d9e30fd0642
UtilityActions:
https://gist.github.com/macanhajc/f10960a8254b7659457f8a09c848c8cf
As mentioned in another answer, componentWillReceiveProps is being phased out, so I would aim for trying to eliminate it where possible. You'll be future-proofing your code and keeping your component logic more declarative and easy to reason about. As someone who has been responsible for (and been frustrated by) lifecycle method abuse like this, here are some things that have helped me.
Remember that when using redux-thunk, along with passing dispatch as the first argument, you can also pass getState as the second. This allows you to access state values in your action logic instead of bringing them into your component's props and adding clutter. Something like:
export const ExampleAction = update =>
(dispatch, getState) => {
const { exampleBool } = getState().ExampleReducer
if (exampleBool) {
dispatch({
type: 'UPDATE_EXAMPLE_STATE',
update
})
}
}
Using async/await in action logic can be a lifesaver when your action depends upon fetched results from an API call:
export const ExampleAction = () =>
async (dispatch, getState) => {
const { valueToCheck } = getState().ExampleReducer
, result = await someAPICall(valueToCheck)
.catch(e => console.log(e))
if (result.length > 0) {
dispatch({
type: 'UPDATE_EXAMPLE_STATE',
update: result
})
}
}
For cases where your component's rendering behavior depends upon certain state values after your state has been updated, I highly recommend reselect. A very basic example would be something like:
component.js
import React, { Component, Fragment } from 'react'
import { connect } from 'react-redux'
import { shouldDisplayItems } from '../selectors'
import MyListviewComponent from './myListview'
class ItemList extends Component {
render() {
const { shouldDisplayItems, items } = this.props
return (
<>
{shouldDisplayItems && <MyListviewComponent items={items} />}
</>
)
}
}
const mapStateToProps = ({ ListItems }) => shouldDisplayItems(ListItems)
export default connect(mapStateToProps)(ItemList)
selectors.js:
(Assuming your ListItems reducer has the params items and visibilityFilter)
import { createSelector } from 'reselect'
export const shouldDisplayItems = createSelector(
[state => state],
({ items, visibilityFilter }) => {
return {
shouldDisplayItems: visibilityFilter && items.length > 0,
items
}
}
)
I should mention that another option would be using higher-order components, but it can be tricky to use this approach before having a good grasp on how to keep too much imperative logic out of your components (I learned this the hard way).
I agree with #AnuragChutani and #Goldy in terms of clarity of the code; break it down some more into more components or functions.
Now after some review of your componentWillReceiveProps function, it is definitely not specific enough to narrow down exactly which prop changes. If any connected redux variable changes, the componentWillReceiveProps function will be invoked each time.
So e.g. if 'token' or 'selected_product' updates, componentWillReceiveProps will be triggered, even though you did not want it to trigger for token updates.
You can use a comparison for a specific variable update in the props.
E.g Using lodash
if(!_.isEqual( nextProps.selected_product, this.props.selected_product ))
// if props are different/updated, do something
Secondly, you can call actions/callbacks in your actions to narrow down navigation.
E.g.
takePicture = (camera, options){
...
//on success
dispatch(handleModalConfirmPrice())
...
}}

Cant show name of logged-in user - angular2 -meteor

I just want to show the name of the current logged in user, but I cant make it works.
I wrote this on the app.component:
import { Component } from '#angular/core';
import template from './app.component.html';
import {ROUTER_DIRECTIVES} from '#angular/router';
import {LoginButtons} from 'angular2-meteor-accounts-ui';
//import our Carousel Component
import {CSSCarouselComponent} from './imports/componenets/carousel/carousel.component';
import { InjectUser } from 'angular2-meteor-accounts-ui';
#Component({
selector: 'app',
template,
directives: [ROUTER_DIRECTIVES, LoginButtons,CSSCarouselComponent]
})
#InjectUser('user')
export class AppComponent {
user: Meteor.User;
constructor() {
console.log(this.user);
}
loginFacebook(event) {
Meteor.loginWithFacebook({}, function(err){
if (err) {
throw new Meteor.Error("Facebook login failed");
}
console.log(Meteor.user().profile.name;);
});
}
}
console.log(this.user); returns undefined.
console.log(Meteor.user().profile.name;); works and gives me the name, but I have no success to export it to the html and show that.
You have to reference Meteor in your Component, for example as let M = Meteor;. Then you can use {{M.user().profile.name}} in your html.
Also this.user is never set in your code, you just define its class. Anyway, you should always use Meteor.user() or M.user(), because it's always up-to-date.

Resources