Categories
Angular Material

Angular Material — Tables

Angular Material is a popular UI framework based on Material Design for Angular.

In this article, we’ll look at how to use Angular Material into our Angular project.

Tables

We can add a table with Material Design style with Angular Material.

For example, we can write:

app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { MatTableModule } from '@angular/material/table';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    BrowserAnimationsModule,
    MatTableModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

app.component.ts

import { Component } from '@angular/core';

export interface PeriodicElement {
  name: string;
  position: number;
  weight: number;
  symbol: string;
}

const ELEMENT_DATA: PeriodicElement[] = [
  { position: 1, name: 'Hydrogen', weight: 1.0079, symbol: 'H' },
  { position: 2, name: 'Helium', weight: 4.0026, symbol: 'He' },
  { position: 3, name: 'Lithium', weight: 6.941, symbol: 'Li' },
  { position: 4, name: 'Beryllium', weight: 9.0122, symbol: 'Be' },
  { position: 5, name: 'Boron', weight: 10.811, symbol: 'B' },
];

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  displayedColumns: string[] = ['position', 'name', 'weight', 'symbol'];
  dataSource = ELEMENT_DATA;
}

app.component.html

<div>
  <table mat-table [dataSource]="dataSource" class="mat-elevation-z8">
    <ng-container matColumnDef="position">
      <th mat-header-cell *matHeaderCellDef> No. </th>
      <td mat-cell *matCellDef="let element"> {{element.position}} </td>
    </ng-container>

    <ng-container matColumnDef="name">
      <th mat-header-cell *matHeaderCellDef> Name </th>
      <td mat-cell *matCellDef="let element"> {{element.name}} </td>
    </ng-container>

    <ng-container matColumnDef="weight">
      <th mat-header-cell *matHeaderCellDef> Weight </th>
      <td mat-cell *matCellDef="let element"> {{element.weight}} </td>
    </ng-container>

    <ng-container matColumnDef="symbol">
      <th mat-header-cell *matHeaderCellDef> Symbol </th>
      <td mat-cell *matCellDef="let element"> {{element.symbol}} </td>
    </ng-container>

    <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
    <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
  </table>
</div>

styles.css

table {
    width: 100%;
}

We add the MatTableModule to add the table.

Then in app.component.ts , we add the table data in the ELEMENT_DATA array.

The displayColumns array has the column names.

In app.component.html , we add the mat-table directive to make the table Material Design style.

dataSource has the data table’s data source.

The ng-container components have the column definitions.

matColumnDef have the columns.

The th and td are in the ng-container and they’ll be shown in the table.

In styles.css , we set the width to 100% to make the table fill the screen.

Expandable Rows

We can add expandable rows into our table.

For example, we can write:

app.component.ts

import { Component } from '@angular/core';
import { animate, state, style, transition, trigger } from '@angular/animations';

export interface PeriodicElement {
  name: string;
  position: number;
  weight: number;
  symbol: string;
}

const ELEMENT_DATA: PeriodicElement[] = [
  { position: 1, name: 'Hydrogen', weight: 1.0079, symbol: 'H' },
  { position: 2, name: 'Helium', weight: 4.0026, symbol: 'He' },
  { position: 3, name: 'Lithium', weight: 6.941, symbol: 'Li' },
  { position: 4, name: 'Beryllium', weight: 9.0122, symbol: 'Be' },
  { position: 5, name: 'Boron', weight: 10.811, symbol: 'B' },
];

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
  animations: [
    trigger('detailExpand', [
      state('collapsed', style({ height: '0px', minHeight: '0' })),
      state('expanded', style({ height: '*' })),
      transition('expanded <=> collapsed', animate('225ms cubic-bezier(0.4, 0.0, 0.2, 1)')),
    ]),
  ],
})
export class AppComponent {
  dataSource = ELEMENT_DATA;
  columnsToDisplay = ['name', 'weight', 'symbol', 'position'];
  expandedElement: PeriodicElement | null;
}

