Check out our Angular Book Series.

Creating Angular Directives Part 2 - Using Renderer2

I'm updating my Learn With tutorial books that teach Angular Development for Angular 8. I decided to do a deep dive into Angular Directives. This series of posts is a snippet of what will be in the full series, and is focused on creating attribute directives.

Check out Part 1, with project setup and a starter directive. We created a directive named reverse which will toggle CSS Between two separate states.

Using Renderer

If you read the docs on the ElementRef's nativeElement property, there is a big warning to avoid using it. It breaks encapsulation by accessing the DOM directly, and can lead to unexpected results in the application.

We're going to use the Renderer2 component instead. It is designed to give a layer of abstraction between Angular and the DOM within directives like this. While we're at it, I'm also going to switch to using a CSS Class instead of accessing styles directly.

The first step is to inject the Renderer2 into the directive's reverse.directive.ts file:


constructor(private renderer: Renderer2, private el: ElementRef) { }

The reversedState property remains unchanged, but we will want to modify the changeState() method. This is the current method:


changeState() {
if (this.reversedState) {
this.el.nativeElement.style.backgroundColor = '#000000';
this.el.nativeElement.style.color = '#ffffff';
} else {
this.el.nativeElement.style.backgroundColor = '#ffffff';
this.el.nativeElement.style.color = '#000000';
}
}

This uses the nativeElement to change the DOM directly. Widely considered a bad a practice.

Change it to use the renderer2 to add and remove classes:


changeState() {
if (this.reversedState) {
this.renderer.removeClass(this.el.nativeElement, 'regular');
this.renderer.addClass(this.el.nativeElement, 'reversed');
} else {
this.renderer.removeClass(this.el.nativeElement, 'reversed');
this.renderer.addClass(this.el.nativeElement, 'regular');
}
}

Instead of drilling down into the nativeElement directly, we're calling the addClass() and removeClass() methods on the renderer, and passing in the nativeElement along with the new style. We could have done the same thing with the previous sample, using addStyle() and removeStyle() methods on the renderer.

Let's be sure to add our regular and reversed styles to create-attribute-directive.css:


.regular {
background: #ffffff;
color: #000000;
}

.reversed {
background: #000000;
color: #ffffff;
}

Rerun the code:

Then click the button:

There are no change from the previous implementation, we just have a different, better, implementation under the hood.

What's Next?

Part 3 of this series will show you how to use properties in your component so that you do not have to hard code styles inside it.

Creating Angular Directives Part 1 - A Simple Directive to Toggle State

I'm updating my Learn With tutorial books that teach Angular Development for Angular 8. I decided to do a deep dive into Angular Directives. This series of posts is a snippet of what will be in the full series, and is focused on creating attribute directives.

What is an Angular Directive?

An Angular directive is just like an Angular component, except with no template. A directive can be applied to any HTML Tag or template. There are two types of directives:

  • Attribute: An Attribute directive will apply something to an existing HTML Element, or even your own Angular component.
  • Structural: A Structural directive will modify the HTML body by creating, or removing, elements.

This series of posts will focus on creating an attribute directive.

Setup

First, create a new project with the Angular CLI:


ng new

You can follow through the instructions at your discretion, but you'll go through a wizard like this:

I named the project ud, for Understanding Directives. I set up routing and used regular CSS.

In the app.component.html file, delete everything except the router-outlet, so the file looks like this:


<router-outlet></router-outlet>

You can run the app, but you won't see much until we start adding routes.

Create the Attribute Directive

We're going to create a directive that will toggle an HTML Element between two separate visual states, done by changing different CSS elements on the directive.

First, we'll do some setup. Create a component for creating our own attribute directive:


ng generate component create-attribute-directive

You'll see something like this:

Open up the app-routing.module.ts file and a sample2 route to the Routes array:


const routes: Routes = [
{path: 'sample2', component: CreateAttributeDirectiveComponent},
{ path: '**', redirectTo: '/sample2'}
];

Now, use the Angular CLI to create our first directive:


ng generate directive directives/reverse

You'll see something like this:

I put the new directive in the directive directory. If nothing is specified it will be put in src/app by default, but long term I like to keep my code better organized without a lot of things in the main app directory. Open up the reverse.directive.ts:


import { Directive } from '@angular/core';
@Directive({
selector: '[appReverse]'
})
export class ReverseDirective {
constructor() { }
}

First, we import the Directive class. This is metadata used to define the directive. The only thing we define, at this point in the metadata is the selector. We'll use this when we apply the directive to an HTML element, the Angular module, something like this:


<button appReverse></button>

Right now, the directive doesn't do anything, but we'll come back to this in the HTML template later. It is strongly recommended you do not name your custom directives with a prefix ng because that is used by the Angular framework and we want to avoid conflicts with other existing directives.

I want to note that the directive is loaded in the main Angular module. Open up app.module.ts and you'll see it in the declarations section:


@NgModule({
declarations: [
AppComponent,
UseAttributeDirectiveComponent,
CreateAttributeDirectiveComponent,
ReverseDirective
],
// other stuff
})

