简体   繁体   中英

Updating Ngrx store from material combo-box and then binding to the returned ngrx store data

I am just starting to get to grips with Ngrx. I have wired up various controls to update and then return data from my store. Text boxes and check boxes work as expected, however combo boxes are a different storey. I can't get the binding right.

Here are the issues I face:

1) When I update the value in the combo box my setCurrentBadge function is fired twice

2) The value I have selected from the list in the combo-box called <mat-select class="badge-codes-combobox"> is not visible.

3) The getCurrentBadge function fires, even though this is located in the ngInit life-cycle hook and the page has not reloaded.

4) When I reload the page the getCurrentBadge function fires but the combo-box does not display the returned value.

As far as the code goes I need the badgeCodeSelected($event) to fire just once when the value of the combo box changes. I need the [value]="selectedBadge" to display the selected value and when the page is reloaded I need the combo-box to display the value returned from the store)

Here's my code.

    <div class="row">
  <div class="col-md-4">
    <app-declaration-type
    [errorMessage] = "errorMessage$ | async"
    [displayTypes] = "displayTypes$ | async"
    [declarationTypes] = "declarationTypes$ | async"
    [badges] = "badges$ | async"
    [traderReference]= "traderReference$ | async"
    [selectedBadge] = "selectedBadge$ | async"
    (badgeSelected) = "badgeCodeSelected($event)" 
    (checked) = "checkChanged($event)"
    (traderReferenceSet) = "onBlurTraderReferenceChange($event)"
    >
    </app-declaration-type>
  </div>
</div>

