In Angular 1, change detection was by dirty checking the $scope hierarchy. We would implicitly or explicitly create watchers in our templates, controllers or components.
Angular creates a change detector object (see ChangeDetectorRef) per component, which tracks the last value of each template binding, such as {{service.a}}. By default, after every asynchronous browser event (such as a response from a server, or a click event, or a timeout event), Angular change detection executes and dirty checks every binding using those change detector objects.
If a change is detected, the change is propagated. E.g.,
{{}} binding value changed, the new value is propagated to DOM property textContent. x changes in a style, attribute, or class binding – i.e., [style.x] or [attr.x] or [class.x] – the new value is propagated to the DOM to update the style, HTML attribute, or class. Angular uses Zone.js to create its own zone (NgZone), which monkey-patches all asynchronous events (browser DOM events, timeouts, AJAX/XHR). This is how change detection is able to automatically run after each asynchronous event. I.e., after each asynchronous event handler (function) finishes, Angular change detection will execute.
I have a lot more detail and reference links in this answer: What is the Angular2 equivalent to an AngularJS $watch?