styles.css

table {
  width: 100%;
}

tr.example-detail-row {
  height: 0 !important;
}

tr.example-element-row:not(.example-expanded-row):hover {
  background: whitesmoke;
}

tr.example-element-row:not(.example-expanded-row):active {
  background: #efefef;
}

.example-element-row td {
  border-bottom-width: 0;
}

.example-element-detail {
  overflow: hidden;
  display: flex;
}

.example-element-diagram {
  min-width: 80px;
  border: 2px solid black;
  padding: 8px;
  font-weight: lighter;
  margin: 8px 0;
  height: 104px;
}

.example-element-symbol {
  font-weight: bold;
  font-size: 40px;
  line-height: normal;
}

.example-element-description {
  padding: 16px;
}

.example-element-description-attribution {
  opacity: 0.5;
}

app.component.html

<div>
  <table mat-table [dataSource]="dataSource" multiTemplateDataRows
    class="mat-elevation-z8">
    <ng-container matColumnDef="{{column}}"
      *ngFor="let column of columnsToDisplay">
      <th mat-header-cell *matHeaderCellDef> {{column}} </th>
      <td mat-cell *matCellDef="let element"> {{element[column]}} </td>
    </ng-container>

    <ng-container matColumnDef="expandedDetail">
      <td mat-cell *matCellDef="let element"
        [attr.colspan]="columnsToDisplay.length">
        <div class="example-element-detail"
          [@detailExpand]="element == expandedElement ? 'expanded' : 'collapsed'">
          <div class="example-element-diagram">
            <div class="example-element-position"> {{element.position}} </div>
            <div class="example-element-symbol"> {{element.symbol}} </div>
            <div class="example-element-name"> {{element.name}} </div>
            <div class="example-element-weight"> {{element.weight}} </div>
          </div>
        </div>
      </td>
    </ng-container>

  <tr mat-header-row *matHeaderRowDef="columnsToDisplay"></tr>
    <tr mat-row *matRowDef="let element; columns: columnsToDisplay;"
      class="example-element-row"
      [class.example-expanded-row]="expandedElement === element"
      (click)="expandedElement = expandedElement === element ? null : element">
    </tr>
    <tr mat-row *matRowDef="let row; columns: ['expandedDetail']"
      class="example-detail-row"></tr>
  </table>
</div>

We keep app.module.ts the same as before.

In app.component.ts , we add the detailExpand animation to show an animation when we toggle the table row.

In app.component.html , we added a new ng-container with the matColumnDef set to expandedDetail .

It spans the whole table’s width.

The detailExpand animation is used in this div.

The columns property in the bottom is set to [‘expandedDetail’] to add the detail column.

Conclusion

We can add basic tables and tables with expanded rows with Angular Material.

Categories
Angular Material

Angular Material — Stepper

Angular Material is a popular UI framework based on Material Design for Angular.

In this article, we’ll look at how to use Angular Material into our Angular project.

Stepper

We can add steps display with the mat-horizontal-stepper component.

For example, we can write:

app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { MatStepperModule } from '@angular/material/stepper';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    BrowserAnimationsModule,
    MatStepperModule,
    MatFormFieldModule,
    FormsModule,
    ReactiveFormsModule,
    MatInputModule,
    MatButtonModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

app.component.ts

import { Component } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  firstFormGroup: FormGroup;
  secondFormGroup: FormGroup;
  isEditable = false;

  constructor(private _formBuilder: FormBuilder) { }

  ngOnInit() {
    this.firstFormGroup = this._formBuilder.group({
      firstCtrl: ['', Validators.required]
    });
    this.secondFormGroup = this._formBuilder.group({
      secondCtrl: ['', Validators.required]
    });
  }
}

app.component.html

