Likert visualisations (Part 2 - Working with the @ngrx store)

Posted on 2017-09-07 16:05:57

In part 1 we set up the application and created our @ngrx actions and reducers. In this post I will explain how to incorporate the store we created into the application.

We’re going to create a component which will contain our data input form. Do using the @angular/cli tool by running ng generate component data-input. This will automatically boilerplate your component and add it to your app module. We also need to import the reducer from part 1 into the module along with the @ngrx store and store module. Add the following lines to the top of the app.module.ts file:

import { Store, StoreModule } from '@ngrx/store';
import { reducers } from "./state-management/reducers";

And the following to the imports definition in the same file:

  imports: [
    /* ... */
    StoreModule.forRoot(reducers)
  ],

Our data input component needs to do a number of things. Users will need to define how many possible responses there are per question. They may also want to add labels to those possible responses. They’ll need to be able to create, update and delete response objects and finally we are going to give them 2 possible ways of visualising the data. Either as a bubble chart or as a set of horizontal stacked bars so we will need a toggle to switch between those two types.

The component constructor function will define our store and then subscribe to each of the objects in the store. We’ll then add some event listeners that will call the actions defined in part one to update the store. The html will be a form styled with bootstrap version 4. Our data-input.component.ts looks like this:

import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { Store } from '@ngrx/store';
import { Response } from "../models/response";

import * as main from '../state-management/actions/main-actions';
import * as fromRoot from '../state-management/reducers';

@Component({
  selector: 'app-data-input',
  templateUrl: './data-input.component.html'
})
export class DataInputComponent implements OnInit {

    labels: string[];
    responses: Response[];
    numberResponses: number;
    chartType: string;

    constructor(
        private store: Store<fromRoot.State>
    ) {
        store.select(fromRoot.getLabels)
            .subscribe (labels => {
                this.labels = labels;
            })
        store.select(fromRoot.getResponses)
            .subscribe(responses => {
                this.responses = responses;
            })
        store.select(fromRoot.getNumberResponses)
            .subscribe(numberResponses => {
                this.numberResponses = numberResponses;
            })
        store.select(fromRoot.getChartType)
            .subscribe(chartType => {
                this.chartType = chartType;
            })
    }

    ngOnInit() {
        this.addResponse();
    }

    trackByIndex(index: number, obj: any): any {
        return index;
    };

    increaseNumberResponses(){
        this.store.dispatch(new main.IncreaseNumberResponses());
    };

    decreaseNumberResponses(){
        this.store.dispatch(new main.DecreaseNumberResponses());
    };

    updateLabels(){
        this.store.dispatch(new main.UpdateLabels(this.labels));
    };

    removeResponse(response){
        this.store.dispatch(new main.RemoveResponse(response));
    };

    updateResponse(response){
        this.store.dispatch(new main.UpdateResponse(response));
    };

    addResponse(){
        this.store.dispatch(new main.AddResponse());
    };

    updateChartType(newType){
        this.store.dispatch(new main.UpdateType(newType));
    };

}

And the html:

<div class="card mt-4">
  <div class="card-body">
    <h4 class="card-title">Questions</h4>
  </div>
  <div class="card-body" *ngFor="let response of responses; let index = index; trackBy:trackByIndex;">
    <div class="row">
      <div class="col-12 form-group">
        <label class="col-form-label">Question text</label>
        <input class="form-control" 
          [(ngModel)]="responses[index].question"
          (change)="updateResponse(response)" 
          name="response-question-{{index}}" 
          placeholder="Question">
      </div>
      <div class="col-12">
        <label class="col-form-label">Question responses</label>
        <div class="row">
          <div class="col-sm-4 col-md-3 col-lg-2 form-group" *ngFor="let r of response.responses; let rindex = index;trackBy:trackByIndex;">
            <input class="form-control" 
              type="number" 
              [(ngModel)]="responses[index].responses[rindex]" 
              (change)="updateResponse(response)" 
              name="repsonse-{{index}}-r-{{rindex}}" 
              placeholder="0" min="0">
          </div>
        </div>
      </div>
      <div class="col-12 form-group">
        <button type="button" class="btn btn-outline-danger" (click)="removeResponse(response)">Delete question</button>
      </div>
    </div>
  </div>
  <div class="card-body">
    <div class="form-group">
      <button type="button" class="btn btn-outline-secondary" (click)="addResponse()">Add question</button>
    </div>
  </div>
</div>
<div class="card mt-4">
  <div class="card-body">
    <h4 class="card-title">Labels</h4>
    <div class="row">
      <div class="col-sm-6 col-md-4 col-lg-3 form-group" *ngFor="let label of labels; let index = index;trackBy:trackByIndex;">
        <input class="form-control" 
          [(ngModel)]="labels[index]" 
          (change)="updateLabels()" 
          name="label-{{index}}" 
          placeholder="Label {{index+1}}">
      </div>
    </div>
  </div>
</div>
<div class="card mt-4">
  <div class="card-body">
    <h4 class="card-title">Number of Responses</h4>
    <div class="form-group">
      <button type="button" class="btn btn-outline-secondary" (click)="decreaseNumberResponses()">Remove response</button>
      <button type="button" class="btn btn-outline-secondary" (click)="increaseNumberResponses()">Add response</button>
    </div>
  </div>
</div>
<div class="card mt-4">
  <div class="card-body">
    <h4 class="card-title">Chart Type</h4>
    <div class="form-group">
      <button type="button" 
        class="btn btn-outline-secondary" 
        [ngClass]="{'active': chartType == 'bubbles'}" 
        (click)="updateChartType('bubbles')">Bubbles</button>
      <button type="button" 
        class="btn btn-outline-secondary" 
        [ngClass]="{'active': chartType == 'bars'}" 
        (click)="updateChartType('bars')">Stacked Bars</button>
    </div>
  </div>
</div>

Now that we have our data management component we can use the data to render our charts using ds.js. We’ll do this in part 3.


Comments