Step 3: Upgrade Buttons and Inputs


In this step you’ll change many of the controls in the app, using these components:

  • <material-toggle>
  • <material-fab>
  • <material-checkbox>
  • <material-radio> and <material-radio-group>

These controls appear in two custom components: <lottery-simulator> and <settings-component>.

Run the live example (view source) of the 3-usebuttons version of the app.

Use material-toggle

Edit lib/lottery_simulator.html to convert the “Go faster” <div> (and its children) into a <material-toggle>, as the following diff shows:

{2-starteasy → 3-usebuttons}/lib/lottery_simulator.html
51
- <div class="controls__faster-button">
52
- <label>
53
- <input #fastCheckbox type="checkbox"
54
- (change)="fastEnabled = fastCheckbox.checked">
55
- Go faster
56
- </label>
57
50
</div>
51
+ <material-toggle class="controls__faster-button"
52
+ label="Go faster"
53
+ [(checked)]="fastEnabled">
54
+ </material-toggle>

Here’s the resulting UI:

tiny but attractive toggle button

The class behind <material-toggle>, MaterialToggleComponent, defines label and checked attributes. The label attribute contains the main text for the toggle, which the app previously specified in the <label> element. A two-way binding to the checked property simplifies setting the toggle’s state.

Use material-fab

Now convert the buttons that have icons into floating action buttons (FABs).

  1. Edit lib/lottery_simulator.html.

  2. Convert the Play button from a <button> to a <material-fab> (MaterialFabComponent), adding the raised attribute and changing (click) to (trigger):

    {2-starteasy → 3-usebuttons}/lib/lottery_simulator.html
    30
    - <button (click)="play()"
    30
    + <material-fab raised (trigger)="play()"
    31
    31
    [disabled]="endOfDays || inProgress"
    32
    32
    id="play-button"
    33
    33
    aria-label="Play">
    34
    34
    <material-icon icon="play_arrow"></material-icon>
    35
    - </button>
    35
    + </material-fab>
  3. Convert the remaining three buttons in the same way, but add the mini attribute. For example:

    {2-starteasy → 3-usebuttons}/lib/lottery_simulator.html
    36
    - <button (click)="step()"
    36
    + <material-fab mini raised (trigger)="step()"
    37
    37
    [disabled]="endOfDays || inProgress"
    38
    38
    aria-label="Step">
    39
    39
    <material-icon icon="skip_next"></material-icon>
    40
    - </button>
    40
    + </material-fab>

Once you’re done, run the app and play with the buttons. They look good, and they have a nice ripple animation when you click them.

main UI buttons are now round

Use material-checkbox