If you do not add your directive to the @NgModule your app won't know how to find it and you'll get compiler errors.

The first thing we want to do is inject the ElementRef into the directive class:


constructor(private el: ElementRef) { }

Dependency Injection for Directives works just like it does for other components, so we inject the value into the constructor. This will give the directive class a reference to the component which is was placed on.

This directive needs to pass in a value of the directive name, and then make changes to the ElementRef's styles based on that. To do that we'll need to define an Input() metadata. The simplest way to do this:


@Input('appReverse') reversedState: Boolean;

When the directive sees the appReverse directive applied to an HTML tag as an attribute, the value will be set in the reversedState value inside the directive class. In order to run some code whenever that value changes I'm going to switch it to a get/set method:


private _reversedState = false;
@Input('appReverse')
get reversedState() {
return this._reversedState;
}
set reversedState(input) {
this._reversedState = input;
this.changeState();
}

The get and set methods are pretty standard. A private variable holds the state. The get method will return that private variable, and the set method will set it. The set method also runs a changeState() method, so let's look at that:


changeState() {
if (this.reversedState) {
this.el.nativeElement.style.backgroundColor = '#000000';
this.el.nativeElement.style.color = '#ffffff';
} else {
this.el.nativeElement.style.backgroundColor = '#ffffff';
this.el.nativeElement.style.color = '#000000';
}
}

This method checks the reversedState value and sets the background and foreground colors. I use nativeElement property on the ElementRef to drill down into the component and set specific styles. For the moment, I'm hard coding the color values, but we'll revisit that in a later sample.

How do we use the directive? Open the create-attribute-directive.component.html file:


<button (click)="reverseState()" [appReverse]="reversedState">
Reverse State
</button>

The appReverse directive is placed directly on the button; and it is given the value of reversedState, which is a Boolean value in the component that we'll create. The click button is used to call a reverseState() method that will toggle the reversedState property.

Open the create-attribute-directive.ts file. First create the reversedState property:


reversedState = false;

Then add the reverseState() method:


reverseState() {
this.reversedState = !this.reversedState;
}

We've seen this method, and property, set up identically in the previous sample. The major change is in how the new styles are applied to the button.

Rerun the app:

Click the button and the state should switch:

Play with it to switch the state back and forth.

Congratulations you did it!

What's Next?

While this is a simple example, it does show that we can create our own directive and use it to affect the HTML element is is applied to. This type of example is pretty common when you search for directive tutorials throughout the web, however by accessing the nativeElement directively it creates a dependency between your directive and the internals of Angular which may change.

The next article in this series will show you how to address this using the Angular renderer.

How do I redirect to a different domain inside Angular?

I was doing a code review for a colleague,and as part of his code he was redirecting the browser:


window.location.href = "http://www.google.com";

This code was working perfectly fine, but when performing a redirect to another domain in an Angular component, there is a `more angular` way to make this work by using the Document service.

First import this:


import {DOCUMENT} from '@angular/common';

Then you need to inject that into your component:


constructor(
@Inject(DOCUMENT) private document: Document
) {}

This inject syntax is a bit different than a normal Angular service because it uses the Inject metadata.

Once the document is injected, you can redirect like this:


this.document.location.href = 'http://www.google.com';

This is just an Angular layer of abstraction over the native JS way to perform a redirect; but when working within an Angular component I like to lean on the Angular abstractions. They help with unit testing purposes.

Get the code here.

How do I change the URL in an Angular Application without reloading the Route?

I'm working on an Angular app, as I often do and wanted to change the the route in the URL without reloading the application. In essence, the user has just created something, we wanted to redirect the user to the edit screen, with all the data loaded. The catch was that the edit screen and create screen were the same exact component, just with different data loaded. And we didn't need to load new data because the user just entered everything; and any database specific data like a primary key was just returned from the service. How can you do that? Use the the Location lib from the Angular library. I'm going to show you how.

The Setup

First, create a new Angular application with routing. Then create a view component--I Called mine view1.

Change the main app.component.html to contain the router outlet:


<router-outlet></router-outlet>

And add these routes to the routing module:


const routes: Routes = [
{
path: 'new',
component: View1Component
},
{
path: 'edit',
component: View1Component
}
];

We're going to load up the new route, and then redirect to edit on the click of a button.

Redirect

In view1.component.html, add a button:


<button (click)="onSwitch()">Change URL</button>

When this button is clicked, we'll perform the URL change without loading the new route.

Move to the view1.component.ts file. First import the Location from @angular/common:


import { Location } from '@angular/common';

This is important, because for some reason IntelliJ is not importing the Location object properly, and nothing works if the import is missing, or worse lost.

Location is an Angular service, so inject it into the view1 component:


constructor(private location: Location) { }

Now, in our onSwitch, use the replaceState method:


onSwitch() {
this.location.replaceState('/edit');
}

This should work:

Play with the sample here.

How do you override a method in JavaScript?

I'm working on an experimental project and wanted to find out if I could override a method in JavaScript. I wanted to see if I could override the JSON.stringify() method to add my own functionality. It turns out you can and it is pretty easy.

