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.
Related
This is a Node app with OIDC login. I want to store the JWT in the cookie.
I'm using version 12 of the library 'angular-auth-oidc-client'. I can't use a later version of this library.
The documentation says to set up your app.module.ts like this:
imports: [
/*...*/,
AuthModule.forRoot({
config: {
authority: ...,
redirectUrl: ...,
/*...*/,
storage: new MyCustomStorage()
}
})
So it uses that 'storage' parameter.
This works correctly if I use, for instance, localStorage in my new MyCustomStorage. But I want to use the cookie.
So, if I for instance make my MyCustomStorage like this:
import { CookieService } from 'ngx-cookie-service';
#Injectable({
providedIn: 'root',
})
export class MyCustomStorage implements AbstractSecurityStorage {
constructor(private cookieService: CookieService) {
}
remove(key: string): void {
throw new Error('Method not implemented.');
}
clear(): void {
throw new Error('Method not implemented.');
}
read(key: string) {
let item = this.cookieService.get(key);
if (!!item) {
return JSON.parse(item);
}
else {
return null;
}
}
write(key: string, value: any) {
value = value || null;
//Expiration time can be set in the third parameter of below function.
this.cookieService.set(`${key}`, JSON.stringify(value), undefined, undefined, undefined, true, 'Strict');
return true;
}
}
I believe this would work... however I now get an error in my app.module.ts, because now creating a new MyCustomStorage() requires a CookieService parameter to be injected.
But that CookieService is automatically injected into the MyCustomStorage usually...
Do I need to manually instantiate a new CookieService in the "storage: new MyCustomStorage()" line? If so, it asks for parameters for DOCUMENT and PLATFORM_ID and REQUEST. Do I have to instantiate those as well? If yes... how do I create a new document and platform id for this new CookieService()?
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.
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
I'm calling a twitter API from angular and then node, but I'm getting an error that a property doesn't exist on type 'Object'. But it is showing up on the page properly.
I've tried renaming the property and looked through the tweet service that I have set up, but I can't seem to figure out what's going wrong.
import { Component, OnInit } from '#angular/core';
import { TweetService } from '../../services/tweet.service';
#Component({
selector: 'app-twitter-page',
templateUrl: './twitter-page.component.html',
styleUrls: ['./twitter-page.component.scss']
})
export class TwitterPageComponent implements OnInit {
tweetsArray;
constructor(
private twitter: TweetService
) { }
ngOnInit() {
this.twitter.getTweets().subscribe(tweet => {
this.tweetsArray = tweet.tweets[0];
console.log(this.tweetsArray);
});
}
}
Error:
ERROR in src/app/main-body/twitter-page/twitter-page.component.ts(20,32): error TS2339: Property 'tweets' does not exist on type 'Object'.
Here tweet is of the type Object which do not have attributes on it.
You should define an interface ITweet and cast tweet to this interface. This gives a shape to the tweet object and defines the set of attributes available on it.
tweet.ts -
export interface ITweet {
...
};
twitter-page.component.ts - (note - .subscribe((tweet:ITweet) => {}))
import { ITweet } from './tweet.ts';
.
.
.
export class TwitterPageComponent implements OnInit {
.
.
.
ngOnInit() {
this.twitter.getTweets().subscribe((tweet:ITweet) => {
this.tweetsArray = tweet.tweets[0];
console.log(this.tweetsArray);
});
}
}
Or alternatively you can just cast tweet to any (.subscribe((tweet:any) => {})). But this is not recommended since you will lose the benefit of type checking.
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.