Angular Drag & Drop Between Lists

Last week I shared steps to make a drag & drop application in Angular in just a few minutes. This is great for sorting items within a single list, but drag & drop is more exciting when used for interaction between elements. Today we’ll look at what it takes to extend last week’s project to include two lists that we can drag & drop between.

Code from the previous article is available here.

The first thing we’ll do is modify the todo-list component to have two lists. Modify todo-list-component.html to have a second cdkDropList element as shown in the code below. We also need to indicate that these lists are connected.

<h2>TODO</h2>
<div 
    cdkDropList 
    #todoList="cdkDropList"
    [cdkDropListData]="tasks"
    [cdkDropListConnectedTo]="[doneList]"
    class="example-list" 
    (cdkDropListDropped)="drop($event)">
    <div class="example-box" *ngFor="let task of tasks" cdkDrag>{{task}}</div>
</div>
<h2>COMPLETED</h2>
<div
    cdkDropList 
    #doneList="cdkDropList"
    [cdkDropListData]="completedTasks"
    [cdkDropListConnectedTo]="[todoList]"
    class="example-list" 
    (cdkDropListDropped)="drop($event)">
    <div class="example-box" *ngFor="let task of completedTasks" cdkDrag>{{task}}</div>
</div>

Now we need to make a pair of changes to todo-list.component.ts. First, we need to add the completedTasks collection referenced by our second list. We also need to modify the drop event handler to determine whether we’re rearranging a list or moving an item between lists.

import { Component, OnInit } from '@angular/core';
import {CdkDragDrop, moveItemInArray, transferArrayItem} from '@angular/cdk/drag-drop';

@Component({
  selector: 'app-todo-list',
  templateUrl: './todo-list.component.html',
  styleUrls: ['./todo-list.component.css']
})
export class TodoListComponent implements OnInit {
  tasks = [
    'Cleaning',
    'Gardening',
    'Shopping'
  ];
  completedTasks = [];

  constructor() { }

  ngOnInit(): void {
  }

  drop(event: CdkDragDrop<string[]>) {
    if (event.previousContainer === event.container) {
      moveItemInArray(
        event.container.data, 
        event.previousIndex, 
        event.currentIndex);
    } else {
      transferArrayItem(
        event.previousContainer.data,
        event.container.data,
        event.previousIndex,
        event.currentIndex);
    }
  }
}

That’s it–we’re done! Let’s run the application and see what happens.

$ ng serve --open

We can now rearrange items in the individual lists, as we could before, and move items between the two lists. Final code for this article is available on GitHub, here.

Feature image by Alexandra_Koch from Pixabay

E2E Angular Drag & Drop App in 5 Steps

Image by sachin_21 from Pixabay

Drag & drop is one of the best enhancements you can implement to give your app a modern feel and provide a great user experience, and Angular’s Component Dev Kit (CDK) makes it really easy to do. In this article, we’ll build a new Angular app from scratch using the Angular CLI and add a drag & drop todo list component.

Prereqs: Make sure you have the Angular CLI installed. You can test by running the command ng --version. If you need to install it, run the command npm install -g @angular/cli. (If you have problems installing on Linux, maybe this article can help!)

Step 1: Create a new app

Create a new app using the Angular CLI. We’ll also install the Angular CDK, and make sure it all works:

$ ng new my-app --defaults
$ cd my-app
$ npm install @angular/cdk
$ ng serve --open

Step 2: Import DragDropModule

In order to make use of Angular CDK’s drag & drop module, we need to import it. Open app.module.ts and import DragDropModule by making the following two edits:

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

import { AppComponent } from './app.component';
import { DragDropModule } from '@angular/cdk/drag-drop';

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

Step 3: Add a new component

Now we’ll use the Angular CLI again to add a new component:

$ ng generate component todo-list

Next we need to modify the component to handle the drop event. We’ll also add some hard-coded test data to work with. Open todo-list.component.ts and make the following changes:

import { Component, OnInit } from '@angular/core';
import {CdkDragDrop, moveItemInArray} from '@angular/cdk/drag-drop';

@Component({
  selector: 'app-todo-list',
  templateUrl: './todo-list.component.html',
  styleUrls: ['./todo-list.component.css']
})
export class TodoListComponent implements OnInit {
  tasks = [
    'Cleaning',
    'Gardening',
    'Shopping'
  ];

  constructor() { }

  ngOnInit(): void {
  }

  drop(event: CdkDragDrop<string[]>) {
    moveItemInArray(this.tasks, event.previousIndex, event.currentIndex);
  }
}

We also need to modify the component HTML. Update the contents of todo-list.component.html to be the following:

<div cdkDropList class="example-list" (cdkDropListDropped)="drop($event)">
    <div class="example-box" *ngFor="let task of tasks" cdkDrag>{{task}}</div>
</div>

Step 4: Use the new component

