7
7
*/
8
8
9
9
import { Type } from '../interface/type' ;
10
- import { assertDefined , assertNotEqual } from '../util/assert' ;
10
+ import { assertDefined , assertEqual , assertNotEqual } from '../util/assert' ;
11
11
import { assertLView } from './assert' ;
12
12
import { getComponentDef } from './def_getters' ;
13
13
import { assertComponentDef } from './errors' ;
@@ -45,6 +45,7 @@ import {destroyLView, removeViewFromDOM} from './node_manipulation';
45
45
import { RendererFactory } from './interfaces/renderer' ;
46
46
import { NgZone } from '../zone' ;
47
47
import { ViewEncapsulation } from '../metadata/view' ;
48
+ import { NG_COMP_DEF } from './fields' ;
48
49
49
50
/**
50
51
* Replaces the metadata of a component type and re-renders all live instances of the component.
@@ -61,7 +62,7 @@ export function ɵɵreplaceMetadata(
61
62
locals : unknown [ ] ,
62
63
) {
63
64
ngDevMode && assertComponentDef ( type ) ;
64
- const oldDef = getComponentDef ( type ) ! ;
65
+ const currentDef = getComponentDef ( type ) ! ;
65
66
66
67
// The reason `applyMetadata` is a callback that is invoked (almost) immediately is because
67
68
// the compiler usually produces more code than just the component definition, e.g. there
@@ -70,6 +71,13 @@ export function ɵɵreplaceMetadata(
70
71
// them at the right time.
71
72
applyMetadata . apply ( null , [ type , namespaces , ...locals ] ) ;
72
73
74
+ const { newDef, oldDef} = mergeWithExistingDefinition ( currentDef , getComponentDef ( type ) ! ) ;
75
+
76
+ // TODO(crisbeto): the `applyMetadata` call above will replace the definition on the type.
77
+ // Ideally we should adjust the compiler output so the metadata is returned, however that'll
78
+ // require some internal changes. We re-add the metadata here manually.
79
+ ( type as any ) [ NG_COMP_DEF ] = newDef ;
80
+
73
81
// If a `tView` hasn't been created yet, it means that this component hasn't been instantianted
74
82
// before. In this case there's nothing left for us to do aside from patching it in.
75
83
if ( oldDef . tView ) {
@@ -78,18 +86,54 @@ export function ɵɵreplaceMetadata(
78
86
// Note: we have the additional check, because `IsRoot` can also indicate
79
87
// a component created through something like `createComponent`.
80
88
if ( root [ FLAGS ] & LViewFlags . IsRoot && root [ PARENT ] === null ) {
81
- recreateMatchingLViews ( oldDef , root ) ;
89
+ recreateMatchingLViews ( newDef , oldDef , root ) ;
82
90
}
83
91
}
84
92
}
85
93
}
86
94
95
+ /**
96
+ * Merges two component definitions while preseving the original one in place.
97
+ * @param currentDef Definition that should receive the new metadata.
98
+ * @param newDef Source of the new metadata.
99
+ */
100
+ function mergeWithExistingDefinition (
101
+ currentDef : ComponentDef < unknown > ,
102
+ newDef : ComponentDef < unknown > ,
103
+ ) {
104
+ // Clone the current definition since we reference its original data further
105
+ // down in the replacement process (e.g. when destroying the renderer).
106
+ const clone = { ...currentDef } ;
107
+
108
+ // Assign the new metadata in place while preserving the object literal. It's important to
109
+ // Keep the object in place, because there can be references to it, for example in the
110
+ // `directiveDefs` of another definition.
111
+ const replacement = Object . assign ( currentDef , newDef , {
112
+ // We need to keep the existing directive and pipe defs, because they can get patched on
113
+ // by a call to `setComponentScope` from a module file. That call won't make it into the
114
+ // HMR replacement function, because it lives in an entirely different file.
115
+ directiveDefs : clone . directiveDefs ,
116
+ pipeDefs : clone . pipeDefs ,
117
+
118
+ // Preserve the old `setInput` function, because it has some state.
119
+ // This is fine, because the component instance is preserved as well.
120
+ setInput : clone . setInput ,
121
+ } ) ;
122
+
123
+ ngDevMode && assertEqual ( replacement , currentDef , 'Expected definition to be merged in place' ) ;
124
+ return { newDef : replacement , oldDef : clone } ;
125
+ }
126
+
87
127
/**
88
128
* Finds all LViews matching a specific component definition and recreates them.
89
129
* @param oldDef Component definition to search for.
90
130
* @param rootLView View from which to start the search.
91
131
*/
92
- function recreateMatchingLViews ( oldDef : ComponentDef < unknown > , rootLView : LView ) : void {
132
+ function recreateMatchingLViews (
133
+ newDef : ComponentDef < unknown > ,
134
+ oldDef : ComponentDef < unknown > ,
135
+ rootLView : LView ,
136
+ ) : void {
93
137
ngDevMode &&
94
138
assertDefined (
95
139
oldDef . tView ,
@@ -102,7 +146,7 @@ function recreateMatchingLViews(oldDef: ComponentDef<unknown>, rootLView: LView)
102
146
// produce false positives when using inheritance.
103
147
if ( tView === oldDef . tView ) {
104
148
ngDevMode && assertComponentDef ( oldDef . type ) ;
105
- recreateLView ( getComponentDef ( oldDef . type ) ! , oldDef , rootLView ) ;
149
+ recreateLView ( newDef , oldDef , rootLView ) ;
106
150
return ;
107
151
}
108
152
@@ -112,14 +156,14 @@ function recreateMatchingLViews(oldDef: ComponentDef<unknown>, rootLView: LView)
112
156
if ( isLContainer ( current ) ) {
113
157
// The host can be an LView if a component is injecting `ViewContainerRef`.
114
158
if ( isLView ( current [ HOST ] ) ) {
115
- recreateMatchingLViews ( oldDef , current [ HOST ] ) ;
159
+ recreateMatchingLViews ( newDef , oldDef , current [ HOST ] ) ;
116
160
}
117
161
118
162
for ( let j = CONTAINER_HEADER_OFFSET ; j < current . length ; j ++ ) {
119
- recreateMatchingLViews ( oldDef , current [ j ] ) ;
163
+ recreateMatchingLViews ( newDef , oldDef , current [ j ] ) ;
120
164
}
121
165
} else if ( isLView ( current ) ) {
122
- recreateMatchingLViews ( oldDef , current ) ;
166
+ recreateMatchingLViews ( newDef , oldDef , current ) ;
123
167
}
124
168
}
125
169
}
0 commit comments