Check out our Angular Book Series.

Use CSS To truncate a string with an ellipsis

My expertise in UI programming is creating reusable components, but less so on the design side of things. Every once in while I discover something interesting about CSS that I feel is worth sharing. Today I'll show you how to truncate a string with an ellipsis, magically.

First, let's create a few H1:


<h1>First Item</h1>
<h1>Second Item</h1>

The truncating will work on any HTML element, I just chose H1 for easy code. How add some CSS:


h1 {
width: 150px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}

First add a specific width. Then hide the overflow with overflow and prevent word wraps with white-space. Finally, specify the text-overflow to value ellipsis. This will force the text to truncate if it gets too long:

Play with the code over here.

How do you access the index of an *ngFor look in Angular?

Sometimes in Angular I have to loop over something in an HTML template. *ngFor is the best way to do that. Something like this:


<div *ngFor="let myValue of myArray">
{{ myValue.property }}
</div>

This is pretty common. I've used it to create a list of items that do not warrant a data grid type structure. I've used it to populate drop downs or to display arrays of images. There are lots of uses for this.

Sometimes, I need to access the index of the array inside the loop. I was using this because I wanted to show which item was selected, and was switching some CSS based on the selected item. How do you access the index of the array inside the loop?

Like this:


<div *ngFor="let myValue of myArray; let i=index">
{{ myValue.property }}; {{index}}
</div>

It is really simple to do, but just does not come up all that often as this is the less common case.

You can even access a bunch more values inside the loop including determing whether the item is the first first and last item, or if it is is an even or odd element. All these allow you to easily make tweaks to the display.

How do you deep copy an array with JavaScript?

The Problem

I was working on an Angular component that accepted an array input and created a drop down from it. As part of our internal architecture the component would add a disabled property to certain items on the input. This would permanently modify the input array, which was cached by the app and used elsewhere. We couldn't have that. We decided to make an internal copy of the array so that our local component changes would not affect the global data store. How do we do that?

Normally when I want to create a deep copy of an object, I turn to Try it here


const object1 = [
{value: 1},
{value: 2},
{value: 3}
];

const object2 = Object.assign({}, object1);

console.log(object1);

console.log(object2);

Take a look at the output:

If you look closely at the output between object 1 and object 2, you'll discover that object2 is no longer an array, it is an object. That was the wrong syntax and not what we intended.

Object.Assign() with an Array

We could use Object.assign() with an array instead of an object, like this:


const object3 = Object.assign([], object1);

That will give us an array. Make sure that they are separate objects:


object1.push({value:4})
console.log(object1);
console.log(object3);

That does solve the issue and we have an array:

It isn't the only way to solve the issue, though.

Use a spread operator

The spread operator will do the same thing.


const object4 = [...object1];
object1.push({value:4})
console.log(object1);
console.log(object4);

I always forget about the spread operator, but it works well. Depending where you look, there are concerns about it's performance over other options. I'm reusing the same screenshot because there is no change:

Use Slice

You can also create a deep copy of an array using array.slice():


const object5 = object1.slice();

object1.push({value:4})

console.log(object1);
console.log(object5);

Repeated screenshot because nothing changed:

This is the way I went with, for no other reason than it was used elsewhere in the code base and it pays to be consistent.

Hopefully you learned something. ;)

I got (mis)quoted in an article about Pokemon Go

I got mis-quoted in an article about Pokemon Go.

A friend of mine forwarded me the request from a site named Help a Reporter Out. I answered a bunch of questions about my experiences playing Pokemon Go. For posterity, I thought I'd share the full response I sent to Emily, the author.

1) Do you feel safe playing Pokemon Go? Why or why not?

I feel as safe as I would if I wasn't playing Pokemon Go. I've walked all over Connecticut playing the game. I've been on local parks and trails, from Old Sturbridge Village in Massachusetts to Disney World in Florida, all over New York City and up to the top of the Space Needle in Seattle. If I am in a place that is well lit with lots of people there is no worry of not being safe.

You do have to be aware so you don't bump into others. A few times I've stayed out on hiking trails way past dark. I wouldn't recommend that without proper equipment, regardless of whether you're playing Pokemon Go or not.

2) What is the strangest experience you've had playing Pokemon Go?

Last year, I discovered a fox den right off a local trail in CT. I just turned and looked at the right time and there were 3-5 baby red foxes jumping around and playing. I came back to visit at a safe distance many times until the snow came.

Another time I was almost run over by some guy driving a golf cart on a local trail. I had to act quick to not get run over. I reported him to the local park service since the trails are supposed to be vehicle free.

Other players, often strangers, are willing to walk up to you to ask questions or give advice such as "There is a `Houndoom` spawning over by the bridge on the other side of the park." Pokemon Go has taken away the embarrassment of approaching strangers by providing common ground to start a conversation. It is brilliant to see.

3) Has anyone ever found your location through the app?

Nope!

4) What is your favorite part about playing the game?

It is a reason to get up and exercise. I run a business out of my home, so it is really tough for me to get my "10,000 steps" without forced exercise time. This gamifies it a bit and gives me incentive to get up and go.


My impression of the article is that it has a negative connotation, trying to highlight how it can be dangerous to play Pokemon Go. I don't feel playing Pokemon Go is any more dangerous than not playing Pokemon Go.

This anecdote of mine that was quoted was embellished and I'm not a fan of that. First, they misquoted me by changing 'run over' to 'hit' when I spoke about the golf cart on the trail. The hard sound of 'hit' have a different impact than the smooth sound of 'run over'. I asked them to correct that.

They also misconstrued my statements to make it sound like the golf cart driver was playing Pokemon Go when he wasn't. They also embellished the story to have me jumping into the bushes which never happened. They aren't even any bushes on the trail I was on.

Definitely an interesting to be quoted in an article.

Why won't my Observable Trigger in a Unit Test?

I've been writing some tests as part of a project I'm working on, and for some reason I could not get a mocked Observable to resolve. This will explain the mistake I made and how to fix it.

The Setup

This component used a resolver to load data before the component loaded. To access a resolver's data, you inject the activated route into the constructor, like this:


constructor(private route:ActivatedRoute){}

In the ngOnInit(), you'd subscribe to the data property:


ngOnInit(){
this.route.data.subscribe((data: {myValue}){
this.myValue = data.myValue
}
);
}

This saves your MyValue data from the resolver into the local component instances myValue.

Writing The Tests

First, I mocked the ActivatedRoute:


class ActivatedRouteMock {
data: Observable<Data>;
}

Then, I created an instance of it:


const activatedRoute = new ActivatedRoutMock();

As part of the TestBed's providers:


{ provide: ActivatedRoute, useValue: activatedRoute}

The first test I wrote was to make sure the subscribe was actively working.

First, I created the observer and component:


const observer;
beforeEach(() =>
{
activatedRoute.data = Observable.create (observer) => {
observer = observer;
}
spyOn(activatedRoute.data, 'subscribe');
component = TestBed.createComponent(myComp).componentInstance;
});

Then, I ran the test:


it('should subscribe to activated route', () =>
{
expect(activatedRoute.data.subscribe).toHaveBeenCalled();
});

This worked great. Now, I wanted to test to make sure that other methods inside the component's result function worked. To do that I'll have manually trigger the observer:


it('should resolve route data and save myValue'. ()=>
}
const myValue = "something";
const results = {};
results['myValue'] = myValue;
observer.next(results);
observer.component();
expect(component.myValue).toBe(myValue);
});

This should work, right? Nope, it failed ever time. The observer was always null. I was scratching my head and adding a lot of breakpoints and outputs trying to figure out what I had done wrong so far.

What is wrong

The function to create the observable is never run until the observable is subscribed to. Clearly we are subscribing to it in the component code:


this.route.data.subscribe((data: {myValue}){
this.myValue = data.myValue
}

So, why doesn't it run? Think about it first, because spoilers are below.

The spy is getting in the way:


spyOn(activatedRoute.data, 'subscribe');

Because of the spy in place, the subscribe() function is intercepted, and as such never run, which never runs the function in the Observable's create function, which never saves the Observer. The solution was simple once I figured out what was going on:


spyOn(activatedRoute.data, 'subscribe').and.callThrough();

Tell the spy to intercept and watch, but still allow the call to go through.

Everything started working wonderfully. :-)

How do I switch between different NG Material Button Types?

ng-materal provides a bunch of button variants. Each button type is an HTML button that offers identical functionality, but with slightly different designs and colors. Here is an example of two buttons:


<button mat-flat-button>My Button 1</button>
<button mat-stroked-button>My Button 2</button>

If you drill into the button class, you'll see that all the button variants are implemented in the same code base, as directives.

I was doing a code review on a current project and saw that the code was conditionally determining which button type to display:


<button mat-flat-button *ngIf="condition">My Button 1</button>
<button mat-stroked-button *ngIf="!condition">My Button 1</button>

I was bugged by the fact that we were implementing two button components in our code for what was essentially a style change. That's when I started digging into the specific implementation and discovered that the primary difference between a mat-flat-button and a mat-stroked-button was style changes.

I proposed this alternate approach:


<button mat-button [ngClass]="condition?'mat-flat-button':'mat-stroked-button'">My Button 1</button>

We use the ngClass directive to conditionally flip the style between the flat-button and the stroked-button. It solved our issue with less code.

How do I display a Calendar Icon as part of the NG Bootstrap DatePicker?

I'm working on a project that uses ng-bootstrap, an Angular Library based on Bootstrap styles. I'm using the DatePicker and set it using their samples as a guide.

This is what I want to see:

But this is what I see:

Where is the fancy icon?

The CSS to create that graphic button is not in their base CSS for some reason. In past versions I've used an image. This time it takes some CSS Magic:


button.calendar, button.calendar:active {
width: 2.75rem;
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACIAAAAcCAYAAAAEN20fAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAOxAAADsQBlSsOGwAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAEUSURBVEiJ7ZQxToVAEIY/YCHGxN6XGOIpnpaEsBSeQC9ArZbm9TZ6ADyBNzAhQGGl8Riv4BLAWAgmkpBYkH1b8FWT2WK/zJ8ZJ4qiI6XUI3ANnGKWBnht2/ZBDRK3hgVGNsCd7/ui+JkEIrKtqurLpEWaphd933+IyI3LEIdpCYCiKD6HcuOa/nwOa0ScJEnk0BJg0UTUWJRl6RxCYEzEmomsIlPU3IPW+grIAbquy+q6fluy/28RIBeRMwDXdXMgXLj/B2uimRXpui4D9sBeRLKl+1N+L+t6RwbWrZliTTTr1oxYtzVWiTQAcRxvTX+eJMnlUDaO1vpZRO5NS0x48sIwfPc87xg4B04MCzQi8hIEwe4bl1DnFMCN2zsAAAAASUVORK5CYII=') !important;
background-repeat: no-repeat;
background-size: 23px;
background-position: center;
}

The image is no longer external, but created in-line. In my Angular project, I added this to the main styles.css to solve the problem.

How do I access Remote Services from the Angular CLI Dev Server?

I have been working on re-writing my Angular books for Angular 7. Along the way, I Decided it is time to finally ditch the build scripts for Angular that I wrote and move the text over to the Angular CLI. The Angular CLI has become a standard so it makes sense to base the books on that.

As part of this, I had to get the local dev server, localhost:4200, to load services from remote servers. I had to set this up for each separate service layer in the book. Thankfully Angular CLI includes proxy support for its server. That just means whenever the localhost:4200 makes a service request to a specific URL, I can redirect it to a different server where the real services lie.

When my UI code accesses:

localhost:4200/coldFusion

