So you need to add Angular Material chips to your UI?

Okay, that's easy enough to do. Just copy and paste some code from the overview page

But you also need to bind those chips to a form field.

That doesn't seem to be on the overview page. 

So I'll show you how to do it here.

And when it's all over, you'll have something that looks like this:

And those entries will bind as a string array to a form field.

If you want to skip the reading, you can take a look at the code on GitHub.

Remember: if you're having problems running the Angular app locally, be sure to check the README.

Please note: this guide is a part of an ongoing series of tutorials on how to create a customer relationship management (CRM) application from scratch. The GitHub code for each guide belongs to a specific branch. The master branch holds the entire application up to the current date. If you want to follow the whole series, just view the careydevelopmentcrm tag. Note that the website displays the guides in reverse chronological order so if you want to start from the beginning, go to the last page.

The Business Requirements

Your boss Smithers walks into your office wearing not one but two face masks. 

When you give him a strange look, he yells: "Hey! It's a pandemic!"

Then he gets down to business.

"It's about that CRM app you're working on," he says. "We need users to tag contacts with keywords. You know, like they do in all the big-name CRM apps."

Smithers continues: "Words like: 'hot,' 'new,' and 'keeper.'"

You don't know of any reputable sales associate who tags people with "keeper" but you nod anyway.

"And we need it by tomorrow," Smithers says as he walks out of your office while putting on a third mask.

It's Basic

First thing's first: when you hear "tag," think "chip."

A chip in Angular Material is the solution you'll go with when implementing tags like you see in other CRMs. I think they call them chips because the word "tag" already has multiple meanings in software development.

Now that you know what you want to do, it's time to learn how to do it.

Start by editing BasicInfoFormComponent. That form's getting pretty busy so we might need to take a look at refactoring it in the not-so-distant future.

For now, though, go ahead and press on.

Add these properties towards the top:

  readonly separatorKeysCodes: number[] = [ENTER, COMMA];
  selectable = true;
  removable = true;

That's boilerplate stuff right out of the docs.

The separatorKeyCodes array represents the keys that users can press to "finish" entering a tag. In this case, the names speak for themselves: users can press the Enter key or the comma key.

The selectable property means the user can click on the tag (or chip) and it will highlight. It's set to true above, but for this guide nothing happens when the user clicks on an existing tag (except the highlight).

The removable property set to true means the user can delete tags by clicking that pretty "x" just to the right of the text in the tag.

Next, update the FormGroup object:

    this.basicInfoFormGroup = this.fb.group({
      'firstName': [this.contact.firstName, [Validators.required, Validators.pattern('^[a-zA-Z \-\]*$')]],
      'lastName': [this.contact.lastName, [Validators.required, Validators.pattern('^[a-zA-Z \-\]*$')]],
      'email': [this.contact.email, [Validators.pattern("^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}$")],
      [this.emailExistsValidator()],
        'blur'
      ],
      'source': [this.contact.source, [Validators.required]],
      'sourceDetails': [this.contact.sourceDetails, [Validators.pattern('^[a-zA-Z0-9 \-\]*$')]],
      'status': [this.contact.status, [Validators.required]],
      'lineOfBusiness': [this.contact.linesOfBusiness],
      'authority': [authority],
      'title': [this.contact.title, [Validators.pattern('^[a-zA-Z \-\]*$')]],
      'account': [(this.contact.account ? this.contact.account.name : ''),
        [this.accountValidator(), Validators.required, Validators.pattern('^[a-zA-Z., \-\]*$')]],
      'tags': [(this.contact.tags) ? this.contact.tags : []]
    });

The only change there is at the very bottom. Make note of that 'tags' form control.

Remember, though: tags need to be stored as an array. So the code above uses a ternary to check if the tags property on the Contact object is null. If so, it creates an empty array. If not, it uses the value of tags from the object.

