Here's a very simple controller and service. The service can be turned on and off with a checkbox.
<body ng-app="App">
<div ng-controller="MyController">
<input type="checkbox" ng-model="enabled">
</div>
</body>
angular.module("App", [])
.controller("MyController", function ($scope, myService) {
$scope.enabled = false;
$scope.$watch("enabled", function (newVal, oldVal) {
if (newVal === oldVal) {
return;
}
if (newVal) {
myService.start();
} else {
myService.stop();
}
});
})
.service("myService", function ($rootScope) {
var interval;
this.start = function () {
interval = setInterval(function () {
console.log("doing something ...");
}, 500);
};
this.stop = function () {
clearInterval(interval);
};
});
There's a problem. What if the service is already started when the controller is initialized?
When you enable the checkbox (which defaults to unchecked), it will start()
the service that's already started.
To prevent this, the service can check if the interval
is set to null
.
...
.service("myService", function ($rootScope) {
var interval = null;
this.start = function () {
if (interval === null) {
interval = setInterval(function () {
console.log("doing something ...");
}, 500);
}
};
this.stop = function () {
if (interval !== null) {
clearInterval(interval);
interval = null;
}
};
});
This prevents the service from getting screwed up. But the checkbox's state won't reflect that of the service.
We can fix this by adding a method to the service to get the state and use that to initialize the enabled
property.
angular.module("App", [])
.controller("MyController", function ($scope, myService) {
$scope.enabled = myService.started();
...
})
.service("myService", function ($rootScope) {
var interval = null;
// returns true if the service is started.
this.started = function () {
return interval !== null;
};
this.start = function () {
if (!this.started()) {
interval = setInterval(function () {
console.log("doing something ...");
}, 500);
}
};
this.stop = function () {
if (this.started()) {
clearInterval(interval);
interval = null;
}
};
});
Now let's introduce a new requirement. If the service encounters an error, it will turn itself off.
...
this.start = function () {
if (!this.started()) {
interval = setInterval(function () {
// check for "error"
if (Math.random() > 0.5) {
this.stop();
return;
}
console.log("doing something ...");
}.bind(this), 500);
}
};
...
This works, but the checkbox still stays on. We can $broadcast
an error event which the controller can pick up.
angular.module("App", [])
.controller("MyController", function ($scope, $rootScope, myService) {
...
// listen for error event
$rootScope.$on("myServiceError", function () {
$scope.$apply(function () {
$scope.enabled = myService.started();
});
});
})
.service("myService", function ($rootScope) {
...
this.start = function () {
if (!this.started()) {
interval = setInterval(function () {
if (Math.random() > 0.5) {
this.stop();
// broadcast error event
$rootScope.$broadcast("myServiceError");
return;
}
console.log("doing something ...");
}.bind(this), 500);
}
};
...
});
Here's the full code:
angular.module("App", [])
.controller("MyController", function ($scope, $rootScope, myService) {
$scope.enabled = myService.started();
$scope.$watch("enabled", function (newVal, oldVal) {
if (newVal === oldVal) {
return;
}
if (newVal) {
myService.start();
} else {
myService.stop();
}
});
$rootScope.$on("myServiceError", function () {
$scope.$apply(function () {
$scope.enabled = myService.started();
});
});
})
.service("myService", function ($rootScope) {
var interval = null;
this.started = function () {
return interval !== null;
};
this.start = function () {
if (!this.started()) {
interval = setInterval(function () {
if (Math.random() > 0.5) {
this.stop();
$rootScope.$broadcast("myServiceError");
return;
}
console.log("doing something ...");
}.bind(this), 500);
}
};
this.stop = function () {
if (this.started()) {
clearInterval(interval);
interval = null;
}
};
});
DEMO There has to be a better way.