J.G.Sable
J.G.Sable

Reputation: 1408

Applying styles to string characters with ngClass function

I'm trying to write a function to be used within [ngClass] to apply a CSS class to certain characters within a larger string. which are returned from a 3rd party API.

Let's say the returned data is like this:

data = [
 { name: 'Johnny', description: "Johnny ate an apple."},
 { name: "Bobby", description: "Bobby hates apples."}
]

My HTML will loop through all objects in the data array and print them to the UI. BUT, I need to search each description property on each object and find out if there's an exact match and apply the style to just that word.

Therefore, I need to compare two strings, character by character.

Let's say the class is 'text-italics'. Which case the CSS would look like this:

.text-italics {
  text-transform: italics !important;
}

And my string is "Johnny ate an apple." from the description property of object one.

But I only want "apple" italicized. Not the "a" in 'ate' or the 'a' in 'an'.

Same for object two. "apple" of "apples" should be italicized. Not the "a" in "hates".

<ng-container *ngFor="let char of item.description | splitString ">
   <span [ngClass]="{'text-italics' : applyItalics(item.descripton, b)}">
     {{char}}
  </span>
</ng-container>

I'm attempting to write a function that would be used within [ngClass]. I'm thinking I'll have broken the strings down into character arrays with .split('') and then loop through each character in the HTML?

I'm playing around with a function like this, but once I know that the term the user searched for is in the termToCheckAgainst, I need to apply the style to each character that is an exact match:

applyItalics(nameOfData: string, termUserSeachedFor: string): boolean {
    nameOfData = nameOfData.toUpperCase();
    termUserSeachedFor = termUserSeachedFor.toUpperCase();
    return nameOfData.includes(termUserSeachedFor);
}

Upvotes: 1

Views: 692

Answers (2)

Ralph Ritoch
Ralph Ritoch

Reputation: 3440

If you want to style character by the character being part of a complete search term than you need to pass an index of the character in the description, such as let char of outerItem.description | splitString; let i = index; and use some algorithm to check the characters are part of the search term. Since we are normalizing to upper case, I use a lower case letter "a" for this.

    //our root app component
import { Component, NgModule, VERSION } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';

 import { splitStringPipe }          from './split-string.pipe';

@Component({
  selector: 'my-app',
  template: `
    <div>
      <dl>
      <dt>Term</dt>
      <dd><input [(ngModel)]="b" /></dd>
      </dl>

      <ng-container *ngFor="let outerItem of data">
    <ng-container *ngFor="let char of outerItem.description | splitString; let i = index;">
      <span [ngClass]="{'text-italics' : applyItalics(outerItem.description, i, b)}">
        {{char}}
      </span>
    </ng-container>
    </ng-container>
    </div>
  `,
})
export class App {
  data: object[];
  b: string;
  constructor() {
    this.b = "term";
    this.data = [
      { name: 'Johnny', description: "Johnny ate an apple."},
      { name: "Bobby", description: "Bobby hates apples."}
    ];
  };

  applyItalics(desc,i,b) {

     if (desc) {
      let normalDesc = desc.toUpperCase();
      let normalTerm = b.toUpperCase();
      let tTerm = normalDesc.replace(normalTerm,Array(normalTerm.length+1).join("a"));
      return tTerm[i] === 'a';
     }
     return false;
  }
}

@NgModule({
  imports: [BrowserModule, FormsModule],
  exports: [splitStringPipe],
  declarations: [splitStringPipe,App],
  bootstrap: [App],
})
export class AppModule {}

Here is the full example on Plunker https://embed.plnkr.co/plunk/pXhbQbZhDnM1jNZ7

Upvotes: 0

PeterS
PeterS

Reputation: 2964

You don't need to have two separate functions for this, just contain everything in the pipe:

@Pipe({
  name: 'wrapItalic'
})
export class WrapItalic {
  transform(content, word) {
    const splits = content.split(' ');
    let result = '';
    splits.forEach(next => {
        word === next ? result += ` <i>${next}</i> ` : result += ` ${next}`;
    });
    return result.trim();
  }
}

And your HTML:

  Test <span [outerHTML]="'Jonny ate an Apple' | wrapItalic: 'Apple'"></span>

It is possibly quicker as you're only doing the work (loop) once

Upvotes: 2

Related Questions