Check out our Angular Book Series.

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.

How to spy on a Property with Karma?

When writing unit tests, SpyOn, is used to watch and manipulate a method call, potentially returning your own data. This is used to easily mock external component calls so that your unit test can focus on the class in question without worrying about complications or side affects from external dependencies. I found myself in a situation where I had a class and that class had a read only get() property. I was creating my own Angular service wrapper for the Dragula drag and drop library. I was exposing some drop and remove listeners get methods.

I wanted to control the results returned from that get method. This was not a situation where I could set the underlying value because there was no set method. But, spyOn would not work because a get() method is viewed as a property, not a method. The class code was conceptually like this:


export class MyClass {

constructor(private dragula: DragulaService){
}

get dropListener(){
return this.dragula.drop('myDragulaGroup');
}
}

My code was using Angular, so the DragulaService was injected into this component, and the dropListener was exposed via a get.

For the purposes of unit testing, I want complete control over that drop listener Observable. Thankfully, spyOnProperties would give me that control.

The unit test would be a bit like this:


it("Should spy on Observable", () =>
{
const myClassInstance = new MyClass();

let dropObserver: Observable<any>;
const dropObservable: Observable<any> = Observable.create( ( ObserverArg: Observer<any>) => {
dropObserver= ObserverArg;
}

mySpy = spyOnProperty(myClassInstance, 'dropListener', 'get').and.returnValue(dropObservable);

I'm gonna take a quick break to explain spyOnProperty. It takes three parameters:

  • Class: The class instance that contains the property you want to watch.
  • Property: The name of the property being watched.
  • Accessor: The Access Type of the method being watched, in this case get.

Note that we are storing the results of the spyOnProperty in the mySpy variable. This is optional when using spyOn, but for spyOnProperty it is required. The unit test could would continue:


const val = myClassInstance.dropListener;

All that line does is access the dropListener property inside the class, which should trigger the spy. Now we can test against the access:


expect(mySpy ).toHaveBeenCalled();
});

In a more advanced unit test, we could subscribe to the dropListener inside the test, and use dropObserver.next() to trigger the result handler in an async block.

Can I create a Provider in Angular without listing it in the provider array?

This was something new I just discovered. You can create an Angular provider, as a singleton and do not have to list it in the provider array. This post will show you how.

The setup

To set up this project, I used the Angular CLI to create a new project. Then I created two services: ServiceOneService and ServiceTwoService. I also created two views: view1 and views2. You can find the code behind this project in this blog's GitHub repo.

Modify the main app.component.html template to include the two views:


<app-view1></app-view1>
<br/>
<app-view1></app-view1>

Create the services

Service1 should look something like this:


import { Injectable } from '@angular/core';
@Injectable()
export class ServiceOneService {
text = `from service one`;
constructor() { }
}

It is created as injectable, and has a single property. Open the app.module add this as a provider:


providers: [
ServiceOneService
],

Service2 should look something like this:


import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class ServiceTwoService {
text = `from service two`;
constructor() { }
}

The service looks almost identical, even having the same text property on it. The big difference is in the injectable metadata, we added a providedIn: 'root'. This is the trick to creating a provider without defining it as a provider. means that the first time the service is injected, Angular will create the Singleton just as if it were set up as an explicit provider.

The Views

Open up the TypeScript file for view1 and view2 and load the services:


constructor(public service1: ServiceOneService, public service2: ServiceTwoService) { }

Open up the HTML component for each view and paste this in there:


From Service 1, set up as traditional provider:
<input type="text" [(ngModel)]="service1.text" >
<Br/><Br/>
From Service 2, using providedIn = root:
<input type="text" [(ngModel)]="service2.text">

This just puts an input on the screen, one binding to the service1.text, and the other binding to service2.text.

Since we're using ngModel don't forget to load the import the FormsModule into the appModule:


imports: [
BrowserModule,
FormsModule
],

Run the Code

Now run the code and load it in a browser. When you type in the service 1 input in one component, the input in the other component should also change:

This is what is normal and we're used to because we set up Service1 as a provider and the single instance should be shared between the two components.

Now try to type in the service 2 input. The values also change:

This surprised me, since Service2 is not set up as a provider. However, the `providedIn` metadata tells Angular to create Service2 as a service. It works like magic.

Get The Full Source for this Sample.

How do I set up a Proxy Server with Angular?

This is an excerpt from my Learn With series on Angular 7, which came out about a month ago. I wrote this up for the DotComIt newsletter, and thought I'd share it here too.

This post details how to use Angular's server side proxy to connect the Angular CLI dev server to access the NodeJS application server without having to worry about cross domain issues. The Angular CLI dev server is not set up to run normal server-side code, like custom NodeJS, ColdFusion, PHP, or Java. Instead the preferred approach is to set up a server-side proxy to redirect your Angular calls to your other remote servers.

The first step is to create a proxy config file. Name the file proxy.conf.js and put it in the root directory of the project, where the angular.json file is located. It should start as an empty file, but let's create a constant:


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

The code also exports the configuration. The PROXY_CONFIG is an array of objects, although for our case we'll just have a single object. First, add a context:


context: [ "/nodejs" ],

The context value says "Whenever a URL comes in that contains `/nodejs`, we'll redirect it elsewhere. Exactly where you want to redirect it depends on your local configuration. Add a target property:


target: "http://127.0.0.1:8080/",

In the case of NodeJS, we're redirecting the request to a different port on localhost.

Set the secure to false:


secure: false,

In local dev environments I never set up HTTPS or use secure URLs like I would in a production environment. There are a few other values that may be needed or not depending on your local setup. I'll go over them here for completeness.

First, you can have the proxy to rewrite the path:


pathRewrite: { "^/nodejs": "" },

You'll only need a path rewrite if you need to modify the URL. For example, if your API will call '/nodejs/authenticate', but your actual service is set to '/authenticate', then this will allow you to direct your service call to the proper code.

Another value to be aware of is changeOrigin:


changeOrigin: true

You'll only need to use the changeOrigin value if you are setting up a local alias for your NodeJS sample. For example, if your NodeJS server is set to listen to requests on 'local.nodejs.learn-with.com' then this will tell the proxy it is okay to rewrite domain from localhost to 'local.nodejs.learn-with.com'.

The last value I want you to be aware of is the debug:


logLevel: "debug",

When you set this property, the console will give you a lot of details of how the proxy is working and rewriting URLs. It can be handy when you need to debug things.

Final Thoughts

I hope you're doing well in this holiday season.

Keep up to date by reading DotComIt's Monthly Technical Newsletter

How do I prevent a Browser from opening up when I run Angular unit tests?

When you run unit tests using the Angular CLI's ng test command you'll often see a browser open up. Sometimes this is useful if you want to debug the unit tests in the browser, however since I have moved the bulk of my unit test debugging to IntelliJ, this is just a minor annoyance.

You can shut it off. Open up the Karma.conf.js file in the src folder of your Angular CLI project. Find this line:


browsers: ['Chrome'],

This tells Karma to open Chrome to run the tests. But, there is another option:


browsers: ['ChromeHeadless'],

Rerun your tests and you'll find that no browser opens up.

I love it when things are easy.

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

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.