I want to redirect it to:

local10.angular.learn-with.com/coldFusion/A7

Coldfusion, Java, PHP, and NodeJS will not be running as part of the Angular CLI Dev server, so this allows me to keep services separate from the UI code, but still develop them together. For the purposes of this article, I'll show you how I set up the proxy for the ColdFusion services, however the same principles apply for whatever services you want to access.

Create the Proxy File

The first step is to create a proxy.conf.js file. First create a proxy-conf.js file and create a PROXY_CONFIG variable:


var PROXY_CONFIG = [{ }]
module.exports = PROXY_CONFIG;

The PROXY_CONFIG object is an array of different redirects. For this article, only one object is listed, which I'll use to define the redirect for the ColdFusion services.

Fist, I'll add the context:


context: [
"/coldFusion"
],

The context tells the server-side proxy to look for all calls to coldFusion and to process them. Next I'll add the target:


target: "http://local10.angular.learn-with.com",

The target tells us which server should be redirected to. Since I'm directing to a different server, I need to make sure to set the changeOrigin property to true:


changeOrigin: true,

You won't need this if your services also reside on localhost. Next set secure to false:


secure: false,

I never set up HTTPS certificates on my local environment for testing purposes, which is why this value is set to false.

Finally, I added a path re-write:


pathRewrite: {
"^": "http://local10.angular.learn-with.com/A7"
},

This is because a request to localhost:4200/coldFusion actually needs to redirect to http://local10.angular.learn-with.com/A7/coldfusion. The path rewrite rules affect this.

One thing that was very helpful when I was debugging this was setting the log level:


logLevel: "debug",

This provides a lot of details in the console. It took me some trial and error to get this working.

Tell Angular about the Config

Open up your angular.json file. Find projects.yourproject.architect.serve.options. It'll probably look something like this:


"options": {
"browserTarget": "lw:build",
},

Add a proxyConfig value to this options object so that Angular CLI knows to load the proxy config file:


"proxyConfig": "proxy.conf.js"

Now you can shut down your app, re-run ng serve and you should be good to go.

How do I uppercase the first letter of a word with CSS?

I was working on a project, and during a Pull Request review someone had written a TypeScript function to capitalize the first letter of each word. I said "It looks good to me" but one of my colleagues said "Hey, why don't we use CSS for this."

"CSS can do this?" Said Jeffry, blinking surprised.

It can, and it is super simple to do. CSS provides a text-transform property.

Try this text:


<div style="text-transform: capitalize;">the quick brown fox jumped over the lazy dogs</div>

Play with the Sample.

Sometimes I'm more surprised by the simple stuff I don't know than the complicated stuff.

How can you listen for a Browser Event in Angular?

This question recently came up in StackOverflow, and I thought I'd cross post my answer because I think I nailed this one.

The poster wanted to listen for an event in Angular. In the bulk of cases, I would do this in your HTML:


<button (click)="eventHandler($event)">

And this inside your component method:


eventHandler(event){
console.log(event);
}

It is simple, well documented, and Angular already 'wraps' the bulk of events dispatched up from your HTML page.

However, the poster really wanted to access the eventsin TypeScript. This is doable, but becomes a bit more complex.

First, add a #value to the HTML element in your HTML Template:


<button #myButton>

The #MyButton syntax allows you to reference the child in code using the @ViewChild metadata:


@ViewChild('myButton')
myButton: ElementRef;

Then you should be able to call the methods on the native element to access the event:


myButton.nativeElement.keydown();
myButton.nativeElement.keypress();
myButton.nativeElement.keyup();
myButton.nativeElement.blur();

If at all possible, I try to avoid drilling down into the ElementRef like this. I think I did it once when I created a reusable toast style component for a client, but for normal development I stick with the event directives.

More Entries

All Content Copyright 2005, 2006, 2007, 2008, 2009 Jeffry Houser. May not be reused without permission
BlogCFC was created by Raymond Camden. This blog is running version 5.9.2.002.