Friday, June 18, 2021

Font Awesome for Angular with Data-Driven Icons

Font Awesome is an easy-to-use library full of tons of images as fonts. There's also an Angular implementation for it, but it's a little trickier to use. We're using the Angular version and needed to be able to render icons based on data that would only be known at runtime. With vanilla Font Awesome you'd just bind the class based on the data and that's that, but with Angular Font Awesome you have to use a componentFactoryResolver to build the icon based on the data. There's technically documentation on exactly how to do this, but even with that it still took me quite a while to figure out exactly what needed to happen so I figured I'd write it up.

First things first, I was set on the right path by this SO answer so feel free to start there yourself or just keep reading. That answer links to this official documentation and between the two I was on my way, but still confused. The documentation says you just bind the icon you want (in the case of the example, faUser). That's not very dynamic and it's definitely not data-driven. What we need is a way to specify the prefix and the icon we want and having that render based on dynamic data. This issue on github shows that you can use the icon() function to generate an icon, but that wasn't working either. Eventually, through some significant debugging and source code review, I figured out that you can just pass an array to componentRef.instance.icon and your icon will show up, if you've already registered that icon in your icon library. Here's the final solution.

Step 1: Import the icons you're going to use. In our case we have a separate module to import all of the icons we want to use. We also have a pro license so we've got multiple icon types from Font Awesome to bring in. Here's what that module looks like.

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

