SoFunction
Updated on 2025-04-11

Angular updates directive according to the status of the service

Angular JS () is a set of frameworks, templates, data binding and rich UI components used to develop web pages. It supports the entire development process and provides the architecture of web applications without manual DOM operations.

AngularJS is designed to overcome the shortcomings of HTML in building applications. HTML is a good declarative language designed for static text display, but it will be weak if you want to build WEB applications. Here AngularJS came into being, making up for the natural defects of HTML and used in component Web applications, etc.

TL;DR

This article explains three ways to update directives based on the status of the service. These are the computed properties of the $watch expression, event delivery, and controller.

question

I have a readerService that contains some status information (such as connection status and power). Now I need to do a directive to show these states. Because it only needs to get data from the readerService and does not require any external value transmission, I injected the service directly into it. But how to update becomes a problem.

The code of service is as follows.

const STATUS = {
DETACH: 'DETACH',
ATTACH: 'ATTACH',
READY: 'READY'
}
class ReaderService {
constructor() {
 = STATUS
// The status will be changed by some callbacks
 = 
}
}
('app').service('readerService', readerService)

The directive code is as follows:

('app').directive('readerIndicator', (readerService) => {
const STATUS = 
const STATUS_DISPLAY = {
[]: 'Disconnected',
[]: 'Connecting...',
[]: 'Connected',
}
return {
restrict: 'E',
scope: {},
template: `
<div class="status">
{{statusDisplay}}
</div>
`,
link(scope) {
// Set and change  here
}
}
})

I have tried the following methods, which are introduced one by one below.

Method 1: $watch

The first way to think of is to use $watch to monitor in directive. Because it is not a property of direct scope, we need to wrap it with a function. Angular will calculate and compare new and old values ​​when dirty-checking, and the callback will be triggered only if the state really changes.

// In directive
link(scope) {
scope.$watch(() => , (status) => {
 = STATUS_DISPLAY[status]
})
}

This approach is simple and efficient enough. As long as the code involved in changing will trigger dirty-checking, directive will be updated automatically. service does not require any code modification.

But if multiple directive attributes are affected by service status, the $watch code will be obscure. Especially when the value modified by $watch will affect other values. for example:

// In directive
link(scope) {
scope.$watch(() => , (status) => {
 = STATUS_DISPLAY[status]
 = status !== 
})
scope.$watch('showBattery', () => {
// some other things depend on showBattery
})
}

At this time, the declarative programming style will be easier to understand, such as the computed property in Ember or Vue. This will be discussed later.

Method 2: $broadcast/$emit + $on

This idea is that every time the service state changes, it sends an event, and then directly listens to the event to change the state. Because status may have been updated when directive rendering. So we need to calculate an initial value in the link.

I started with $broadcast. The code is as follows:

// In service
setStatus(value) {
 = value
// Need to inject $rootScope
this.$rootScope.$broadcast('', )
}
// In directive
link(scope) {
 = STATUS_DISPLAY[]
scope.$on('', (event, status) => {
 = STATUS_DISPLAY[status]
})
}

But I immediately found that after $broadcast, the UI has to wait for more than 1 second to update (but the $on callback is very fast). After Google, I learned that the reason is that $broadcast is broadcasting to all scopes in the lower layer, and then dirty-checking is done after the broadcast is completed. A better approach is to use $emit, which will only pass events up, but you have to use $rootScope whether you send or listen to events.

The modified code is as follows:

// In service
setStatus(value) {
 = value
// Use $emit instead of $broadcast
this.$rootScope.$emit('', )
}
// In directive
link(scope) {
 = STATUS_DISPLAY[]
// Use $rootScope instead of scope
$rootScope.$on('', (event, status) => {
 = STATUS_DISPLAY[status]
})
}

If you have to use $broadcast for some reason, you can forcefully trigger dirty-checking with $digest or $apply at the end of the $on callback, which can also achieve the purpose of quickly updating the UI.

Method 3: controller + property

I personally think the first two methods can solve the problem, but the code maintenance is not very good. $watch is very difficult to understand when properties are related to each other. $emit/$on requires writing some logic twice (when initializing directive and when callback execution). In Method 1, I mentioned that sometimes declarative properties are easier to understand than $watch. This method is to use controller. Directive can set its own controller as the data source (or view model), and we can use the attributes that need to be calculated as the attributes of the controller. This way they will be calculated automatically when dirty-checking.

// In directive
class ReaderController {
constructor($scope, readerService) {
 = readerService
}
get statusDisplay() {
return STATUS_DISPLAY[]
}
}
return {
// ...
controller: ReaderController,
controllerAs: 'vm',
template: `
<div class="status">
{{}}
</div>
}

In this way, most of the logic can be moved into the controller. Without DOM operations we can even not write the link method. There is no need to add additional $watch and $on either. It is just because of the dirty-checking feature that binds to template often calculates several times more. So the properties must be very simple. This will not be a problem in most cases.

The above content is the editor’s introduction to Angular update directive according to the status of the service. I hope it will be helpful to everyone!