The primary UI is looking good! Now let’s start improving the settings section of the UI, which is implemented in lib/src/settings/settings_component.* files. First, let’s change the checkbox to use <material-checkbox>.

  1. Edit the Dart file for <settings-component> (lib/src/settings/settings_component.dart) to import angular_components, and to register MaterialCheckboxComponent and materialProviders (ignore the MaterialRadio* components since you’ll add them later):

    {2-starteasy → 3-usebuttons}/lib/src/settings/settings_component.dart
    @@ -5,6 +5,7 @@
    5
    5
    import 'dart:async';
    6
    6
    import 'package:angular/angular.dart';
    7
    + import 'package:angular_components/angular_components.dart';
    7
    8
    import 'package:components_codelab/src/lottery/lottery.dart';
    8
    9
    import 'package:components_codelab/src/settings/settings.dart';
    @@ -12,7 +13,13 @@
    12
    13
    selector: 'settings-component',
    13
    14
    styleUrls: const ['settings_component.css'],
    14
    15
    templateUrl: 'settings_component.html',
    15
    - directives: const [NgFor],
    16
    + directives: const [
    17
    + MaterialCheckboxComponent,
    18
    + MaterialRadioComponent,
    19
    + MaterialRadioGroupComponent,
    20
    + NgFor
    21
    + ],
    22
    + providers: const [materialProviders],
    16
    23
    )
    17
    24
    class SettingsComponent implements OnInit {
    18
    25
    final initialCashOptions = [0, 10, 100, 1000];
  2. Edit the template file (lib/src/settings/settings_component.html), changing the “checkbox” input (and its surrounding label) into a <material-checkbox>.

    {2-starteasy → 3-usebuttons}/lib/src/settings/settings_component.html
    70
    58
    <h3>Annual interest rate</h3>
    71
    - <label>
    72
    - <input #investingCheckbox type="checkbox"
    59
    + <material-checkbox label="Investing" [(checked)]="isInvesting">
    60
    + </material-checkbox><br>
    73
    - [checked]="isInvesting"
    74
    - (change)="isInvesting = investingCheckbox.checked">
    75
    - Investing
    76
    - </label><br>
    77
    - <div>
    78
    - <label *ngFor="let value of interestRateOptions">
    79
    - <input
    80
    - type="radio"
    81
    - #current
    82
    - [checked]="value == interestRate"
    83
    - [disabled]="!isInvesting"
    84
    - (click)="interestRate = current.checked ? value : interestRate">

Look how much simpler that code is! MaterialCheckboxComponent supports a label attribute and two-way binding to checked, enabling much cleaner HTML.

Use material-radio and material-radio-group

Still working on the settings, let’s convert radio buttons into <material-radio> components. Each group of radio buttons is contained by a <material-radio-group>.

  1. Edit the Dart file for <settings-component> (lib/src/settings/settings_component.dart) to register MaterialRadioComponent and MaterialRadioGroupComponent:

    {2-starteasy → 3-usebuttons}/lib/src/settings/settings_component.dart
    15
    - directives: const [NgFor],
    16
    + directives: const [
    17
    + MaterialCheckboxComponent,
    18
    + MaterialRadioComponent,
    19
    + MaterialRadioGroupComponent,
    20
    + NgFor
    21
    + ],
    22
    + providers: const [materialProviders],
  2. In the template file (lib/src/settings/settings_component.html), find the string "radio". Change the enclosing label to <material-radio>, and then the immediately enclosing <div> to <material-radio-group>.

  3. Move the input’s [checked] and (click) code into the material-radio component. If the input has [disabled] code, move that too.

  4. Change (click) to (checkedChange), and current.checked to $event. Here’s why: MaterialRadioComponent fires checkedChange when the radio button’s selection state changes. The event’s value is true if the radio button has become selected, and otherwise false.

  5. Remove the <input> tag. Your code changes should look like this:

    {2-starteasy → 3-usebuttons}/lib/src/settings/settings_component.html
    6
    6
    <h3>Initial cash</h3>
    7
    - <div>
    8
    - <label *ngFor="let item of initialCashOptions">
    9
    - <input
    10
    - type="radio"
    7
    + <material-radio-group>
    8
    + <material-radio *ngFor="let item of initialCashOptions"
    9
    + [checked]="item == initialCash"
    10
    + (checkedChange)="initialCash = $event ? item : initialCash">
    11
    - #current
    12
    - [checked]="item == initialCash"
    13
    - (click)="initialCash = current.checked ? item : initialCash">
    14
    11
    ${{ item }}
    15
    - </label>
    16
    - </div>
    12
    + </material-radio>
    13
    + </material-radio-group>
  6. Repeat the process for the remaining radio button groups.

  7. Run the app. You might notice a small problem with the appearance of the Strategy settings:

    screenshot

  8. Fix the appearance problem by editing lib/src/settings/settings_component.css to add a rule that maximizes that component’s width:

    {2-starteasy → 3-usebuttons}/lib/src/settings/settings_component.css
    @@ -1,5 +1,5 @@
    1
    - .betting-panel label {
    2
    - display: block;
    1
    + .betting-panel material-radio {
    2
    + width: 100%;
    3
    3
    }
    4
    4
    h3:not(:first-child) {

    The app is now much better looking, but it still displays too much information. We’ll fix that in the next step.

    Problems?

    Check your code against the solution in the 3-usebuttons directory.