# Change Detection when Declaration and Insertion parent views don't match
Author: Miško Hevery
# Prior Discussion
- [TemplateRef passed into other component not checked under Ivy · Issue #33393 · angular/angular](https://github.com/angular/angular/issues/33393#issuecomment-546243506)
- [Embedded views with different declaration and insertion points](https://docs.google.com/document/d/1E0HYC9Dl8UhNsteK5GKtqISfV4bMo8G1fR1AUVY_no8/edit#heading=h.dfrdwxsgfuxu)
- [Ivy Minor Changes](https://docs.google.com/document/d/1Dije0AsJ0PxL3NaeNPxpYDeapj30b_QC0xfeIvIIzgg/preview)
- [FW-842 DESIGN: View engine dirty-checks projected views when the declaration place is checked](https://gist.github.com/mhevery/d6db04ec7b14027acdd0457b546466a8)
- [“Transplanted” views](https://docs.google.com/presentation/d/1AlaZN7znhprEUkhr760i8DKwDTVaerU7HGOgrszYfsc/edit#slide=id.p)
# Definitions
```typescript=
@Component({
selector: 'lib-comp',
template: `
LibComp: {{greeting}}!
<ng-container
[ngTemplateOutlet]="template"
[ngTemplateOutletContext]="{$implicit: greeting}">
</ng-container>
`
})
class LibComp {
@Input()
template: TemplateRef;
greeting:string = "Hello";
}
@Component({
template: `
AppComp: {{name}}!
<ng-template #myTmpl let-greeting>
{{greeting}} {{name}}!
</ng-template>
<lib-comp [template]="myTmpl"></lib-comp>
`
})
class AppComp {
name: string = "world";
}
```
- **Declaration View**: The `LView` where the `<ng-template>` was declared. In our case it would be the `LView` of `AppComp`.
- **Insertion View**: The `LView` where the instance of `<ng-template>` has been inserted. In our example it would be the `LView` of `LibComp`.
- **Transplant `LView`**: Transplant `LView` is the `LView` where the insertion point `LView[PARENT]` and declaration `LView[DECLARATION_VIEW]` do not match. In our example this would be `LView` created by the `LibComp` from the `TemplateRef` which was declared at `AppComp`.
# Mental Model
- The developer of application (`AppComp`) and the library author (`LibComp`) are two different developers, therefore the assumptions of one should not leak into the other. For example the `LibComp` should be free to decide if the `LibComp` is `OnPush` or `Always` without effecting the kinds of assumptions `AppComp` has. To put it differently `LibComp` should be able to change from `Always` to `OnPush` without breaking CD for the transplanted `LView`.
- Implication of this is that the CD strategy is attached to the transplanted template and not to the insertion `LView`. (Author of the template controls the CD strategy.)
| `AppComp` | `LibComp` | Outcome
|-----------|-----------|---------
| `Always` | `Always` | Run CD through all `LView`s on each tick.
| `Always` | `OnPush` | Changing `AppComp.name` should trigger the update of the binding `{{greeting}} {{name}}!` regardless if `LibComp` is dirty.
| `OnPush` | `Always` | Changing `LibComp.greeting` should trigger the update of the `{{greeting}} {{name}}!` regardless if the `AppComp` is dirty.
| `OnPush` | `OnPush` | Changing `LibComp.greeting` or `AppComp.name` should trigger the CD of the template.
What the above table suggest is that the transplanted`LView` should be CDed if either the `AppComp.name` or the `LibComp.greeting` changes. In other words the transplant `LView` is subject to being marked dirty by either the `AppComp` or `LibComp` being marked dirty.
NOTE:
- If the transplant `LView` is detached it should not be CDed. (This behavior is different from VE)
Suppose both `AppComp` and `LibComp` are `OnPush`. Which views should be CDed when either `AppComp` or `LibComp` change state and are marked dirty?
<div style="font-size: 11px">
|`AppComp` `.name`|`LibComp` `.greeting`|Transplanted `{{greeting}} {{name}}!`| `AppComp: {{name}}!`|`LibComp: {{greeting}}!`|Outcome
|---|---|---|---|---|---
|- |- |- |- |- | when neither component is marked dirty a CD run should not update any of the bindings.
| mutate | - | Update | Update | - | When `AppComp` is marked dirty than `{{greeting}} {{name}}!`, `AppComp: {{name}}!` should be updated, but `LibComp: {{greeting}}!` should not be updated.
| - | mutate | Update | - | Update | When `LibComp` is marked dirty than `{{greeting}} {{name}}!`, `LibComp: {{greeting}}!` should be updated, but `AppComp: {{name}}!` should not be updated.
| mutate | mutate | Update | Update | Update | When `LibComp` and `AppComp` is marked dirty than `{{greeting}} {{name}}!`, `LibComp: {{greeting}}!`, and `AppComp: {{name}}!` should be updated.
</div>
The implications from the above table are that:
- `AppComp` being marked dirty should CD its transplanted `LView` but it should not cause CD in `LibComp`.
- This is a breaking change with respect to VE, which (I believe) causes the CD in `LibComp` as well.
- `LibComp` being marked dirty should CD its transplanted `LView` but it should not cause CD in `AppComp`.
- (I belive) this is consistent with VE behavior.
- A detached transplanted `LView` should not be CD in either case.
- This is a breaking change with respect to VE, which (I believe) causes the CD even if the transplanted `LView` is detached.
# Proposed Fix
The implication from the above discussion is that the `<ng-template>` needs to keep track of the transplanted `LView` instances so that it can invoke change detection on them from both declared as well as inserted view.
- [ ] `LView` currently keeps track of all components in `LView[CHILD_HEAD|CHILD_TAIL|NEXT]` (in addition to `LView`). This is unnecessary because we already keep track of all child components in `TView.components`. Removing them would speed up creation and updates as tracking and iterating requires time.
- [ ] All transplanted `LView`s should be added the the declared `LView` linked-list.
- Only add if `LView[PARENT] !== LView[DECLARATION_VIEW` (transplanted) and `LView[PARENT] !== null` (attached).
- [ ] Component's linked-list should also contain `LView[PREV]` to make it efficient to remove transplanted `LView`s. This is an additional cost to the `LView` as it will increase its header size by one slot.
- Change Detection Processing:
- [x] During CD of `LibComp` we should always visit any (including transplanted) view at insertion point (current behavior).
- [ ] During CD of declaration `LView` we should also visit the transplanted views in the `LView[CHILD_HEAD|CHILD_TAIL|NEXT]`.
- The implication of this is that in the case of transplanted `LView` we will run the CD on the transplanted `LView` twice. Once from the `AppComp` and once from the `LibComp`. We could guard against this, but that would require additional work and checks, and given that this scenario is rare, I don't think it is worth worrying about this double checking.
# Open Issues
- [ ] This does not solve the problem of having the transplanted View inserted in the `LibComp` and than have the `LibCom` be detached. In such a situation one would expect that the template does not get CD under any use case, but in our case it would get CDed in the case when the `AppCom` would get marked as dirty.