How to Watch Scope Properties in Angular with TypeScript

There's more than one way to watch a property on your scope in Angular. I'll show you the best way to setup a watch using TypeScript.

For this demo I’ll be opening and closing a door and watching the isOpen property for changes. Here is the very simple Door class:

class Door
{
	isOpen: boolean;
	
	open(): void
	{
		this.isOpen = true;
	}
	
	close(): void
	{
		this.isOpen = false;
	}
}

I’m going to assume you already know how to setup an Angular controller using TypeScript. Here is the controller we’ll be using:

class HouseController
{
	frontDoor: Door;
	
	static $inject = ['$scope'];
	constructor($scope: ng.IScope)
	{
		this.frontDoor = new Door();
		
		// setup $watch
	}
}

angular.module('houseApp').controller('HouseController', HouseController);

Angular’s $watch takes three parameters. We’ll only look at the first two for this demo, watchExpression and listener. Both parameters take either a string or an expression. The simplest way to watch a property is like this:

$scope.$watch('frontDoor.isOpen', (newValue, oldValue) => {
	// frontDoor.isOpen has changed
});

Now, if we are doing this, we’ll need to put frontDoor directly on $scope. I personally don’t do this because I use controllerAs syntax to assign my controller to a property on $scope.

Here’s my template:

 <div ng-controller="HouseController as house">
	<button ng-click="house.frontDoor.open()" ng-if="!house.frontDoor.isOpen">Open Door</button>
	<button ng-click="house.frontDoor.close()" ng-if="house.frontDoor.isOpen">Close Door</button>
	<p>
		The door is {{house.frontDoor.isOpen === true ? "open" : "closed"}}
	</p>
 </div>

As you can see I am calling my controller and then assigning it to the property of house on $scope with this line: ng-controller="HouseController as house"

We can modify our $watch to work with how our controller is defined in the view like this:

// Added 'house'----.
$scope.$watch('house.frontDoor.isOpen', (newValue, oldValue) => {
	// frontDoor.isOpen has changed
});

But this isn’t very flexible. What if we were using this controller in multiple places and we couldn’t depend on it being asigned to the house property of $scope?

Instead of passing the $watch a string literal, let’s give it a function that returns the property we want to watch:

$scope.$watch(() => { return this.frontDoor.isOpen; }, (newValue, oldValue) => {
	// frontDoor.isOpen has changed
});

Here’s the entire controller:

class HouseController
{
	frontDoor: Door;
	
	static $inject = ['$scope'];
	constructor($scope: ng.IScope)
	{
		this.frontDoor = new Door();
		
		$scope.$watch(() => { return this.frontDoor.isOpen; }, (newValue, oldValue) => {
			if (newValue !== oldValue) {
				var doorState = (newValue === true) ? 'open' ? 'closed';
				alert('The door is ' + doorState);
			}
		});
	}
}

See it in action:

See the Pen How to Watch Scope Properties in Angular by Ken Howard (@kenhowardpdx) on CodePen.

 

These blog posts helped me form my own opinions of the best way to use $watch with TypeScript: