Repository: angular
Issue: Add the option to define the scheduling API for work (i. e. change detection) in Angular
Labels: action: discuss,area: core,area: elements,core: change detection,needs: discussion

### Which @angular/* package(s) are relevant/releated to the feature request?

elements

### Description

This issue targets a performance-related problem. It is explained in this issue based on a specific API that schedules the change detection of Angular elements, but also other features in Angular would benefit from such configuration option.

## Required theory to understand the problem

The Browsers main thread is a single-threaded system, meaning things happen one after another. 

When we connect this information with the fact that users constantly interact with our site this means if our main thread is busy the user cant interact with the page. The events like scroll or click will get delayed until the main thread is unblocked from work again and can process those interactions.

Such situations cause problems like:
- blocking UIs
- animation jank
- scroll jank or stottering
- delayed navigations
- bad frame rates in general

All those problems boil down to the way the user perceives the interactions with the website. 
Due to the human eye and how our screens got standardized, the frame rate is defined as good if it is 60 frames per second which is a screen update every 16.6 milliseconds. 

In the browser, we can see tasks in the main thread that are too long for a good framerate marked with a red triangle.
![angular-long-task](https://user-images.githubusercontent.com/10064416/129488112-22697fe0-5ca1-4eac-b551-7c7d6ea1bcb8.PNG)

In the image, we see ChromeDevtools marks frames that take longer than 50ms as [long task](https://developer.mozilla.org/en-US/docs/Web/API/Long_Tasks_API).  All those tasks exceeded the input response budget.

The reason why it is 50ms and not 16.6ms is based on other things that can happen in relation to the user input. 
The related theory is known as the [RAIL model](https://web.dev/rail/).

## The problem

When it comes to long tasks caused by scripting we can do 2 things to avoid that:
- reduce scripting work and let the user interact earlier
- chunk up work and use scheduling API's to distribute the work overtime and let the user interact in between

![angular-scripting-time](https://user-images.githubusercontent.com/10064416/129488499-df475ee2-8b69-478d-9938-a167bec7a06f.PNG)

As often the work just can't get reduced so we have to chunk and schedule.
Angular did that internally in different places. One of them is in the `elements` package:

https://github.com/angular/angular/blob/master/packages/elements/src/component-factory-strategy.ts#L255-L267

Also, the utils file is an interesting place to look at:
https://github.com/angular/angular/blob/master/packages/elements/src/utils.ts#L13-L46

Angular elements change detection is scheduled with [`requestAnimationFrame`](https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame). 
This scheduling technique places the execution of work within a task that also recalculates styles and paints. Animation frame literally triggers styles recalculation.

The image shows a Timer event followed by an AnimationFrame event. We can see the animation frame includes the recalc styles and paint and the timer doesn't.

![timer vs animationframe](https://user-images.githubusercontent.com/10064416/129489280-5f353727-87f2-43cb-9694-0b08b9ea8abc.PNG)

There are also other scheduling APIs. They all help to prioritize the work and define the moment of execution differently.

When using the `requestAnimationFrame` API we should know that it is not a queued system. 
All scheduled tasks will end up in the same task of the main thread.

![timer vs animationframe - collapse](https://user-images.githubusercontent.com/10064416/129489541-98e5679c-e94a-4877-a8b9-5e22f0706cb7.PNG)

The image shows that all AnimatioFrame events end up in the same task.

This scenario gets to a problem depending on:
- the number of Angular elements
- the amount of work done in the elements

Here is a long task detected when navigating between 2 small pages on [angular.io](angular.io)
 
![angular-animation-frame-short](https://user-images.githubusercontent.com/10064416/129489385-cba22c4a-40a0-4a52-80c4-f951eb5b2c31.PNG)

The measure was taken on a strong machine and no other demanding processes running. In a real-life scenario with i.e. youtube running and other demanding processes, this gets a real pain. I did not even mention mobile phones where the CPU is not strong or phones that don't have a GPU.

If we abstract that problem away from the animation frame we can quickly see the problem is here because the scheduling technique has no notion of the frame budget. 






### Proposed solution

To solve this problem our scheduling technique should have a notion of the frame budget. 
This would lead to a new scheduling cycle whenever the budget is close to exceeding.

In between the tasks, the user can interact and the site feels more smooth overall.

![notion-of-frame-budget](https://user-images.githubusercontent.com/10064416/129490121-5b594b13-fbf8-49df-b7c3-90f947768d6a.png)

To achieve this we would have to implement a way to switch the scheduling technique and be able to provide techniques that maintain a notion of the frame budget. 

## Existing techniques

**React Scheduler**
 
React for example already implemented such technologies under the name concurrent mode.

If we take a look at the flame charts we can see that a function named `performWorkUntilDeadline` is called. 
Without going into much detail about this specific solution it basically is a scheduling technique that maintains a notion of the frame budget. 
 
![react-performWorkUntilDeadline](https://user-images.githubusercontent.com/10064416/129490827-5da82400-e623-4ce1-bef0-57d434921eab.PNG)

**Post Task Scheduling**

https://www.chromestatus.com/feature/6031161734201344

**Google Maps Scheduler**

## Suggested Solution

We would have to create an interface that abstracts the scheduling technique.
Also, the scheduling method should not be just a function but maybe a service so it could get overwritten by DI?


I quick skatch of the interface could look like this:

```typescript
interface CanSchedule {
  schedule(work: () => void, options: Record<string, any>):  AbortControllerLike {
  }
}
```

I used any as newer scheduling API's provide more configuration options than just a delay. 
Priority of post task scheduling is a good example of other options.

Also, the return value should not just be an id for timeout or animation frame but something more aligns with TaskScheduler.

## Additional thoughts

Further considerations should be done for `ngZoneEventCoalescing` too.

### Alternatives considered

An alternative would be to implement it in Angular and not exposing such a way of configuration. 
But as there are quite some different ways to do this it would not be a good idea.