<div>
  <mat-horizontal-stepper linear #stepper>
    <mat-step [stepControl]="firstFormGroup" [editable]="true">
      <form [formGroup]="firstFormGroup">
        <ng-template matStepLabel>Fill out your name</ng-template>
        <mat-form-field>
          <mat-label>Name</mat-label>
          <input matInput formControlName="firstCtrl"
            placeholder="Name" required>
        </mat-form-field>
        <div>
          <button mat-button matStepperNext>Next</button>
        </div>
      </form>
    </mat-step>
    <mat-step [stepControl]="secondFormGroup" [editable]="true">
      <form [formGroup]="secondFormGroup">
        <ng-template matStepLabel>Fill out your address</ng-template>
        <mat-form-field>
          <mat-label>Address</mat-label>
          <input matInput formControlName="secondCtrl"
            placeholder="Address" required>
        </mat-form-field>
        <div>
          <button mat-button matStepperPrevious>Back</button>
          <button mat-button matStepperNext>Next</button>
        </div>
      </form>
    </mat-step>
    <mat-step>
      <ng-template matStepLabel>Done</ng-template>
      <p>You are now done.</p>
      <div>
        <button mat-button matStepperPrevious>Back</button>
        <button mat-button (click)="stepper.reset()">Reset</button>
      </div>
    </mat-step>
  </mat-horizontal-stepper>
</div>

We add the MatStepperModule to let us add the mat-horizontal-stepper and mat-step components.

The MatFormFieldComponent lets us add a form field.

The FormsModule and ReactiveFormsModule let us add the form validation to our app.

MatInputModule lets us add inputs to mat-form-fields with the matInput directive.

MatButtonModule lets us add Material Design style buttons.

In app.component.ts , we add the reactive forms.

Then in app.component.html , we add the mat-horizontal-stepper to add the stepper.

The mat-step components have the steps.

Each step has its own form with its own form field.

The editable attribute makes the step editable.

The matStepperNext directive makes the button go to the next step.

The formControlName attribute sets the form control name to the ones defined in the component class so that we can add form validation.

We need to add the formGroup attribute for this to work.

The stepper.reset method resets the stepper state.

Conclusion

We can add a stepper with the mat-step and mat-horizontal-stepper components.

Categories
Angular Material

Angular Material — Snackbar and Sort Header

Angular Material is a popular UI framework based on Material Design for Angular.

In this article, we’ll look at how to use Angular Material into our Angular project.

Snackbar

A snackbar is a container for notification.

The MatSnackBar service can be used to add it.

For example, we can write:

app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { MatSnackBarModule } from '@angular/material/snack-bar';
import { MatButtonModule } from '@angular/material/button';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    BrowserAnimationsModule,
    MatSnackBarModule,
    MatButtonModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

app.component.ts

import { Component } from '@angular/core';
import { MatSnackBar } from '@angular/material/snack-bar';

@Component({
  selector: 'snack-bar-component-example-snack',
  template: 'pizza party',
  styles: [`
    .example-pizza-party {
      color: hotpink;
    }
  `],
})
export class PizzaPartyComponent { }

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  durationInSeconds = 5;

  constructor(private _snackBar: MatSnackBar) { }

  openSnackBar() {
    this._snackBar.openFromComponent(PizzaPartyComponent, {
      duration: this.durationInSeconds * 1000,
    });
  }
}

app.component.html

<div>
  <button mat-stroked-button (click)="openSnackBar()">
    Pizza party
  </button>
</div>

We add the MatSnackbarModule to let us add the snackbar.

Then in app.component.ts , we inject the MatSnackBar service into AppComponent to let us call the openFromComponent method with a component with the snackbar content.

The duration is in milliseconds.

In the template, we have a button to open the snackbar by calling the openSnackBar method.

Sort Header

We can add the sort header component to let us sort state and display tabular data.

For example, we can write:

app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { MatSortModule } from '@angular/material/sort';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    BrowserAnimationsModule,
    MatSortModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

app.component.ts

import { Component } from '@angular/core';
import { Sort } from '@angular/material/sort';

