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 I test the rxjs debounceTime() function?

I'm working on an Angular application, and sometimes we use the debounceTime() function. When a user is typing in a text box we may want to trigger a service call based on the characters we type. We use debounceTime() to make sure that user has finished typing for a certain length of time when the service call occurs. Our code might be something like this:


this.input.valueChanges.pipe(
debounceTime(300)
).subscribe( value =>
{
// do something
this.newValue = value;
});

The input here is most likely an element in a reactive form.

This says that when the observable is resolved, we wait 300ms before doing something. In that time, if the user types more characters, those characters overwrite the original ones, and we start waiting again. IF there are 300 milliseconds without any more typing, we move forward to the subscribe and do something.

This presented a problem when testing. Here is a sample psuedo test code:


it('should execute some code', () =>
{
input.setValue('some new value');
expect(newValue).toBe('some new value');
});

The problem is that the value is set and the expect is checked before we waited 300 milliseconds, and as such the test fails.

The solution is to run the test in a fakeAsync block and make use of the tick() function.

This should fix it:


it('should execute some code', fakeAsync(() =>
{
input.setValue('some new value');
tick(300);
expect(newValue).toBe('some new value');
}));

The tick() function tells the code to wait, or pause, for 300 milliseconds before proceeding. This is the magic that made the tests like this work.

Warning: I wrote all this code in the browser, so is untested and may have some minor syntax errors; but the approach should be solid.

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 do I access Query Parameters in Angular?

When creating web applications, it is common to add data to a URL that you need to access. For example, one might build an app with a list view that shows a lot of things, such as superheros. Then they have a detail view which shows an expanded detail of a single superhero, such as aquaman.

This post has nothing to do with superheroes. It is all about how to access that item in the URL from an Angular app.

The Setup

I created a default angular application with routing enabled. I created one route, named view1:

const routes: Routes = [
{ path: 'view1/:value', component: View1Component },
{ path: '**', redirectTo: 'view1/default' },
];

The route has a single query param, named value. That is what we'll try to access. The route directs to a new component, which you can create with the Angular CLI:


ng generate component view1

Make sure to replace the main app.component.html with the router outlet:


<router-outlet></router-outlet>

One more thing to do. Open up the view1.component.ts file. We'll need to inject the ActivatedRoute into the component:


constructor(private activatedRoute: ActivatedRoute) { }

You'll also need to import this:


import {ActivatedRoute} from '@angular/router';

You can find the full source here. Let's look at what it takes to access the query param.

Option 1: Params

I set up all these examples in the ngOnInit() method of the View1Component. This is the first:


this.activatedRoute.params.subscribe((params) =>
{
console.log(params.value);
});

The ActivatedRoute has a params Observable. We can subscribe to it, and when it resolves we can access the values. The params object sent to the subscribe function is just an object:

It is a collection of name value pairs and we can access our value using params.value.

Option 2: paramMap

The second option is to access the paramMap. This is also done in the ngOnInit() method against the activatedRoute:


this.activatedRoute.paramMap.subscribe((params ) =>
{
console.log(params['params'].value);
console.log(params.get('value'));
});

paramMap is an observable that resolves to a paramMap. Instead of a generic object, it uses the JavaScript map:

Using a map is a more modern, or ES6, way to handle things instead of using a generic object. The map is better suited for a collection of key value pairs, because it provides an API for accessing values instead of something easily changed.

Option 3: Router Snapshot

The final option I'll show today is to access the snapshot of the activated route. The snapshot mirrors a lot of similar properties to the ActivatedRoute. The main difference is that the snapshot is the route's values at a certain moment in time, whereas the activatedRoute is not. That is why the params, or paramMap, are Observables in the ActivatedRoute, but actual values in the snapshot.

Access the params on the value like this:


console.log(this.activatedRoute.snapshot.params.value);

You'll see something like this:

You can also get at the paramMap, value:


console.log(this.activatedRoute.snapshot.paramMap.get('value'));

You'll see something like this:

Overall, this is similar to using the Observables, except without the Observable part. When accessing the activatedRoute in the ngOnInit, the snapshot should be identical. But, if you need to access something from an unrelated route, or are in the process of switching routes, that is why you'd access the snapshot.

How do you check if a URL variable exists?

When I envisioned this post, I thought I'd write about how to tell if a URL variable exists, but I realized the meat of the post was about finding and accessing the URL variables. If you need to check to see if a value exists, I recommend using the paramMap. For a value that does exist:


if (params.has('value')) {
console.log('it has value');
} else {
console.log('No value');
}

And for a value that won't exist in our demo:


if (params.has('noValue')) {
console.log('it has a novalue');
} else {
console.log('No noValue exists');
}

We use the has() function the params map to check for the existence of the key.

Final Thoughts

Don't forget to grab the code behind this post for runnable samples.

I'm not sure why there are so many ways to get at this data; my preference is to use the paramMap. What about you?

How do I use a set method to access an conditionally created ViewChild in Angular?

In last week's post I spoke about the problems accessing an Angular ViewChild if it is conditionally created using *ngIf. The solution I proposed there was to use the hidden directive instead of *ngIf. This works, but the caveat is that the child is always created as part of the DOM even if it is not always displayed. I wanted to do another post, providing a different approach

The Setup

I'm going to modify the same application I built last time. It was a simple application created with the Angular CLI and I added one child component. You can get the full code here.

Creating the View

This time, I'm adding a third sample to the app.component.html template:


<button (click)="child3Hidden = !child3Hidden">Toggle Sample 3 Visibility</button>
<br/>
Value Passed into Child: <input type="text" (keyup)="onSample3Change($event)" />
<br/>
<app-view1 #child3 *ngIf="!child3Hidden" ></app-view1>

It uses a value, child3Hidden, to toggle the visibility of the child. Create that in app.component.ts:


child3Hidden = true;

There is an input which is used to change the input value for our app-view1 component:


onSample3Change(event) {
this.child3.value = event.currentTarget.value;
}

This just takes the text input from the AppComponent view and passes it to the ViewChildReference. This is all a similar setup to what we've seen before.

The Problem

Since this ViewChild is conditionally created with *ngIf we cannot access it inside the ngOnInit() and will get an error. The ngOnInit() is like this:


ngOnInit() {
console.log(this.child3);
}

You'll see something like this:

Just an error because the default state of the ViewChild is undefined.

A Solution

Instead of trying to access the ViewChild inside of ngOnInit() we can define the child using get and set methods, like this:


@ViewChild('child3')
set child3(value: View1Component) {
this._child3 = value;
console.log(this.child3);
}
get child3(): View1Component {
return this._child3;
}

Whatever code we want to run in ngOnInit() can be run in the set method, which will get toggled every time the child is created or destroyed. I clicked the button a bunch of times to show you the output:

It is an alternate solution, which works pretty well. The trick part is that whenever you access the ViewChild you'll have to do an undefined check with this approach, something you would not have to do if it were hidden.

Get can get the full code here.

How to Access a Conditionally Created @ViewChild in Angular ngOnInit?

I've been working on an application using Dragula for drag and drop, and dom-autoscroller for scrolling while dragging.

To work, the autoscroller library needs access to the ViewChild, usually set up in ngOnInit. My problem was that we were using *ngIf to hide the scrollable container until things were added to it. As such, the ViewChild was not defined at the time that ngOnInit() ran. This post will explain the problem, and how we solved it.

The Setup

To demonstrate the problem, and our solution, I created a default project with the Angular CLI and added a single component, view1.

The view1 component accepts a single input:


@Input()
value = `default value`;

And the HTML template outputs the value:

view1 works with {{value}}

It is a simple setup. You can get the full code at the Github Repo for my Blog samples.

The Problem

In the app.component.html, add the view child:


<button (click)="child1Hidden = !child1Hidden">Toggle Sample 1 Visibility</button>
<br/>
Value Passed into Child: <input type="text" (keyup)="onSample1Change($event)" />
<br/>
<app-view1 #child1 *ngIf="!child1Hidden" ></app-view1>

The child is hidden based on the child1Hidden value, so create that in the app.component.ts:


child1Hidden = true;

I use a method to update the component's input value. Create as a ViewChild:


@ViewChild('child1') child1: View1Component;

And add the method:


onSample1Change(event) {
this.child1.value = event.currentTarget.value;
}

Note: I know this might be better implemented using HTML properties to update the value, but not all ViewChild interactions are that simple.

Inside the component class, I also want to implement OnInit interface:


export class AppComponent implements OnInit {

And add the ngOnInit() method associated with the interface:


ngOnInit() {
console.log(this.child1);
}

Because the child is hidden by default, this.child1 will be undefined at the time ngOnInit() is run:

Changing the text value before making the child visible will also return errors because our code is accessing an undefined ViewChild reference:

But, once you enable the underlying component:

We are good to go.

The question remains, how do we handle initialization code for that component? I have two solutions, one I'll cover in this post and the second in the next.

Use Hidden

We can replace the *ngIf with the hidden attribute to make this work. Modify our template code:


<button (click)="child2Hidden = !child2Hidden">Toggle Sample 2 Visibility</button>
<br/>
Value Passed into Child: <input type="text" (keyup)="onSample2Change($event)" />
<br/>
<app-view1 #child2 [hidden]="child2Hidden" ></app-view1>

This creates a parallel, similar implementation. It uses child2Hidden instead of child1Hidden. Define it in the app.component.ts:


child2Hidden = true;

Also add the OnSample2Change() method, which updates the input value of the child:


onSample2Change(event) {
this.child2.value = event.currentTarget.value;
}

Be sure to define child2 as a formal ViewChild:


@ViewChild('child2') child2: View1Component;

And output child2 inside ngOnInit():


console.log(this.child2);

Rerun the app:

There are no errors in the console even though the view is not shown.

The reason this works is that when you conditionally show or hide a component using *ngIf Angular removes, or adds it, to the DOM. So, if the value is false, the ViewChild is undefined because the component does not exist. When we use the hidden directive, the component may be hidden from view, but is still created as part of the DOM.

Final Thoughts

I have mixed feelings about using hidden to get around the problem because I am not convinced that it is a good idea to create every child as part of the DOM if it is not needed or being used at the moment. Next week I'll show you a different approach.

You can get the Get can get the full source code, and a peak at next week's sample.

How to access a Constant in an Angular View Template?

Angular provides a mechanism for sharing instance variables inside a component with that template's view component, but how do you access a constant in a view template? Constants in TypeScript are not created as instance variables on a class, so they are not easily accessible inside the HTML template. This post will show you how.

The Setup

Create a new project with the Angular CLI. You can also get access to the full source code in my Blog Sample Github Repo.

Create the Constant File

Create a file in the root directory named constants.ts. Add in a single constant:


export const MY_TEST_CONSTANT = `Something`;

This is pretty simple.

Load the Constant in the Component

The trick to access the constant's value inside the view template is to copy the Constant value to an instance variable in the component. Open up the app.component.ts on your new project, and add a line like this:


myConstant = MY_TEST_CONSTANT;

You'll also need to import the constant:


import {MY_TEST_CONSTANT} from './Constants';

Update the View Template

Now the View Template can access the constants value. Open up the app.component.html file, ad add something like this:


<strong>Here is your Constant</strong>: {{myConstant}}

You're no longer referencing a global constant, but rather a component instance variable that has the same value as the global constant.

It works:

You can use the myConstant value as part of any Angular element in the HTML template including *ngIf, *ngFor, ngModel, or whatever you need.

Don't forget to check out the full code.

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.