I have a form that asks a user to fill out some details regarding connecting to an MQTT broker. I use a component that is a modal dialog for this.
Inside that component I then pass that form data into a Map data structure, which is then passed to my appService so I can extract that data to be used in another component. The form data is passed when I click my button on the form, and the connectToBroker()
function is called.
Here is the code for my form modal dialog component:
export class BrokerConnectComponent
{
constructor(private Form: FormBuilder, private mqtt: MqttService, private dialogRef: MatDialogRef<BrokerConnectComponent>, private appService: AppService) { }
BrokerConnectForm = this.Form.group({
brokerName: [''],
brokerUsername: [''],
brokerPassword: [''],
brokerAddress: [''],
brokerPort: ['']
});
brokerFormValues = new Map<string, any>();
connectToBroker()
{
this.brokerFormValues
.set('brokerName', this.BrokerConnectForm.get('brokerName')?.value)
.set('brokerUsername', this.BrokerConnectForm.get('brokerUsername')?.value)
.set('brokerPassword', this.BrokerConnectForm.get('brokerPassword')?.value)
.set('brokerAddress', this.BrokerConnectForm.get('brokerAddress')?.value)
.set('brokerPort', this.BrokerConnectForm.get('brokerPort')?.value)
this.appService.setBrokerConnectionValues(this.brokerFormValues);
this.dialogRef.close();
}
}
This is the service I'm using to pass data between components:
export class AppService
{
private connectionStatusSource = new Subject<number>();
currentConnectionStatus = this.connectionStatusSource.asObservable();
private brokerConnectionValuesSource = new Subject<Map<string, any>>();
currentBrokerConnectionValues = this.brokerConnectionValuesSource.asObservable();
constructor() {}
setConnectionStatus(status: number)
{
this.connectionStatusSource.next(status);
}
setBrokerConnectionValues (values: Map<string, any>)
{
this.brokerConnectionValuesSource.next(values);
}
}
And finally the other component that retrieves the data from the service:
export class AppComponent implements OnInit, OnDestroy
{
brokerConnectionDetails = new Map<string, any>();
brokerUsername!: string;
brokerPassword!: string;
brokerAddress!: string;
brokerPort!: number;
subscription!: Subscription;
constructor(private appService: AppService, private mqtt: MqttService) {}
ngOnInit(): void
{
this.appService.currentBrokerConnectionValues.subscribe(values => this.brokerAddress = values.get('brokerAddress'))
this.appService.currentBrokerConnectionValues.subscribe(values => this.brokerUsername = values.get('brokerUsername'))
this.appService.currentBrokerConnectionValues.subscribe(values => this.brokerPassword = values.get('brokerPassword'))
this.appService.currentBrokerConnectionValues.subscribe(values => this.brokerPort = values.get('brokerPort'))
this.appService.currentBrokerConnectionValues.subscribe(values => this.brokerConnectionDetails = values);
this.brokerPort = this.brokerConnectionDetails.get('brokerPort');
this.mqtt.connect({
hostname: this.brokerAddress,
username: this.brokerUsername,
password: this.brokerPassword,
port: this.brokerPort});
}
}
I've tried a few ways of passing this Map data into variables in this component, and I'm able to print out the inputted form data into the html template of this component. But when I try to pass this data to my mqtt.connect function it doesn't work at all.
Any help would be appreciated.
CodePudding user response:
The problem with this code is that you assume that
this.appService.currentBrokerConnectionValues.subscribe(values => this.brokerConnectionDetails = values);
and
this.mqtt.connect({
hostname: this.brokerAddress,
username: this.brokerUsername,
password: this.brokerPassword,
port: this.brokerPort});
work in sequence.
When AppComponent
is loaded, it will execute the ngOnInit() function when the component is initialized. During that call, it will:
- Subscribe to the
this.appService.currentBrokerConnectionValues
multiple times - call
this.mqtt.connect(...)
Note that an RxJS subscription will work only when something is pushed into the stream you are subscribing to.
Instead, what you would want to do here is:
ngOnInit() {
this.appService.currentBrokerConnectionValues
.subscribe((values) => {
this.mqtt.connect({
hostname: values.get('brokerAddress'),
username: values.get('brokerUsername'),
password: values.get('brokerPassword'),
port: values.get('this.brokerPort'),
});
});
}
This is just a modification to the code you have posted. I would not recommend taking this approach however.
I would recommend the following, instead:
AppComponent
presents modalBrokerConnectComponent
BrokerConnectComponent
shows the reactive form- When closing the modal use
dialogRef
(the reference you get when you dothis.dialog.open(...)
) to dodialogRef.afterClosed().subscribe(...)
to set a return value of the modal. Here, set the form values (or the map value) as the return value. See the basic Angular Material Dialog implementation on https://material.angular.io/components/dialog/overview if you don't know how to do it. - Once you have this value, call
mqtt.connect(...)
right away fromAppComponent
itself.
Of course, I do not have a full idea of your existing code or what other contraints you have. But for a basic application this should be a good enough approach.
CodePudding user response:
First of all, you don't need to subscribe for every property... It's enough to subscribe only once.
private subscription = new Subscription();
ngOnInit(): void {
this.subscription.add(
this.appService.currentBrokerConnectionValues.subscribe((values) => {
this.brokerAddress = values.get("brokerAddress");
this.brokerUsername = values.get("brokerUsername");
this.brokerPassword = values.get("brokerPassword");
this.brokerPort = values.get("brokerPort");
this.brokerConnectionDetails = values;
this.mqtt.connect({
hostname: this.brokerAddress,
username: this.brokerUsername,
password: this.brokerPassword,
port: this.brokerPort,
});
})
);
}
Secondly, you need to manage the subscription, as you are subscribing to a hot observable, so besides tracking the subscription, you also need to make sure you unsubscribe when your component gets destroyed:
ngOnDestroy(): void {
this.subscription.unsubscribe();
}
And last, but not least, don't use plain Subject
for your brokerConnectionValuesSource
; use a BehaviorSubject
instead. This one emits the last value to the new subscribers and helps you circumvent a lot of issues related to mismatches between when you subscribe and when your subjects emit data.