interface Dessert {
  name: string,
  calories: number,
  fat: number
}

const compare = (a: number | string, b: number | string, isAsc: boolean) => {
  return (a < b ? -1 : 1) * (isAsc ? 1 : -1);
}

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  desserts: Dessert[] = [
    { name: 'Frozen yogurt', calories: 159, fat: 6 },
    { name: 'Ice cream sandwich', calories: 237, fat: 4 },
    { name: 'Eclair', calories: 262, fat: 16, },
    { name: 'Cupcake', calories: 305, fat: 4, },
    { name: 'Gingerbread', calories: 356, fat: 16 },
  ];

  sortedData: Dessert[];

  constructor() {
    this.sortedData = this.desserts.slice();
  }

  sortData(sort: Sort) {
    const data = this.desserts.slice();
    if (!sort.active || sort.direction === '') {
      this.sortedData = data;
      return;
    }

    this.sortedData = data.sort((a, b) => {
      const isAsc = sort.direction === 'asc';
      switch (sort.active) {
        case 'name': return compare(a.name, b.name, isAsc);
        case 'calories': return compare(a.calories, b.calories, isAsc);
        case 'fat': return compare(a.fat, b.fat, isAsc);
        default: return 0;
      }
    });
  }
}

app.component.html

<div>
  <table matSort (matSortChange)="sortData($event)">
    <tr>
      <th mat-sort-header="name">Dessert (100g)</th>
      <th mat-sort-header="calories">Calories</th>
      <th mat-sort-header="fat">Fat (g)</th>
    </tr>

    <tr *ngFor="let dessert of sortedData">
      <td>{{dessert.name}}</td>
      <td>{{dessert.calories}}</td>
      <td>{{dessert.fat}}</td>
    </tr>
  </table>
</div>

We add the MatSortModule so that we can use the matSort directive into the table element.

Then we can click on the table header to sort the columns.

When we click on the header, the matSortChange event is emitted.

When it’s emitted, sortData is called.

Then, in app.component.ts , we call sort on the data and call our compare function to do the sorting.

Conclusion

We can add a snackbar and a sort header to sort table columns with Angular Material.

Categories
Angular Material

Angular Material — Sidenavs, Side Toggles, and Sliders

Angular Material is a popular UI framework based on Material Design for Angular.

In this article, we’ll look at how to use Angular Material into our Angular project.

Sidenav

Angular comes with a sidenav component.

We can add it by writing:

app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { MatSidenavModule } from '@angular/material/sidenav';
import { MatButtonModule } from '@angular/material/button';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    BrowserAnimationsModule,
    MatSidenavModule,
    MatButtonModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

app.module.html

<div>
  <mat-drawer-container class="example-container" autosize>
    <mat-drawer #drawer class="example-sidenav" mode="side">
      <p>Auto-resizing sidenav</p>
      <p *ngIf="showFiller">Lorem, ipsum dolor sit amet consectetur.</p>
      <button (click)="showFiller = !showFiller" mat-raised-button>
        Toggle extra text
      </button>
    </mat-drawer>

    <div class="example-sidenav-content">
      <button type="button" mat-button (click)="drawer.toggle()">
        Toggle sidenav
      </button>
    </div>
  </mat-drawer-container>
</div>

app.component.ts

import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  showFiller = false;
}

styles.css

.example-container {
  height: 100vh;
}

We add the sidenav with the mat-drawer-container as its container.

mat-drawer is the sidenav drawer.

The Toggle sidenav button shows the drawer.

The Toggle extra text button closes the drawer.

The filler text display is controlled by showFiller .

drawer.toggle() lets us toggle the drawer.

Slide Toggles

We can add a toggle with the mat-slide-toggle component.

For example, we can write:

app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { MatSlideToggleModule } from '@angular/material/slide-toggle';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    BrowserAnimationsModule,
    MatSlideToggleModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

app.component.html

<div>
  <mat-slide-toggle color="red" [checked]="true" [disabled]="false">
    Slide me!
  </mat-slide-toggle>
</div>

We add the MatSlideToggleModule into our module so that we can add the mat-slide-toggle component into the template.

checked sets the checked state.

disabled makes it disabled if it’s true .

color sets the color.

Slider

We can add a slider with the mat-slider component.

For example, we can write:

app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { MatSliderModule } from '@angular/material/slider';
import { MatCardModule } from '@angular/material/card';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    BrowserAnimationsModule,
    MatSliderModule,
    MatCardModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

app.component.html

<div>
  <mat-card class="example-result-card">
    <mat-card-content>
      <mat-slider min="1" max="5" step="0.5" value="1.5"></mat-slider>
    </mat-card-content>
  </mat-card>
</div>

Adding the MatSliderModule lets us add the mat-slider to our template.

min has the minimum allowed value.

max has the maximum allowed value.

step has the interval we can set.

value is the value of the slider.

Conclusion

We can add sidenavs, slide toggles, and sliders into our Angular app with Angular Material.

Categories
JavaScript Basics

Highlights of JavaScript — Constructor Methods and Getting URLs

To learn JavaScript, we must learn the basics.

In this article, we’ll look at the most basic parts of the JavaScript language.

Constructor Methods

We can add methods to constructors or classes.

For example, we can write:

class Plan {
  constructor(name, price, space, pages) {
    this.name = name;
    this.price = price;
    this.space = space;
    this.pages = pages;
  }

  calcAnnualPrice() {
    return this.price * 12;
  }
}

We add the calcAnnualPrice method to return the value of this.price * 12 .

this is the class instance, which is the object that’s created and returned when we invoke the Plan class.

So we get the price property from the object we create from the class and multiply it by 12.

To invoke the constructor and call the method on the created object, we can write:

const plan = new Plan('basic', 3, 100, 10)
console.log(plan.calcAnnualPrice())

We invoke the Plan constructor with the new keyword.

Then we call calcAnnualPrice on the plan object to get the annual price.

The class syntax is just syntactic sugar on top of prototype inheritance.

The calcAnnualPrice method is just a property of the Plan ‘s prototype property.

The prototype is the prototype of the Plan class, which is the object that it inherits from.

If we log the value of Plan.prototype.calcAnnualPrice :

console.log(Plan.prototype.calcAnnualPrice)

we see the code of the calcAnnualPrice method logged.

Checking for Properties and Methods

To check if a property or method is actually in the object itself rather than its prototype, we can use the hasOwnProperty method.

For example, if we created an object from the Plan class:

const plan = new Plan('basic', 3, 100, 10)

Then we can call hasOwnProperty by writing:

console.log(plan.hasOwnProperty('name'))

Then we should see true logged since name is a property of the plan object itself.

The hasOwnProperty method comes from Object.prototype , which is a property of almost all JavaScript objects.

If we want to list an object’s properties, we can write:

for (const prop in plan) {
  if (plan.hasOwnProperty(prop)) {
    console.log(prop);
  }
}

We loop through the plan ‘s property keys with the for-in loop.

The for-in loop loops through all then properties and its prototypes, so we have to use the hasOwnProperty method to check if a property is actually in the plan object itself.

prop is the property name itself.

Therefore, we should see:

name
price
space
pages

logged.

Getting the URL

We can get and set the URL of the page with JavaScript.

To get the URL, we can use the window.location.href property.

If we type that into the browser dev console, we should get the full URL of the page.

window.location.hostname has the hostname which is the first part of the URL.

For example, if we have the URL“https://jsfiddle.net/09t6La27/10/" , then window.location.hostname returns “jsfiddle.net” .

window.location.hash returns the part of the URL after the # sign.

For example, if we have http://example.com/#foo , window.location.hash returns “#foo” .

Conclusion

We can add methods to constructors or classes.

Also, we can get parts of a URL with the window.location object.