Next, create a method for adding a tag:

  addTag(event: MatChipInputEvent): void {
    const input = event.input;
    const value = event.value;

    if ((value || '').trim()) {
      if (value.length < 21) {
        this.basicInfoFormGroup.controls['tags'].setValue([...this.basicInfoFormGroup.controls['tags'].value, value.trim()]);
        this.basicInfoFormGroup.controls['tags'].updateValueAndValidity();
      }
    }

    // Reset the input value
    if (input) {
      input.value = '';
    }
  }

That method accepts a MatChipInputEvent object That's the field (and its value) that the user uses to type in a new tag.

The input constant is the input text field and the value constant is the value of the input (whatever the user typed in).

Next, the code makes sure there's an actual value to add to the array of tags with that first if check. 

The inner if check makes sure that the input doesn't exceed 20 characaters. We don't want people putting in whole sentences as tags.

Once the input passes those two checks, it's added to the form field array.

Finally, the value of the input field is reset to an empty string so the user can add another tag.

And here's the code to remove a tag:

  removeTag(tag: string): void {
    const index = this.basicInfoFormGroup.controls['tags'].value.indexOf(tag);

    if (index >= 0) {
      this.basicInfoFormGroup.controls['tags'].value.splice(index, 1);
      this.basicInfoFormGroup.controls['tags'].updateValueAndValidity();
    }
  }

Nothing too complicated there. The code just checks the form field for the existence of the tag and removes it from the array.

Template in a Teapot

Now update the corresponding template. Add this markup:

    <div class="vertical-form-field">
      <div class="label">Tags</div>
        <div>
          <mat-form-field appearance="fill" class="no-label-field" fxFlex="50" fxFlex.lt-md="100">
            <mat-chip-list #chipList aria-label="Enter tags" formControlName="tags">
              <mat-chip *ngFor="let tag of basicInfoFormGroup.controls['tags'].value" [selectable]="selectable"
                        [removable]="removable" (removed)="removeTag(tag)">
                {{tag}}
                <mat-icon matChipRemove *ngIf="removable">cancel</mat-icon>
              </mat-chip>
              <input placeholder="Add tag & hit Enter"
                     [matChipInputFor]="chipList"
                     [matChipInputSeparatorKeyCodes]="separatorKeysCodes"
                     [matChipInputAddOnBlur]="addOnBlur"
                     (matChipInputTokenEnd)="addTag($event)">
            </mat-chip-list>
          </mat-form-field>
        </div>
    </div>

Again, most of that is boiler plate stuff that's copied and pasted from the docs.

But hone in on the *ngFor line in the <mat-chip> element. That's referencing the array in the form field and not an array from an object property. 

Make sure you follow suit. Because that's not what the docs tell you to do.

Also, the form control name is added to the <mat-chip-list> element. You might be tempted to add it to the <input> element but I can assure you that won't work.

Going the Extra Mile

Now you've made it possible so users can add tags to contacts. But that doesn't do a whole lot of good if they can't see the tags when they view the contacts in the app.

Let's take care of that. Edit view-contact.component.html. Add this block of code:

<div fxLayout="row wrap" *ngIf="contact.tags">
  <div class="column-layout">
    <div class="column-header">
      Tags
    </div>
    <div style="margin-top: 10px">
      <span *ngFor="let tag of contact.tags" class="normal-detail">
        <span class="tag-background">{{tag}}</span>&nbsp;
      </span>
    </div>
  </div>
</div>

That code iterates through all the tags in the Contact object's tags array and prints them out one-by-one in a <span> element. 

That &nbsp; you see at the end of the inner <span> element forces a space between the tags.

The inner <span> also uses a CSS class called tag-background. Here's what it looks like:

.tag-background {
  background-color: #f0f0f0;
  padding: 2px 10px 2px 10px;
}

That just gives each tag a grey background with some padding around the edges. 

When you see it in action, the output looks like this:

And that's exactly what you're looking for.

Wrapping It Up

Now you know how to add chips to your forms and bind them to a field. Feel free to take what you've learned here and make it your own.

If you want to see more of the code in action, feel free to take a look at the source on GitHub.

Have fun!

Photo by samer daboul from Pexels