import { FontAwesomeModuleFaIconLibrary } from '@fortawesome/angular-fontawesome';
import { faSearch as falSearch } from '@fortawesome/pro-light-svg-icons'// fal
import { faCheckCirclefaShoppingCart as farShoppingCart } from '@fortawesome/pro-regular-svg-icons'// far
import {
  faBars,
  faBuilding,
  faHeadset,
  faHome,
  faSearch as fasSearch,
  faShoppingCart as fasShoppingCart,
  faTimes,
  faUnlock,
  faUser
from '@fortawesome/pro-solid-svg-icons'// fas
import { FontAwesomeIconHostComponent } from './components/font-awesome-host/font-awesome-host.component';

@NgModule({
  declarations: [ FontAwesomeIconHostComponent ],
  exports: [ FontAwesomeIconHostComponentFontAwesomeModule ]
})
export class CustomFontAwesomeModule {
  constructor(libraryFaIconLibrary) {
    library.addIcons(faBars);
    library.addIcons(faBuilding);
    library.addIcons(faCheckCircle);
    library.addIcons(faHeadset);
    library.addIcons(faHome);
    library.addIcons(falSearch);
    library.addIcons(farShoppingCart);
    library.addIcons(fasSearch);
    library.addIcons(fasShoppingCart);
    library.addIcons(faTimes);
    library.addIcons(faUnlock);
    library.addIcons(faUser);
  }
}


Step 2: Create a Font Awesome Host Component to build the icons dynamically based on the data
Update: I added a size property
import { ComponentComponentFactoryResolverInputOnInitViewChildViewContainerRef } from '@angular/core';

import { FaIconComponent } from '@fortawesome/angular-fontawesome';
import { IconNameIconPrefix } from '@fortawesome/fontawesome-svg-core';

@Component({
  selector: 'app-fa-host',
  template: '<ng-container #host></ng-container>'
})
export class FontAwesomeIconHostComponent implements OnInit {
  @ViewChild('host', {static: trueread: ViewContainerRef}) containerViewContainerRef;

  @Input() iconIconName;
  @Input() prefixIconPrefix;
  @Input() sizeSizeProp;

  constructor(private componentFactoryResolverComponentFactoryResolver) {
  }

  public ngOnInit(): void {
    this.createIcon();
  }

  public createIcon(): void {
    const factory = this.componentFactoryResolver.resolveComponentFactory(FaIconComponent);
    const componentRef = this.container.createComponent(factory);
    componentRef.instance.icon = [this.prefixthis.icon];
    componentRef.instance.size = this.size;
    // Note that FaIconComponent.render() should be called to update the
    // rendered SVG after setting/updating component inputs.
    componentRef.instance.render();
  }
}


Step 3: Use the Font Awesome Host Component and provide the icon and prefix based on the data
<atlas-fa-host [icon]="item.icon" [prefix]="item.iconPrefix" *ngIf="!!item.icon"></atlas-fa-host>

In this use case "item" is an object that has an icon property and an iconPrefix property. This still isn't perfect in my book because any time you add a row to the data that represents an icon you haven't imported yet, you'd have to update the Angular code to import it. But this does allow us to iterate through a list of objects and dynamically render an icon based on the data for each object. That's progress.

Monday, March 22, 2021

Searching all Fields in all Tables in a Single Database

Way back in 2015 I provided the SQL to search for a specific string in any field in any table in any database on a server. I don't remember why I needed that, but I guess I did. What I've come to use more frequently, however, is a search of every field in every table in a single database. So here's that query:


DECLARE @SearchText NVARCHAR(1000) = 'Some value'
DECLARE @SchemaName NVARCHAR(256), @TableName NVARCHAR(256), @ColumnName NVARCHAR(256)
DECLARE TableCursor CURSOR FOR
SELECT sch.[Name] AS SchemaName, st.[Name] AS TableName, sc.[Name] AS ColumnName
FROM sys.tables st WITH (NOLOCK)
INNER JOIN sys.columns sc WITH (NOLOCK)
    ON st.object_id = sc.object_id
INNER JOIN sys.schemas sch WITH (NOLOCK)
    ON st.schema_id = sch.schema_id

OPEN TableCursor
FETCH NEXT FROM TableCursor INTO @SchemaName, @TableName, @ColumnName

WHILE @@FETCH_STATUS = 0
BEGIN

    DECLARE @InternalSQL NVARCHAR(MAX) = 'SELECT @CountParam = COUNT(*) FROM [' + @SchemaName + '].[' + @TableName + '] WHERE [' + @ColumnName + '] LIKE ''%' + @SearchText + '%'''
    DECLARE @Count INT

    EXEC SP_EXECUTESQL @InternalSQL, N'@CountParam INT OUT', @Count OUT

    IF (@Count > 0)
    BEGIN
        PRINT @SchemaName + '.' + @TableName + '.' + @ColumnName
    END

    FETCH NEXT FROM TableCursor INTO @SchemaName, @TableName, @ColumnName

END

CLOSE TableCursor
DEALLOCATE TableCursor

Friday, February 12, 2021

Scrum Branching Strategy


I've been using git for a few years now and I don't see how I could ever go back to TFVC or *shudder* Visual Source Safe. Git makes managing branches and work significantly easier than other source control technologies I've used in the past, but there seems to be some concern or confusion regarding a good branching strategy to use along with Scrum.

I was pointed to this article by a colleague and while I agreed with some of it, some of it just seemed flat out wrong to me. We've worked really hard through several iterations to land on our branching strategy in my current role and it works really well. I figured I'd share it and our reasons for going this route.

Before I start, it's important to point out that we use Azure DevOps for our CI/CD pipelines so we use their Pull Request tool to merge between some branches. If you're using a different tool then you may follow slightly different processes that make sense for that tool. I've done my best to lay out the way we do it using git terminology, but there are definitely parts that are specific to Azure DevOps.

The Basic Premise

We started with the idea that you should always have a branch that matches what is live right now. We decided that would be our main branch. We also have the classic 4 environments (dev, QA, staging, production), but we determined that we don't necessarily need to have corresponding branches for those environments at all times. That's because at any given time those environments could change. Though there are rules for promoting to QA and staging, dev is free to be updated at any time and is frequently changing (as we develop). We also recognized that in the event of a hotfix we wanted to be able to test the hotfix in the QA environment before promoting the change directly to the production environment (once the hotfix is approved).

The Branches

  • main - this branch matches our production environment nearly all the time
  • staging - this branch is created only when necessary
  • release/<release number> - this branch is our iteration branch and contains all changes that are expected to be available as a result of our iteration
  • <task branches> - these branches are transient, abundant, and created as necessary to complete individual pieces of the iteration (i.e. stories or bugs), but could also be created as sub-task branches to work on tasks within stories or bugs

The Strategy

Once we made the decision to change to this branching strategy, we deleted all branches except main because they were no longer useful. So assume these steps start with only the main branch in existence and that the contents of the main branch exactly represent what is in the production environment.
  1. Create a branch from main called release/<release number> where <release number> represents the next version number
    • If 1.0 is in production you'd create a branch called release/1.1
  2. When a new story is started in the sprint, create a branch for it, named something meaningful to the story (I'll refer to this as the story branch the rest of the way)
    • If the story is about accepting online orders you might name the branch online-orders
  3. Every task in the story could have a separate task branch named whatever you want (I'll refer to these as task branches the rest of the way)
    • If the story to accept online orders has separate pieces to accept credit card payments and invoices you might create a branch called accept-credit-cards when you start working on that task and another developer might create a branch called accept-invoices when they start working on the other task
  4. As the work is completed on each task, the task branch is merged into the story branch
    • Depending on team dynamics, you could use a pull request to do this, but on our team we just review our own changes and make sure there are no merge conflicts
    • It doesn't matter what merge type you use (fast-forward, rebase, etc.) because these commits will be squashed in the next step
  5. When a story is complete, the story branch can be deployed to the dev environment to validate everything is working together as expected
  6. When the story is ready to be tested (this varies by team and project, but on our team a story is ready to be tested when the developers feel confident that it could safely go live right at that moment; in other words, we don't "throw it over the wall" and wait to see what the tester finds), we merge the story branch into the release/<release number> branch and deploy that branch to the QA environment
    1. We do this using a pull request in Azure DevOps
    2. The important part here is to do a squash commit and provide a useful commit message for the work that was done on the story branch (e.g. "Made changes to allow online orders to be placed via credit card and invoice")
  7. If the tester finds something, create a new branch based on the release/<release number> branch, fix the bug, and repeat step 6
  8. At the end of the sprint, only completed stories are in the release/<release number> branch (that's important)
  9. Create a staging branch from main
  10. Perform a squash commit from release/<release number> into staging and make the commit message the release number
    1. We do this via pull request in Azure DevOps and it allows us to use the release number as the title of the pull request and then we fill in the individual commit message from the story branches as the description of the pull request; that way we have the detailed information from each story as well as the release number in the commit log
    2. This is also the time to associate work items with the commit if your team does that
  11. Once the staging branch has been updated, the release/<release number> branch can safely be deleted and the staging branch can be deployed to the staging environment
  12. When the next sprint starts, create a new release/<release number> branch based on the next release (so if our previous release was 1.1 this branch would be named release/1.2) from the staging branch (that's important)
  13. When it's time to deploy the code to production, rebase the changes from the staging branch onto the main branch
    1. Rebasing one branch onto another applies each commit from the source onto the destination, but there's only one commit to staging that gets rebased onto master at this point
    2. Since we create the new release/<release number> branch from the staging branch we already have the same exact commit (with the same SHA1) in the release/<release number> branch that is now in main
  14. Delete the staging branch and repeat this process starting at step 2 for every sprint until the project is complete

Conclusion

Obviously every team is different and what works for us may not work for you. But this does work very well for us. The commit log on main is clean and concise, but contains all of the important information for which stories are in which release. I'll try to do a separate write-up on our hotfix approach tomorrow (or next week or something).

Tuesday, February 2, 2021

Flexbox Row/Column

I've been working more towards using Flexbox and away from Bootstrap's column layout as much as possible. I love the column layout, but it still feels janky to me sometimes and Flexbox seems a lot smoother. I was recently updating a page that uses columns to display 3 items on a row and each item  (with margins and padding) takes up 33% of the row. When there are 4 items, there are 2 rows with 3 items on the first row and 1 item on the second row, but the single item on the second row still only takes up 33% of the second row.

I was able to accomplish this using the following markup and CSS.

<div class="d-flex flex-wrap">
  <div class="item">
    ...single item contents...
  </div>
  <div class="item">
    ...single item contents...
  </div>
  <div class="item">
    ...single item contents...
  </div>
  <div class="item">
    ...single item contents...
  </div>
</div>
.item {
  flex: 0 0 33%;
}

At one point I had flex: 1 0 33% and that was really close, except the one item on the second line took up the entire line. This works beautifully and I'm happy because I get to use flexbox.

Tuesday, November 17, 2020

Git Stash Message

I've often struggled with git stash because I was under the impression you couldn't tag, label, or otherwise identify what you were doing when you stashed something. I've still used it plenty, but it was always a bit of a struggle to figure out which stash was which down the road.

Yesterday I learned you actually can label the stashes, by using save.

git stash save [message]

Good times.

Monday, February 24, 2020

Array Intersection in JavaScript

As JavaScript  progresses we continue to see new features an enhancements that make it into a more robust language. As we apply other languages and frameworks on top of JavaScript we get even more functionality (duh, that's why we use those frameworks). One thing that doesn't exist quite yet out of the box in JavaScript is array intersection. What I mean is the ability to see when one array contains values from another array.

For example, let's say our application has role-based permissions that get populated as an array. That array might look like this:const roles = ['employee', 'manager']; We protect something in our app by applying restrictions to it, maybe like this:const restrictions = ['president', 'accountant']; What we need to know is does the user have the right role to lift the restriction. In other words, we want to know if the two arrays intersect.

Since there is no Array.intersect (yet) we have to come up with something. This is just what I use when I have two simple arrays (in this case two arrays of string values). We could add it to the Array prototype if we want, but I'm not going to show that here.

const roles = ['employee', 'manager'];
const restrictions = ['president', 'accountant'];
const matches = roles.filter(r => restrictions.includes(r));


Once this runs, matches contains all of the values that were in both arrays (so it's an empty array in this case). We can do evaluations on it like:
if (!!matches && !!matches.length) {
  // do some stuff
}

BONUS: We can do the same thing with complex objects by applying the map function to this process. So if our array of roles looked like this:const roles = [{name: 'employee'}, {name: 'manager'}]; we could do the intersect like this:const matches = roles.map(r => r.name).filter(role => restrictions.includes(role));

I got this one and a couple of other goodies from this post on Medium. Happy coding!

Tuesday, February 11, 2020

Masked Input Length Validation in Angular

We use angular2-text-mask to mask some of our inputs at work (so phone number fields show parentheses and a hyphen, for example). I discovered today that using the built-in Angular minLength validator doesn't work with the text mask because the masked value meets the length criteria. So I wrote my own validator based on the built-in one that accepts an optional array of characters to replace and then only returns true if the modified length (that is, the length of the string after removing masking characters) is long enough (or short enough, depending on which validator you use). You know what? Let me just show you.

   1:  import { Injectable } from '@angular/core';
   2:  import { AbstractControl, ValidationErrors, ValidatorFn } from '@angular/forms';
   3:  
   4:  @Injectable({
   5:    providedIn: 'root'
   6:  })
   7:  export class MaskedLengthValidator {
   8:    static minLength(minLength: number, replace?: Array<string>): ValidatorFn {
   9:      return (control: AbstractControl): ValidationErrors | null => {
  10:        if (this.isEmptyInputValue(control.value)) {
  11:          return null;
  12:        }
  13:  
  14:        let value = control.value;
  15:  
  16:        if (!!replace && !!replace.length) {
  17:          replace.forEach(c => {
  18:            var regEx = new RegExp(c, 'g');
  19:            value = value.replace(regEx, "");
  20:          });
  21:        } else {
  22:          value = control.value.replace(/_/g, "");
  23:        }
  24:  
  25:        const length: number = value ? value.length : 0;
  26:        return length < minLength ?
  27:          {'minlength': {'requiredLength': minLength, 'actualLength': length}} :
  28:          null;
  29:      };
  30:    }
  31:  
  32:    static maxLength(maxLength: number, replace?: Array<string>): ValidatorFn {
  33:      return (control: AbstractControl): ValidationErrors | null => {
  34:        let value = control.value;
  35:  
  36:        if (!!replace && !!replace.length) {
  37:          replace.forEach(c => {
  38:            var regEx = new RegExp(c, 'g');
  39:            value = value.replace(regEx, "");
  40:          });
  41:        } else {
  42:          value = !!value ? control.value.replace(/_/g, "") : value;
  43:        }
  44:  
  45:        const length: number = value ? value.length : 0;
  46:        return length > maxLength ?
  47:          {'maxLength': {'requiredLength': maxLength, 'actualLength': length}} :
  48:          null;
  49:      };
  50:    }
  51:  
  52:    static isEmptyInputValue(value: any): boolean {
  53:      // we don't check for string here so it also works with arrays
  54:      return value == null || value.length === 0;
  55:    }
  56:  }
  57:  

So that's the validator. The simple way to use it is like this:
   1:    ngOnInit() {
   2:      this.someForm = this.formBuilder.group({
   3:        phoneNumber: [null, [Validators.required, MaskedLengthValidator.minLength(10), MaskedLengthValidator.maxLength(10)]]
   4:      });
   5:    }

And if you have placeholders in your mask other than underscores you can use it like this:
   1:    ngOnInit() {
   2:      this.someForm = this.formBuilder.group({
   3:        phoneNumber: [null, [Validators.required, MaskedLengthValidator.minLength(10, ['-']), MaskedLengthValidator.maxLength(10, ['-'])]]
   4:      });
   5:    }

This is pretty straightforward once you think about it. Now you don't have to think about.