At this point, we’re actually done adding the basic drag & drop functionality, so let’s use our new component. Replace the entire contents of app.component.html with this:

<app-todo-list></app-todo-list>

Now, head back out to your terminal, re-launch the app, and–voila!–you’ve got drag & drop!

$ ng serve --open

(Optional) Step 5: Make it pretty

Sure it’s functional, but it doesn’t look very good! Spruce it up by adding the following CSS to todo-list.component.css:

.example-list {
  width: 500px;
  max-width: 100%;
  border: solid 1px #ccc;
  min-height: 60px;
  display: block;
  background: white;
  border-radius: 4px;
  overflow: hidden;
}

.example-box {
  padding: 20px 10px;
  border-bottom: solid 1px #ccc;
  color: rgba(0, 0, 0, 0.87);
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: space-between;
  box-sizing: border-box;
  cursor: move;
  background: white;
  font-size: 14px;
}

.cdk-drag-preview {
  box-sizing: border-box;
  border-radius: 4px;
  box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2),
              0 8px 10px 1px rgba(0, 0, 0, 0.14),
              0 3px 14px 2px rgba(0, 0, 0, 0.12);
}

.cdk-drag-placeholder {
  opacity: 0;
}

.cdk-drag-animating {
  transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}

.example-box:last-child {
  border: none;
}

.example-list.cdk-drop-list-dragging .example-box:not(.cdk-drag-placeholder) {
  transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}

Ah, that feels a little better!

Intro to Unit Testing In Angular With Karma & Jasmine

Ah, unit testing. One of my favorite subjects. I’ve been big on unit tests for what seems like more than a decade now, but I’ve never dipped my toes into the UI unit test pool! It’s been long enough, though. It’s time to learn!

So, I’m taking my first look at writing tests for an Angular application using Karma and Jasmine. I thought this tutorial at scotch.io was a great first read. It provides overviews of what Karma and Jasmine are, and walks you through the steps of creating an Anuglar application and adding tests from scratch using the Angular CLI, and that’s basically the foundation I’m working with today.

In this article, I’m going to write some tests as I extend behavior on “an existing product” and also write tests for some untested code. I’ll be using my favorite boilerplate code–the .NET Core CLI Angular template–as my existing product, as it provides a functional appliction with some existing tests for us to build on.

If you haven’t already, start by creating a new Angular application by running the following command:

dotnet new angular -o my-app

Verify that can run your new application by running dotnet run in the my-app directory. my-app/ClientApp contains the Angular application. Since we’re going to be adding to existing test suite, we should verify that the existing tests run and pass. Run the following from my-app/ClientApp:

npm install
ng test

You should see output like the following, indicating that all tests have passed.

Now that we’ve verified that existing tests work, we can start making changes. Let’s assume we want to modify the Counter page to have a decrement button in addition to its increment button, because sometimes we click too many times. Before we start making changes to the Counter component itself, we can describe the desired behavior in the existing counter.component.spec.ts file, which already contains similar logic for the increment functionality. Most of what needs to be written can be copied and adjusted from the Increment test.

Here’s the test I wrote:

it ('should decrease the current count by 1 when Decrement is clicked', async(() => {
  const countElement = fixture.nativeElement.querySelector('strong');
  expect(countElement.textContent).toEqual('0');

  const decrementButton = fixture.nativeElement.querySelector('#decrement-button');
  decrementButton.click();
  fixture.detectChanges();
  expect(countElement.textContent).toEqual('-1');
}));

I can run tests again, and guess what–it’ll fail. Cool. So let’s make it work, which is just two steps. First we need to add the button to counter.component.html:

<h1>Counter</h1>

<p>This is a simple example of an Angular component.</p>

<p aria-live="polite">Current count: <strong>{{ currentCount }}</strong></p>

<button class="btn btn-primary" (click)="incrementCounter()">Increment</button>

<button class="btn btn-secondary" (click)="decrementCounter()" id="decrement-button">Decrement</button>

And then we need to add the logic for when it’s clicked in counter.component.ts:

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

@Component({
  selector: 'app-counter-component',
  templateUrl: './counter.component.html'
})
export class CounterComponent {
  public currentCount = 0;

  public incrementCounter() {
    this.currentCount++;
  }

  public decrementCounter() {
    this.currentCount--;
  }
}

That’s it. Now we can run ng test again and see that we now have 3 passing tests.

Good stuff. Now, let’s turn our attention to some untested code. FetchDataComponent is functional but has no tests for its display of data retrieved from the API. We’ll need to mock the API call to return some data and then assert that results are displayed as expected.

Here’s my fetch-data.component.spec.ts file with a test that mocks the API call and asserts that data is populated in the table on the UI:

import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';

import { FetchDataComponent } from './fetch-data.component';

