Home > Mobile >  Angular - Pass values from a form, into a Map data structure then use that data in a different compo
Angular - Pass values from a form, into a Map data structure then use that data in a different compo

Time:07-08

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:

  1. Subscribe to the this.appService.currentBrokerConnectionValues multiple times
  2. 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:

  1. AppComponent presents modal BrokerConnectComponent
  2. BrokerConnectComponent shows the reactive form
  3. When closing the modal use dialogRef (the reference you get when you do this.dialog.open(...)) to do dialogRef.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.
  4. Once you have this value, call mqtt.connect(...) right away from AppComponent 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.

  • Related