@@ -17,6 +17,7 @@ import {NG_HOST_SYMBOL, createError, createLView, createTView, directiveCreate,
17
17
import { ComponentDef , ComponentType } from './interfaces/definition' ;
18
18
import { LElementNode } from './interfaces/node' ;
19
19
import { RElement , Renderer3 , RendererFactory3 , domRendererFactory3 } from './interfaces/renderer' ;
20
+ import { RootContext } from './interfaces/view' ;
20
21
import { notImplemented , stringify } from './util' ;
21
22
22
23
@@ -43,6 +44,19 @@ export interface CreateComponentOptions {
43
44
* Example: PublicFeature is a function that makes the component public to the DI system.
44
45
*/
45
46
features ?: ( < T > ( component : T , componentDef : ComponentDef < T > ) => void ) [ ] ;
47
+
48
+ /**
49
+ * A function which is used to schedule change detection work in the future.
50
+ *
51
+ * When marking components as dirty, it is necessary to schedule the work of
52
+ * change detection in the future. This is done to coalesce multiple
53
+ * {@link markDirty} calls into a single changed detection processing.
54
+ *
55
+ * The default value of the scheduler is the `requestAnimationFrame` function.
56
+ *
57
+ * It is also useful to override this function for testing purposes.
58
+ */
59
+ scheduler ?: ( work : ( ) => void ) => void ;
46
60
}
47
61
48
62
@@ -155,11 +169,22 @@ export const NULL_INJECTOR: Injector = {
155
169
}
156
170
} ;
157
171
172
+ /**
173
+ * A permanent marker promise which signifies that the current CD tree is
174
+ * clean.
175
+ */
176
+ const CLEAN_PROMISE = Promise . resolve ( null ) ;
158
177
159
178
/**
160
179
* Bootstraps a Component into an existing host element and returns an instance
161
180
* of the component.
162
181
*
182
+ * Use this function to bootstrap a component into the DOM tree. Each invocation
183
+ * of this function will create a separate tree of components, injectors and
184
+ * change detection cycles and lifetimes. To dynamically insert a new component
185
+ * into an existing tree such that it shares the same injection, change detection
186
+ * and object lifetime, use {@link ViewContainer#createComponent}.
187
+ *
163
188
* @param componentType Component to bootstrap
164
189
* @param options Optional parameters which control bootstrapping
165
190
*/
@@ -170,15 +195,23 @@ export function renderComponent<T>(
170
195
if ( componentDef . type != componentType ) componentDef . type = componentType ;
171
196
let component : T ;
172
197
const hostNode = locateHostElement ( rendererFactory , opts . host || componentDef . tag ) ;
198
+ const rootContext : RootContext = {
199
+ // Incomplete initialization due to circular reference.
200
+ component : null ! ,
201
+ scheduler : opts . scheduler || requestAnimationFrame ,
202
+ clean : CLEAN_PROMISE ,
203
+ } ;
173
204
const oldView = enterView (
174
205
createLView (
175
- - 1 , rendererFactory . createRenderer ( hostNode , componentDef . rendererType ) , createTView ( ) ) ,
206
+ - 1 , rendererFactory . createRenderer ( hostNode , componentDef . rendererType ) , createTView ( ) ,
207
+ null , rootContext ) ,
176
208
null ! ) ;
177
209
try {
178
210
// Create element node at index 0 in data array
179
211
hostElement ( hostNode , componentDef ) ;
180
212
// Create directive instance with n() and store at index 1 in data array (el is 0)
181
- component = getDirectiveInstance ( directiveCreate ( 1 , componentDef . n ( ) , componentDef ) ) ;
213
+ component = rootContext . component =
214
+ getDirectiveInstance ( directiveCreate ( 1 , componentDef . n ( ) , componentDef ) ) ;
182
215
} finally {
183
216
leaveView ( oldView ) ;
184
217
}
@@ -188,27 +221,120 @@ export function renderComponent<T>(
188
221
return component ;
189
222
}
190
223
191
- export function detectChanges < T > ( component : T ) {
192
- ngDevMode && assertNotNull ( component , 'detectChanges should be called with a component' ) ;
193
- const hostNode = ( component as any ) [ NG_HOST_SYMBOL ] as LElementNode ;
194
- if ( ngDevMode && ! hostNode ) {
195
- createError ( 'Not a directive instance' , component ) ;
196
- }
224
+ /**
225
+ * Synchronously perform change detection on a component (and possibly its sub-components).
226
+ *
227
+ * This function triggers change detection in a synchronous way on a component. There should
228
+ * be very little reason to call this function directly since a preferred way to do change
229
+ * detection is to {@link markDirty} the component and wait for the scheduler to call this method
230
+ * at some future point in time. This is because a single user action often results in many
231
+ * components being invalidated and calling change detection on each component synchronously
232
+ * would be inefficient. It is better to wait until all components are marked as dirty and
233
+ * then perform single change detection across all of the components
234
+ *
235
+ * @param component The component which the change detection should be performed on.
236
+ */
237
+ export function detectChanges < T > ( component : T ) : void {
238
+ const hostNode = _getComponentHostLElementNode ( component ) ;
197
239
ngDevMode && assertNotNull ( hostNode . data , 'Component host node should be attached to an LView' ) ;
198
240
renderComponentOrTemplate ( hostNode , hostNode . view , component ) ;
199
- isDirty = false ;
200
241
}
201
242
202
- let isDirty = false ;
203
- export function markDirty < T > (
204
- component : T , scheduler : ( fn : ( ) => void ) => void = requestAnimationFrame ) {
205
- ngDevMode && assertNotNull ( component , 'markDirty should be called with a component' ) ;
206
- if ( ! isDirty ) {
207
- isDirty = true ;
208
- scheduler ( ( ) => detectChanges ( component ) ) ;
243
+ /**
244
+ * Mark the component as dirty (needing change detection).
245
+ *
246
+ * Marking a component dirty will schedule a change detection on this
247
+ * component at some point in the future. Marking an already dirty
248
+ * component as dirty is a noop. Only one outstanding change detection
249
+ * can be scheduled per component tree. (Two components bootstrapped with
250
+ * separate `renderComponent` will have separate schedulers)
251
+ *
252
+ * When the root component is bootstrapped with `renderComponent` a scheduler
253
+ * can be provided.
254
+ *
255
+ * @param component Component to mark as dirty.
256
+ */
257
+ export function markDirty < T > ( component : T ) {
258
+ const rootContext = getRootContext ( component ) ;
259
+ if ( rootContext . clean == CLEAN_PROMISE ) {
260
+ let res : null | ( ( val : null ) => void ) ;
261
+ rootContext . clean = new Promise < null > ( ( r ) => res = r ) ;
262
+ rootContext . scheduler ( ( ) => {
263
+ detectChanges ( rootContext . component ) ;
264
+ res ! ( null ) ;
265
+ rootContext . clean = CLEAN_PROMISE ;
266
+ } ) ;
209
267
}
210
268
}
211
269
212
- export function getHostElement < T > ( component : T ) : RElement {
213
- return ( ( component as any ) [ NG_HOST_SYMBOL ] as LElementNode ) . native ;
270
+ /**
271
+ * Retrieve the root component of any component by walking the parent `LView` until
272
+ * reaching the root `LView`.
273
+ *
274
+ * @param component any component
275
+ */
276
+ function getRootContext ( component : any ) : RootContext {
277
+ ngDevMode && assertNotNull ( component , 'component' ) ;
278
+ const lElementNode = _getComponentHostLElementNode ( component ) ;
279
+ let lView = lElementNode . view ;
280
+ while ( lView . parent ) {
281
+ lView = lView . parent ;
282
+ }
283
+ const rootContext = lView . context as RootContext ;
284
+ ngDevMode && assertNotNull ( rootContext , 'rootContext' ) ;
285
+ return rootContext ;
286
+ }
287
+
288
+ function _getComponentHostLElementNode < T > ( component : T ) : LElementNode {
289
+ ngDevMode && assertNotNull ( component , 'expecting component got null' ) ;
290
+ const lElementNode = ( component as any ) [ NG_HOST_SYMBOL ] as LElementNode ;
291
+ ngDevMode && assertNotNull ( component , 'object is not a component' ) ;
292
+ return lElementNode ;
293
+ }
294
+
295
+ /**
296
+ * Retrieve the host element of the component.
297
+ *
298
+ * Use this function to retrieve the host element of the component. The host
299
+ * element is the element which the component is associated with.
300
+ *
301
+ * @param component Component for which the host element should be retrieved.
302
+ */
303
+ export function getHostElement < T > ( component : T ) : HTMLElement {
304
+ return _getComponentHostLElementNode ( component ) . native as any ;
305
+ }
306
+
307
+ /**
308
+ * Retrieves the rendered text for a given component.
309
+ *
310
+ * This function retrieves the host element of a component and
311
+ * and then returns the `textContent` for that element. This implies
312
+ * that the text returned will include re-projected content of
313
+ * the component as well.
314
+ *
315
+ * @param component The component to return the content text for.
316
+ */
317
+ export function getRenderedText ( component : any ) : string {
318
+ const hostElement = getHostElement ( component ) ;
319
+ return hostElement . textContent || '' ;
320
+ }
321
+
322
+ /**
323
+ * Wait on component until it is rendered.
324
+ *
325
+ * This function returns a `Promise` which is resolved when the component's
326
+ * change detection is executed. This is determined by finding the scheduler
327
+ * associated with the `component`'s render tree and waiting until the scheduler
328
+ * flushes. If nothing is scheduled, the function returns a resolved promise.
329
+ *
330
+ * Example:
331
+ * ```
332
+ * await whenRendered(myComponent);
333
+ * ```
334
+ *
335
+ * @param component Component to wait upon
336
+ * @returns Promise which resolves when the component is rendered.
337
+ */
338
+ export function whenRendered ( component : any ) : Promise < null > {
339
+ return getRootContext ( component ) . clean ;
214
340
}
0 commit comments