describe('FetchDataComponent', () => {
    let component: FetchDataComponent;
    let fixture: ComponentFixture<FetchDataComponent>;
    let httpMock: HttpTestingController;

    beforeEach(async(() => {
        TestBed.configureTestingModule({
            imports: [HttpClientTestingModule],
            declarations: [FetchDataComponent],
            providers: [{ provide: 'BASE_URL', useValue: 'https://my.fake.url/' }]
        }).compileComponents();
    }));

    beforeEach(() => {
        fixture = TestBed.createComponent(FetchDataComponent);
        component = fixture.componentInstance;
        httpMock = TestBed.get(HttpTestingController);
    });

    it('should retrieve weather forecasts', async(() => {
        const dummyForecasts = [
            {
                date: "date1",
                temperatureC: 0,
                temperatureF: 32,
                summary: "summary1"
            },
            {
                date: "date2",
                temperatureC: 100,
                temperatureF: 212,
                summary: "summary2"
            }
        ];

        const req = httpMock.expectOne('https://my.fake.url/weatherforecast');
        req.flush(dummyForecasts);

        expect(component.forecasts).toBe(dummyForecasts);
        fixture.detectChanges();
        const rowCount = fixture.nativeElement.querySelectorAll('table tr').length;
        expect(rowCount).toBe(3); // header plus two data rows
    }));
});

There’s a lot to unpack with this test, and I’m going to attempt to give a visual, summary-level explanation in the screenshot below.

Once again, we run tests using ng test and see that we now have passing tests for FetchDataComponent!

Installing Angular CLI on Ubuntu 18.04.4 LTS

I was all geared up to explore unit testing in Angular this morning–something I’ve been putting off learning for quite some time–and quickly hit an problem. I started by installing Angular CLI using apt install ng-common, but trying to use the Angular CLI’s ng command was launching Emacs!

It took me a minute to figure this out, and here’s what it took. First, I uninstalled:

sudo apt remove ng-common

Next, I installed using npm:

sudo npm install -g @angular/cli

This was common how-to-fix-it advice, but this didn’t resolve the problem for me. At this point ng was no longer launching an editor, but instead it was an unrecognized command. Luckily, I found the solution here.

I noticed in the install output that the bin location was listed. I was able to verify where the ng binary was located and add an alias. After that, ng --version worked successfully.

Here’s the alias I added to ~/.bashrc:

alias ng='/usr/local/lib/node_modules/@angular/cli/bin/ng'

Permission Bits in Base64 String

We’ve been kicking around some ideas of how to revamp user permissions in our application, and I was reflecting on how I’ve done it in the past. Previously, I worked on a client/server application where each user had a permission string that tracked all the rights they’d been granted, and global enumerations were used to identify which permissions and rights. I was explaining the approach to colleagues, and there was enough interest that I decided to recreate it.

Here’s the summary:

  • Each user has a Base64-encoded permission string
  • There’s an enumeration for the different modules; this identifies which byte in the permission string contains the rights
  • There’s an enumeration for the different rights which can be granted for a given module; these are used in bitwise operations to check or modify a user’s rights to a module

Enumerations

public enum Modules
{
    ModuleOne = 0,
    ModuleTwo = 1,
    ModuleThree = 2
}

public enum Permissions
{ 
    Create = 1,     // 0000 0001
    Read = 2,       // 0000 0010
    Update = 4,     // 0000 0100
    Delete = 8,     // 0000 1000
    Execute = 16    // 0001 0000
}

Granting Permission

Granting a permission is a simple as specifying the module and permission to grant. The module is used to identify the correct byte, and then a bitwise OR is performed to enable the corresponding permission bit.

string GrantPermission(string permissionString, Modules module, Permissions permission)
{
    if (permissionString == null)
    {
        permissionString = string.Empty;
    }

    var bytes = Convert.FromBase64String(permissionString);
    
    if (bytes.Length <= (int)module)
    {
        Array.Resize(ref bytes, (int)module + 1);
    }

    bytes[(int)module] |= (byte)permission;

    return Convert.ToBase64String(bytes);
}

Checking Permission

Checking a permission is very similar to granting except that we AND the permission mask. If the result isn’t zero, that means the user has the specified permission.

bool HasPermission(string permissionString, Modules module, Permissions permission)
{
    if (string.IsNullOrWhiteSpace(permissionString))
    {
        return false;
    }

    var bytes = Convert.FromBase64String(permissionString);

    if (permissionString.Length < (int)module)
    {
        return false;
    }

    var b = bytes[(int)module];

    return (b & (byte)permission) != 0;
}

Revoking Permission

Revoking a permission is similar as well. This time, we’ll invert the bits on the permission mask and AND it to the module permission byte so that all non-zero bits except the specified permission will remain the same.

string RevokePermission(string permissionString, Modules module, Permissions permission)
{
    if (permissionString == null)
    {
        permissionString = string.Empty;
    }

    var bytes = Convert.FromBase64String(permissionString);

    if (bytes.Length <= (int)module)
    {
        return permissionString;
    }

    bytes[(int)module] &= (byte)~permission;

    return Convert.ToBase64String(bytes);
}

Conclusion

This was just a quick & dirty re-implementation of something from the past. I haven’t put this code through the wringer, so there may be some bugs, and you could probably make it more developer-friendly by extracting it to its own class or moving this logic into some nice extension methods. It’s an efficient way to store and check permissions for a collection of modules. Depending on your needs, it might be a good fit. I’m also wondering if this could be used in a hybrid system–for example, perhaps it would make sense for different applications or scopes to have their own permission string within a larger system.

Pull Image From Private Registry

In order to deploy an image into Kubernetes, the image must be available in a registry. I’m very much in the learning & experimenting phase of my Kubernetes journey, and I find myself using Docker Hub private registries for a lot of things. After using docker login I can docker push & docker pull images from a private registry just by naming them correctly with my Docker ID e.g. <my-docker-id>/some-image. However, kubectl doesn’t automatically inherit this knowledge and access, so what must be done to enable deployment of a locally-developed image into a locally-runing cluster? Let’s take a look!

The first thing we need to do is authenticate to the registry, which we can do using docker login. This will prompt for credentials and store an authorization token in ~/.docker/config.json. The lines below demonstrate how to do this and view the result.

docker login
cat ~/.docker/config.json

In order to make the authentication token accessible to the Kubernetes cluster, we can use Secrets. The following commands can be used to copy the credential from the Docker config.json into a Secret named regcred and inspect the result.

kubectl create secret generic regcred \
    --from-file=.dockerconfigjson=<path/to/.docker/config.json> \
    --type=kubernetes.io/dockerconfigjson
kubectl get secret regcred --output=yaml

Next, we’ll create a Deployment that includes our Secret. Create a new file my-app-deployment.yaml with the following contents:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app-deployment
spec:
  replicas: 1
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      containers:
      - name: my-app
        image: <my-docker-id>/my-app:latest
        ports:
        - containerPort: 80
      imagePullSecrets:
      - name: regcred

And we’ll need to expose the deployment with a Service, so create another file my-app-service.yaml with this for its contents:

apiVersion: v1
kind: Service
metadata:
  name: my-app-svc
  labels:
    app: my-app
spec:
  type: NodePort
  ports:
  - port: 80
    nodePort: 30080
  selector:
    app: my-app

Now we use these files to create the Deployment and Service in Kubernetes:

kubectl create -f my-app-deployment.yaml
kubectl create -f my-app-service.yaml

That should do the trick. You can inspect the Deployment and Service using kubectl get pods and kubectl describe service my-app-service respectively. Assuming everything deployed correctly, you should be able to access your app at http://cluster-host-ip:30080.

Run an ASP.NET Core App on Raspberry Pi With Docker

In my last article, I wrote about how to create a single-page Angular app using the .NET Core CLI, create a Docker image, and run it as a container in about 4 steps that take just minutes to execute. By modifying a single line in your Dockerfile, you can target the 32-bit ARM architecture needed to run the image as a container on a Raspberry Pi.

Here’s the one line that needs to change in the Dockerfile to make it runnable on ARM32 (old line is commented for reference):

# FROM mcr.microsoft.com/dotnet/core/aspnet:3.1 AS base
FROM mcr.microsoft.com/dotnet/core/aspnet:3.1.2-buster-slim-arm32v7 AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443

FROM mcr.microsoft.com/dotnet/core/sdk:3.1 AS build
WORKDIR /src
COPY ["my-app.csproj", "./"]
RUN dotnet restore "./my-app.csproj"
COPY . .
WORKDIR "/src/."
RUN dotnet build "my-app.csproj" -c Release -o /app/build
RUN apt-get update && \
    apt-get install -y wget && \
    apt-get install -y gnupg2 && \
    wget -qO- https://deb.nodesource.com/setup_10.x | bash - && \
    apt-get install -y build-essential nodejs

FROM build AS publish
RUN dotnet publish "my-app.csproj" -c Release -o /app/publish

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "my-app.dll"]

Note that the official list of available tags for different architectures can be found here. Consult this list to determine if newer images are available.

That’s it, though. Create your image, push it to Docker Hub, pull it on your Raspberry Pi, and run it just as you would locally. If you haven’t used Docker Hub before, you’ll need to login and create a repository. Check out the Docker Hub Quickstart for help. You may also need to run docker login on both machines to access your new repo.

# dev machine
docker build -t <dockerID>/repo .
docker push <dockerID>/repo

# raspberry pi
docker pull <dockerID>/repo
docker run -d -p 5000:80 <dockerID>/repo
The default dotnet new Angular app running locally on Raspberry Pi.