First, let's create an object we want to convert to JSON:


let myObject = {
foo: "bar",
foo2: "bar2",
foo3: "bar3"
}
console.log('------ JSON.stringify output ---------');
console.log(JSON.stringify(myObject));

Output that to make sure all is good:

Now, let's may a backup copy of JSON.stringify:


let myJSONStringifyCopy = JSON.stringify;
console.log('------ My Copy output ---------');
console.log(myJSONStringifyCopy(myObject));

This is just a new variable that references JSON.stringify() and we can use it just as we would JSON.stringify():

Okay, but now we want to replace JSON.stringify(). Create our own method:


function myOwnReplacement(value) {
return "faker";
}
JSON.stringify = myOwnReplacement;

console.log('------ JSON Stringified Replace ---------');
console.log(JSON.stringify(myObject));

console.log('------ My Copy output ---------');
console.log(myJSONStringifyCopy(myObject));

Will overwriting JSON.stringify also overwrite our myJSONSTringifyCopy? Nope as these dumps prove:

Play with the sample here.

In the case of this replacement it isn't very useful, especially since the resulting output is no longer JSON. But, I'm working on something slightly more interesting that I hope to be able to share at some future point.

Note: I do not endorse doing this to your friend's application on April Fools day, unless you really want to.

Why didn't my Angular View Update when I added items to the input array?

I was recently working on an Angular project where the view had an input array:


@Input() items: Array<items>;

The view of the component would loop over the array:


<div *ngFor="let item of items">
{{ item.data }}
</div>

At some point in the app, user interaction happens, and the app needs to load more data. We had a result handler from a service call doing something like this:


items.push(...itemsToAdd)

If you output the results, you'll see that the items array had the new items. Try it here.

However, the view would not update the display to reveal the new items in the array. How come?

In short, the view did not know that it needed to update itself. The view contains a pointer to the array and was watching for changes to that pointer. While the items in the array changed, the array pointer did not. An easy solution was to change the way items were added to the new array:


items = items.concat(itemsToAdd)

Since we were updating the actual input value, the view would know that the item had changed, triggering a redraw of the screen.

That's how we handled it!

How to fix the "definition not supported by current JavaScript version" error in IntelliJ

I put together this screencast to show you how to fix the 'not supported' error in IntelliJ that I often see when creating new projects.

How do I get an Array of all the Keys in a JavaScript Map?

I'm working on an Angular application and want to loop over all the keys in a JavaScript map. A map is a key value pair data type, like an object, but geared for dynamic properties. In the ColdFusion world we called this a structure, and in ActionScript we called it a dictionary, but the concept is the same.

I can create a map and add items to it, like this:


let myMap = new Map();
myMap.set('foo', 'bar');
myMap.set('bar', 'Barbar');
myMap.set('foobar', 'BarbarFooFoo');

You can look at the map like this:

Once it has a set of keys, I can get the keys using this:


myMap.keys();

This is an object, or more specifically an iterator. But, I want the keys as an array.

One trick is to convert the iterator to an array using Array.from():


Array.from( hydratedImageMap.keys() );

A more 'ES6' technique would be to use the spread operator:


[ ...hydratedImageMap.keys() ]

You'll see similar output:

Not all that hard! Get the code here or play with it here.

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. ;)

Why won't my Template Strings work?

As you probably know, I have been doing a lot of work with Angular, TypeScript, ES6, and the surrounding ecosystem. I've started using template strings a bunch.

This was the old way to create a string from many variables:


myNewValue = "http://" + myDomain + myPath + "?value=" + myValue;

It worked but got tedious. ES6 introduced something called template strings. Instead of using all those plus signs to concatenate variables, we can do use a friendly syntax:


myNewValue = `http://${myDomain}${myPath}"?value=${myValue}`;

I've read about this, done a few proof of principle samples, but for the first time I'm actually using this on a project and integrating it with code.

These are the two mistakes I make most common.

Use the Grave Accent

A template string must be enclosed in a grave accent, or backtick. This is the same key you use when formatting inline code in a StackOverflow comment or inside Slack. If you use double quotes or regular single quotes, your string will not be recognized as a template string and the variables will not turned into real actual values.

This works:


`http://${myDomain}${myPath}"?value=${myValue}`

But, this will not:


"http://${myDomain}${myPath}"?value=${myValue}"

Nor will this:


'http://${myDomain}${myPath}"?value=${myValue}'

I picked this one up the hard way after a particularly frustrating debug session wondering why all my unit tests suddenly broke.

Use Curly Brackets, not Parenthesis

The values that you inject in the string must be surrounded by curly brackets, like an object in JavaScript. For some reason my mind wants to use parenthesis, as if calling a function.

This works:


`http://${myDomain}${myPath}"?value=${myValue}`

But, this will fail miserably:


`http://$(myDomain)$(myPath)"?value=$(myValue)`

Despite these two minor roadblocks, I'm finding a lot of value in template strings because the new way of string concatenation is a lot easier to read than the old way.

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.