The repo is here.
The commit for this part of the tutorial is here :)
It is time for our final part, in which we will add functionality to the React
app we have, so it can determine if it is a standalone app and use its own means of fetching activities, or if it is a part of something else, and delegate the request to the parent window.
The key point here is our use-activity-provider
module. The only thing this folder exports to the rest of the application is the useActivityProvider
hook, which returns a function, which corresponds to GetActivity
interface. The rest is concealed under the hood of the module. What that means is that we simply need to craft another function for communicating with the parent window, which would correspond to GetActivity
interface and then return it from our useActivityProvider
hook in cases when our React
app detects it is inside an iframe.
Sounds simple, right?
In order to do that we will need two more hooks inside use-activity-provider
module, which will be working under its hood. The first one will do nothing but receiving messages which come down from the parent window, and the other one will serve as an adapter to pipe these messages to the familiar GetActivity
interface, which the rest of the application is expected.
Finally, useActivityProvider
will be granted the logic to tell if the app is standalone or inside an inframe, and will get to pick which one of the two functions returning Promise
to provide to the application.
These two new hooks deserve a module of their own, since they encapsulate a good chunk of logic, so we'll be placing them inside use-ask-for-activity
folder in use-activity-provider
.
We'll start with the simpler hook, the one which receives activities from messages:
npm run nx -- g @nrwl/react:hook use-activity-from-message --directory=app/use-activity-provider/use-ask-for-activity --project=react-app --skipTests=true --export=false --flat
Now let's populate the file with logic. We will utilize useEffect
, useCallback
and useState
hooks:
// ./apps/react-app/src/app/use-activity-provider/use-ask-for-activity/use-activity-from-message.ts
import { useState, useCallback, useEffect } from 'react';
import { ActivityItem } from '@demo--nx-iframe-microfrontends/models';
export function useActivityFromMessage(): ActivityItem | null {
const [activity, setActivity] = useState<ActivityItem | null>(null);
const logMessage = useCallback((event: { data: ActivityItem }) => {
setActivity(event.data);
}, []);
useEffect(() => {
window.addEventListener('message', logMessage);
return () => {
window.removeEventListener('message', logMessage);
};
}, [logMessage]);
return activity;
}
Looks fairly straightforward, doesn't it? We add a listener and every time activity comes down (for the sake of simplicity we are not performing any checks here, i.e. if it is really ActivityItem
, etc.), we throw it into useState
and send it further to whoever is using the hook. This hook has no idea how the acitvity is further delivered and that's the marvel of it.
Now we need our last hook, which will provide means for requesting activity from the parent window and return the result which it will obtain from our recently created useActivityFromMessage
.
I suck at naming, so I will call it useAskForActivity
:)
npm run nx -- g @nrwl/react:hook use-ask-for-activity --directory=app/use-activity-provider/use-ask-for-activity --project=react-app --skipTests=true --export=false --flat
This one is going to be a bit more tricky: we will need it to return a promise, but we would have to manually resolve it with the result coming from useActivityFromMessage
. Luckily we can easily obtain a reference to resolve
of a Promise
and keep it preserve using useRef
hook :)
// ./apps/react-app/src/app/use-activity-provider/use-ask-for-activity/use-ask-for-activity.ts
import { useEffect, useRef } from 'react';
import { ActivityItem } from '@demo--nx-iframe-microfrontends/models';
import { GetActivity } from '../../models/get-activity.model';
import { useActivityFromMessage } from './use-activity-from-message';
export function useAskForActivity(): GetActivity {
const activity = useActivityFromMessage();
const megares = useRef<(activity: ActivityItem) => void>();
useEffect(() => {
if (activity) {
activityResolver.current?.(activity);
}
}, [activity]);
return (): Promise<ActivityItem> => {
window.parent.postMessage(
{
message: 'plz give some activity, bro?',
},
'*'
);
return new Promise<ActivityItem>((res) => {
activityResolver.current = res;
});
};
}
So as you see when the returned function is invoked by a consumer, it will message parent window, create a new Promise
, store its resolve
to useRef
resolver and trigger it once activity comes from useActivityFromMessage
!
All what's left is to tweak useActivityProvider
to determine whether our app is standalone or iframe
, we could use window location for the check and then return the correct version of GetActivity
implementation:
// ./apps/react-app/src/app/use-activity-provider/use-activity-provider.ts
import { GetActivity } from '../models/get-activity.model';
import { fetchActivity } from './fetch-activity.util';
import { useAskForActivity } from './use-ask-for-activity/use-ask-for-activity';
export function useActivityProvider(): GetActivity {
const askForActivity = useAskForActivity();
const isStandaloneApplication = window.location === window.parent.location;
return isStandaloneApplication ? fetchActivity : askForActivity;
}
So now you have it, http://localhost:4201/
run Angular
application with React
inside an iframe requesting Angular
to do http requests, and at the same time there's a standalone React
app http://localhost:4200/
which functions independently.
Cool, eh? :)
And that's how this series ends :>