@Component({
  selector: 'app-declaration',
  templateUrl: './declaration-shell.component.html',
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class DeclarationShellComponent implements OnInit {
  errorMessage$: Observable<string>;
  displayTypes$: Observable<boolean>;
  declarationTypes$: Observable<Declarationtype[]>;
  badges$: Observable<Badge[]>;
  selectedDeclarationType$: Observable<string>;
  selectedBadge$: Observable<Badge>;
  traderReference$: Observable<string>;

  constructor(private store: Store<fromDeclaraionType.State>) {}

  ngOnInit() {
    this.store.dispatch(new fromDeclarationTypeActions.LoadDeclarationType());
    this.store.dispatch(new fromDeclarationTypeActions.LoadBadges());

    this.errorMessage$ = this.store.pipe(select(fromDeclaraionType.getError));
    this.displayTypes$ = this.store.pipe(
      select(fromDeclaraionType.getToggleDeclarationTypes)
    );
    this.declarationTypes$ = this.store.pipe(
      select(fromDeclaraionType.getDeclarationTypes)
    );

    this.selectedDeclarationType$ = this.store.pipe(
      select(fromDeclaraionType.getCurrentDeclarationType)
    );

    this.badges$ = this.store.pipe(select(fromDeclaraionType.getBadges));

    this.selectedBadge$ = this.store.pipe(
      select(fromDeclaraionType.getCurrentBadge),
      tap(x => console.log('About to fetch current badge from store {0}', x))
    );

    this.traderReference$ = this.store.pipe(
      select(fromDeclaraionType.getTraderReference)
    );
  }

  checkChanged(value: boolean): void {
    console.log('About to dispatch toggle Display Declaration Types');
    this.store.dispatch(
      new fromDeclarationTypeActions.ToggleDeclarationTypes(value)
    );
  }

  onBlurTraderReferenceChange(value: string) {
    console.log('About to dispatch Set Trader Reference');
    this.store.dispatch(
      new fromDeclarationTypeActions.SetTraderReference(value)
    );
  }

  badgeCodeSelected(value: Badge) {
    console.log('About to dispatch Set Current Badge');
    console.log(value);
    this.store.dispatch(new fromDeclarationTypeActions.SetCurrentBadge(value));
  }
}

 <div class="declaration-type">
  <mat-accordion>
    <mat-expansion-panel
      [expanded]="displayTypes === true"
      (opened)="(displayTypes === true)"
    >
      <mat-expansion-panel-header
        [collapsedHeight]="customCollapsedHeight"
        [expandedHeight]="customExpandedHeight"
      >
        <mat-panel-title> <h4>Declaration Type</h4> </mat-panel-title>
        <label>
          <input
            class="form-check-input"
            type="checkbox"
            (change)="checkChanged($event.target.checked)"
            [checked]="displayTypes"
          />
          Display Types?
        </label>
      </mat-expansion-panel-header>

      <div class="controls-wrapper">
        <div class="flex-container">
          <div class="flex-item-declarationType">
            <label class="field-label labelAlignment">
              Decln Type [01]:
              <mat-select class="declaration-type-combobox" [value]="selectedDeclarationType">
                <mat-option
                  *ngFor="let declarationType of declarationTypes"
                  [value]="declarationType?.value"

                >
                  {{ declarationType.value }}
                </mat-option>
              </mat-select>
            </label>
          </div>
          <div class="flex-item-badgeCode">
            <label class="field-label labelAlignment">
              Badge Codes:
              <mat-select class="badge-codes-combobox" [value]="selectedBadge" >
                <mat-option (onSelectionChange)="badgeCodeSelected(badge)"
                  *ngFor="let badge of badges"
                  [value]="badge?.code">
                 <div>{{ badge.code }} - {{ badge.name }}</div>
                </mat-option>
              </mat-select>
            </label>
          </div>
        </div>
        <div class="flex-container">
          <div class="flex-item-traderReference">
            <label class="field-label labelAlignment">
              Trader Reference [07]:
              <input
                matInput
                type="text"
                [(ngModel)]="traderReference"
                class="trader-reference-inputBox"
                (blur)="onTraderReferenceSet(traderReference)"
              />
              <button
                mat-button
                *ngIf="traderReference"
                matSuffix
                mat-icon-button
                aria-label="Clear"
                (click)="traderReferenceValue = ''"
              >
                <mat-icon>close</mat-icon>
              </button>
            </label>
          </div>
        </div>
      </div>
    </mat-expansion-panel>
  </mat-accordion>
</div>

@Component({
  selector: 'app-declaration-type',
  templateUrl: './declaration-type.component.html',
  styleUrls: ['./declaration-type.component.scss']
})
export class DeclarationTypeComponent implements OnInit {
  customCollapsedHeight = '40px';
  customExpandedHeight = '40px';

  @Input() errorMessage: string;
  @Input() displayTypes: boolean;
  @Input() declarationTypes: Declarationtype[];
  @Input() badges: Badge[];
  @Input() selectedDeclarationType: string;
  @Input() selectedBadge: Badge;
  @Input() traderReference: string;

  @Output() checked = new EventEmitter<boolean>();
  @Output() declarationTypeSelected = new EventEmitter<string>();
  @Output() badgeSelected = new EventEmitter<Badge>();
  @Output() traderReferenceSet = new EventEmitter<string>();

  constructor() {}

  ngOnInit(): void {}

  checkChanged(value: boolean): void {
    this.checked.emit(value);
  }

  badgeCodeSelected(value: Badge) {
    this.badgeSelected.emit(value);
  }

  onTraderReferenceSet(value: string) {
    this.traderReferenceSet.emit(value);
  }
}

This would be a bit of a shift for you, but I wanted to share that I have a library that, among other things, has built-in directives to bind form controls to a store. The big shift is that you'll need to build a StoreObject instead of using a selector, and you won't write or handle any actions.

The library is called ng-app-state , and the key to the binding to form controls is in the nasModel directive. I'll give you an idea below.

Let's say the value for your checkbox is in the store at a place like this:

class DeclarationTypeState {
  badgeCode: string;
}

Then you'd use it like this:

@Component({
  template: `
    <mat-select [nasModel]="badgeCodeStore">
      <mat-option ...></mat-option>
    </mat-select>
  `,
})
export class DeclarationTypeComponent {
  badgeCodeStore: StoreObject<string>;

  constructor(myStore: MyStore) {
    this.badgeCodeStore = myStore('keyForDeclarationTypeState')('badgeCode');
  }
}

This will do the 2-way binding into your store to make the mat-select stay in sync with the store, and edit the value in the store when the user changes it.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM