Drag ‘n’ Drop with Angular Materials Table Component

Image: https://material.angular.io/

Blog Post #007

Duncan Faulkner – February 2020

The Angular Material mat-table component probably has the most examples on the Angular Materials website. But I think we can squeeze one more in.

So here is a Drag ‘n’ Drop example.

I needed the ability to drag ‘n’ drop rows in a mat-table for a project I was working on and I thought this would make a good example of how easy it was to implement.

This example requires all the usual setup of an Angular application, rather than fill this post with all the steps to setup an Angular project. I have written a post here that goes through the steps. I will also create a another separate post on adding Angular Material, but for now here is how to add Angular Material to your Angular Project.

Project set up? Let’s add Angular Materials, from the terminal type:

ng add @angular/material

This will add the Angular Material dependencies to the project, next create a directory called shared and then add a new file called material.module.ts in this directory.

Then add the material imports, for this we just need MatTableModule.

// Other Material imports here
import { MatTableModule } from '@angular/material/table';

const MATERIALMODULES = [MatTableModule];

@NgModule({
     imports: [...MATERIALMODULES],
     declarations: [],
     exports: [...MATERIALMODULES]
});
export class MaterialModule {}

Then import material.module.ts file into the app.module.ts file.

// default modules add when app.module.ts was created
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 { HttpClientModule } from "@angular/common/http";

// Add these modules to import
import { MaterialModule } from "./shared/materials.module";
import { DragDropModule } from "@angular/cdk/drag-drop";

const MATERIALMODULE = [MaterialModule];
const IMPORTMODULES = [
  BrowserModule,
  AppRoutingModule,
  MaterialModule,
  BrowserAnimationsModule,
  HttpClientModule,
  DragDropModule
];
const APPCOMPONENT = [AppComponent];

@NgModule({
  declarations: [...APPCOMPONENT],
  imports: [...IMPORTMODULES],
  exports: [...MATERIALMODULE],
  providers: [],
  bootstrap: [...APPCOMPONENT]
})
export class AppModule {}

The MaterialModule is the Angular Material file we just created a few moments ago and the DragDropModule is required to implement the drag and drop from the Angular Material CDK (more on this later).

Follow me on Twitter to get notified about my newest blog posts!🐥

In the app.component.html file remove any html and replace it with the following.

<table mat-table [dataSource]="dataSource" cdkDropList [cdkDropListData]="dataSource"
    (cdkDropListDropped)="drop($event)" class="mat-elevation-z8">

    <ng-container matColumnDef="drag">
        <th mat-header-cell *matHeaderCellDef> Drag </th>
        <td mat-cell *matCellDef="let element">
            <mat-icon cdkDragHandle svgIcon="dragVertical"></mat-icon>
        </td>
    </ng-container>

    <!-- Position Column -->
    <ng-container matColumnDef="position">
        <th mat-header-cell *matHeaderCellDef> No. </th>
        <td mat-cell *matCellDef="let element"> {{element.position}} </td>
    </ng-container>

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

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

    <!-- Symbol Column -->
    <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;" cdkDragLockAxis="y" cdkDrag [cdkDragData]="row">
    </tr>
</table>

A mat-table component takes a datasource, but we also need to add cdkDropList, a cdkDropListData and cdkDropListDropped to the table. The cdkDropList is a container that wraps a set of draggable items. The cdkDropListData is the datasource to attach to this container. And the cdkDropListDropped emits an event when a user drops an item into the container.

The last <tr> section in the mat-table, needs the collection of columns this is the array containing the column names, and row refers to current row.

The cdkDragLockAxis restricts the dragged element to either vertical or horizontal, this stops the user dragging the item all over the screen if it doesn’t make sense to, for example if you are only dragging a list of items to sort them then locking the axis to the “y axis” would keep this within the boundary of the list. The cdkDrag refers to the item being dragged and cdkDragData is the data attached to this drag instance.

That’s the HTML section, now for the code section.

import { Component } from "@angular/core";
import { IconService } from "./services/icon.service";
import { CdkDragDrop, moveItemInArray } from "@angular/cdk/drag-drop";

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" },
  { position: 6, name: "Carbon", weight: 12.0107, symbol: "C" },
  { position: 7, name: "Nitrogen", weight: 14.0067, symbol: "N" },
  { position: 8, name: "Oxygen", weight: 15.9994, symbol: "O" },
  { position: 9, name: "Fluorine", weight: 18.9984, symbol: "F" },
  { position: 10, name: "Neon", weight: 20.1797, symbol: "Ne" }
];

/**
 * @title Basic use of `<table mat-table>`
 */
@Component({
  selector: "app-root",
  styleUrls: ["app.component.scss"],
  templateUrl: "app.component.html"
})
export class AppComponent {
  constructor(private iconService: IconService) {
    this.iconService.registerIcons();
  }

  displayedColumns: string[] = ["drag", "position", "name", "weight", "symbol"];
  dataSource = ELEMENT_DATA;

  drop(event: CdkDragDrop<PeriodicElement[]>) {
    moveItemInArray(this.dataSource, event.previousIndex, event.currentIndex);
    console.log(event.container.data);
  }
}

The drop event takes a generic CdkDragDrop<> in this case it’s a array of type PeriodicElement, this refers to the row that is being dropped. The moveItemInArray takes a datasource, the previousIndex and the currentIndex. The dataSource is the array (in this example the ELEMENT_DATA), the previousIndex is where the item came (position 10) from and the currentIndex (position 1) is where the item is being dropped. This method does all the hard work of the sorting etc… we just need to write the code to persist this and update the view (I’ve not done that in this example). If you check the console.log at this point then we can see that the data has been reordered.

And that’s all there is to add Drag n Drop to a mat-table.

Tips on writing good Git Commit messages

Photo by Yancy Min on Unsplash

Blog Post #006

Duncan Faulkner – February 2020

This post is slightly off my usual topics of Angular and Angular Material though its loosely linked to Angular I felt it was worthy of a post as this is something we do all the time.

I’ve been using Git for about five years or so now, and writing Git Commit messages can be hard (I would say this is just as hard as coming up with meaningful function and variable names in code), especially when trying to be consistent with the formatting of the message.

Follow me on Twitter to get notified about my newest blog posts!🐥

I’m sure we’ve all written commit messages like this, the way I have written them have all started like:

git commit -m "projName-jiraNumber - bug fix" 

git commit -m "projName-jiraNumber - more work" 

git commit -m "projName-jiraNumber - minor changes" 

git commit -m "projName-jiraNumber - work on feature X"

git commit -m "projName-jiraNumber - updated Y on..."  

At first these don’t appear to look that bad, you get a rough idea of the change. But when faced with hundreds of commit messages (and from different developers), finding a specific Commit in the Git History becomes that much harder.


Up until quite recently I hadn’t realised that you can provide multiple -m flags with a commit to add multiple commit messages.

Why would you want to add multiple messages to a commit?

Surely we can just cram everything into one message?

By adding multiple messages you can provide much more detail and be more specific about the commit, it doesn’t need to be an abbreviated or cryptic message that only you can remember what the commit was for.

Now, when reading a commit message in say GitLab, the first message becomes the Subject, the second message is the Body, the third message is the Footer (I’ll come to this in a bit), their values are concatenated as separate paragraphs.

git commit -m "projName-jiraNumber - fixed..." -m "body/description..." -m "footer..."

So this is a little better, adds more detail to the commit message, but we could improve this further by following a convention.

Conventional Commit Standard (I didn’t know that was a thing either!). This is a set of guide lines for creating Git Commit messages that follow a particular format.

The format is:

type(scope):subject
BODY
FOOTER

Each commit message consists of a header, body and footer. The header is mandatory, scope is optional, subject is mandatory. When writing a commit message try and limit each line of the commit to a max of a 100 hundred characters, so it can be read easily on GitLab etc…

Type

The type must be one of the following:

  • build: Changes that affect the build
  • ci: Changes to our CI configuration files and scripts
  • docs: Documentation only changes
  • feat: A new feature
  • fix: A bug fix
  • perf: A code change that improves performance
  • refactor: A code change that neither fixes a bug nor adds a feature
  • style: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)
  • test: Adding missing tests or correcting existing tests

Revert

If a commit reverts a previous change then the type should begin with revert: and the body should explain what this change is reverting.

Scope

The scope refers to the section that this commit belongs to Components, Services, Directives, Pipes for example user-component, user-service, user-directive etc.. Other scopes include changelog, styles, test and refactor.

Subject

The subject should contain a short description of the change, and written in present tense, for example “change” and not “changed” or “changes”.

Body

The body should contain a longer description of the change, try not to repeat the subject and keep it in the present tense as above.

Footer

The footer is used to highlight where issues have created a breaking change in your commit. The footer should start with BREAKING CHANGE: the body should contain a description of the breaking change.

The footer can also be used to reference other issues, for example: “Fixes ticket #123”.


Examples

A few examples of Git Commit messages using this new format:

feat(order): add a save button
add the save button to the sales order form

docs(manual): document the order process
create new document explaining the sales order process

fix(user): update the user menu
update to the user menu to fix the broken menu item

fix(user-service): change save method in API
BREAKING CHANGE: change to the save method to include additional parameters


Follow me on Twitter to get notified about my newest blog posts!🐥

Let me know in the comments what you think to this format.

Setting up an Angular Project

Blog Post #005

Duncan Faulkner – February 2020

I find myself repeating the Angular setup process at the start of most posts I write. So to save time. I will write it out here and reference this post when referring to setting up an Angular application in the future.

This will be a standard setup, for setting up a project with NX Workspaces see my previous post here.

Step one:

Open a terminal and create a directory to store our application in.

cd development
mkdir myproject 
cd myproject

Step two:

If you have nvm, node and the angular cli installed you can skip this step.

To install node.js, you could install this from nodejs.org, but for greater flexibility, I find installing node version manager (nvm), will save a lot of hassle next time you need update node.

Note: Remember to check the version number and update this in the scripts below, at the time of writing this was v0.35.2, it may be different.
Note: You only need to run one of these.

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.2/install.sh | bash

or

wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.2/install.sh | bash

Once installed you should be able to check the version, in the terminal type:

nvm --version
0.35.2

Now we have nvm installed we can install node. Again from the terminal type:

nvm install #and the version of node you want to install, for example:
nvm install 12.14.1

#Other useful nvm commands are:
nvm ls #this will list all installed versions of node
nvm use 12.6.0 #will use this version of node.

nvm alias default v9.3.0 #this will set the default version.

Now we have node installed we can now install angular cli, from the ternimal type:

npm install -g @angular/cli 

This will install the angular cli globally, as denoted by the -g. After installation is complete check the version of angular by typing:

ng v
angular version printed out in console
screen shot of angular version

Step three

Now that’s all setup, lets create a project, in the terminal type:

ng new my-first-project

The angular cli will ask the following questions.

? Would you like to add Angular routing? (y/N) y

 CSS
>SCSS [https://sass-lang.com....] #select this one
 Sass [https://sass-lang.com...]
 Less [http://lesscss.org...]
 Stylus [http://stylus-lang.com...]

That should start the process off and it will install all the dependencies and create our project. You may see some warnings at the end of the install, these are dependencies they may need to be updated, which we will do at the end. For now switch to the my-first-project directory and open up VS Code.

cd my-first-project
code-insiders .
my-first-project shown in vs code

And that’s all there is to it.

Step four

Update the project to the latest versions. In the terminal type:

ng update #this will list any packages that have an update available.

This will check to see if there are any updates, if there are then run:

ng update --all

#there are a couple of additional flags that can be used (with caution!) these are:

ng update --all --allow-dirty #this allows the update to happen when the current branch (if using version control) has not been checked in

ng update --all --allow-dirty --force #this will force the updates, though chances are compatibility will be broken and you may need to roll some packages back to a previous state. 

There is a good chance that this will fail, mainly because there are newer versions of certain packages that would break compatibility.

The next step would be to add Angular Material to the project. For that I will create a separate post so as to keep this purely for the standard setup.

Thanks for reading enjoy…


Previous Posts

Mono-repo and NX Workspaces

Blog post #003

Duncan Faulkner – January 2020

NX Workspaces

What is an NX workspace and what are the benefits of using one?

This blog post takes a look at what an NX Workspace is, how to set one up and to discuss some of the benefits.

An NX Workspace is a product from nrwl.io that extends the Angulars CLI (Command Line Interface) and creates an application project structure for a variety of frameworks and libraries, not just Angular. The structure is based around a monorepo layout and is a technique that large enterprise organisations are adopting. The idea behind the monorepo is that every project or library are all contained in one large project (separated into individual projects under a parent folder).

Wait…all projects in the same solution? That can’t be best practice?

At first this sounds a little odd, why would you bundle all of your projects into one large repo? Surely that must be harder to find things, what about versioning of projects, how do you build individual projects? Can you build individual projects? Do you have to build all of them, what if they are independent projects? These are some the questions I asked when I first started to use the NX Workspace and I will try and answer these here.

Lets run through setting up an empty NX Workspace and we’ll add an Angular project later on. Open a terminal window and type:

npx create-nx-workspace@latest myproject
cd myproject

This will create a new NX Workspace, after a few seconds it will ask for a type of project to create. At the moment these are:

# sample one
What to create in the new workspace (Use arrow keys)
 empty             [an empty workspace]
 web components    [a workspace with a single app built using web components]
 angular           [a workspace with a single Angular application]
 angular-nest      [a workspace with a full stack application (Angular + Nest)]
 react             [a workspace with a single React application]
 react-express     [a workspace with a full stack application (React + Express)]
 next.js           [a workspace with a single Next.js application]

For now, using the arrow keys select the `empty` project. The next step, is to select which CLI to use, choose NX.

NX          [Extensible CLI for JavaScript and TypeScript applications]
Angular CLI [Extensible CLI for Angular applications (Default).]

Note: The NX Workspace is not a replacement for the Angular CLI, it extends the CLI by way of schematics and builders. This allows you to do more things, add different project types for example, an Angular front end with a Nest JS back end or a Node and Express application or React and Express and also the ability to build your own, but all contained in a monorepo structure.

After a few minutes a new empty project is created. Using your favourite text editor open the project. I’m using VS Code, so in the terminal cd into the myproject folder and type code-insiders . (don’t forget the dot!).

An empty NX workspace in VS Code.

empty nx workspace

What’s in the project?

The .vscode folder contains all the VS Code setting files. The apps folder is where all the projects like Angular, React live. The libs folder is where all the library projects live. And the tools folder is where all the pre/post scripts and schemas live. The rest of the files are just config type files.

The main difference between an NX Workspace and an Angular CLI project is the addition of the apps folder. This is the root folder where all projects (except for libraries) live. It’s also worth noting here that in an NX Workspace project there is only one package.json file for the whole solution.

At the moment what we just created doesn’t do anything, we need to add a application to this empty workspace (Angular/React/Vue for example). In the following example I will be setting up and Angular project.

From the terminal type:

npm install --save-dev @nrwl/angular

This will setup the NX Workspace so we can install the Angular project in this instance, there are other options. Because we setup the Workspace using NX CLI we have to use nx commands rather than ng commands, if you selected Angular CLI or are upgrading an existing Angular project to an NX Workspace then you can use the ng commands.

Next from the terminal type nx g @nrwl/angular:app myapp this will create our Angular project my-app and a myapp-e2e project, it will also set the projects to use Jest and Cypress both of these are testing suites (Jest for unit and Cypress for end to end testing).

nx workspace with angular project

Now we should be able to build and run the project, though at the moment there is just a home page with references to NX and the nrwl.io website. Both projects should build and run (including the myapp-e2e application, though this will need to be started separately).

At this point we can now start to build our application and we are not limited to one type of project either. We could have also created an React app and an Express/Node API application and have them all working together if our project called for it.

If our applications have common components that need to be shared, we can (and should) create a library project for this component. The advantages of using a library project keeps the main application as small as possible and allows us to reuse the same component time and time again across all project types, all we need is to include a reference to the library from our projects.

I should just point out that libraries were introduced as part of Angular (I think in version 6) and not NX Workspaces, the NX workspace creates a library folder by default.

So that’s the basic workspace created and with a simple Angular application. Back to the questions I raised at the beginning:

  • Surely that must be harder to find things?
  • What about versioning of projects?
  • How do you build individual projects?

Surely that must be harder to find things.

At first this might seem to be harder, but in a short space of time it’s quite easy to navigate around, each application is in it’s own folder, and you could create parent folders for both application and the end to end project. Which would make it less cluttered under the apps folder.
Create library projects for code reuse, for components that are common and potentially common across multiple applications, for example login screens, user creation, role and permission assignments, menus these make for excellent library components.

What about versioning of projects.

Because all the applications reside under one repository they all have the same version number. This may not be ideal, for example what if you add a new application to an existing NX Workspace that has had previous production releases then the new application will start at that version?

I guess this does work to some extent, but I need to investigate this further there are still some questions that I feel need further clarification on and I’m collating some of these and I will update this post when I have something to add.

How do you build individual projects?

Is it possible to build individual projects – simple answer is yes you can. Make sure you have the NX CLI installed globally.

npm install -g @nrwl/cli 

Then to build a specific application run:

nx run myapp:build

Your application should build and it should appear under the dist directory. In this directory you will see a *-es2015.js and *-es5.js you need to deploy both sets to your web server, when a client connects using an es2015 compatible browser the *-es2015.js files will be used, likewise if an older browser is used then the *-es5.js are used.

Conclusion

The NX Workspace is a great addition to the Angular CLI, I haven’t used all that it has to offer as there is a lot to it, but what I have used is excellent. It deals with the basic structure of an application and it feels natural when you get used to it’s layout of the project.

Note: If you create a empty NX Workspace and then add an Angular project, you might be wondering why there is no angular.json file, it is there but it’s called workspace.json.


Previous Posts…