Component HTML:
<input type="text" maxlength="40" nz-input placeholder="Input Book Name to search" [(ngModel)]="bookName" (keyup)="search(bookName)" />
这个bookName是Component的一个属性:
运行时渲染到包含了[(ngModel)]的模板:
创建directive实例:
创建ngModel实例:
我在UI输入一个a,因为双向绑定,Component的bookName值也会变成a,这是怎么实现的呢?
原来每当UI发生变化,触发refreshView重绘界面,会执行checkhooks:
/** * Executing hooks requires complex logic as we need to deal with 2 constraints. * * 1. Init hooks (ngOnInit, ngAfterContentInit, ngAfterViewInit) must all be executed once and only * once, across many change detection cycles. This must be true even if some hooks throw, or if * some recursively trigger a change detection cycle. * To solve that, it is required to track the state of the execution of these init hooks. * This is done by storing and maintaining flags in the view: the {@link InitPhaseState}, * and the index within that phase. They can be seen as a cursor in the following structure: * [[onInit1, onInit2], [afterContentInit1], [afterViewInit1, afterViewInit2, afterViewInit3]] * They are are stored as flags in LView[FLAGS]. * * 2. Pre-order hooks can be executed in batches, because of the select instruction. * To be able to pause and resume their execution, we also need some state about the hook's array * that is being processed: * - the index of the next hook to be executed * - the number of init hooks already found in the processed part of the array * They are are stored as flags in LView[PREORDER_HOOK_FLAGS]. */ /** * Executes pre-order check hooks ( OnChanges, DoChanges) given a view where all the init hooks were * executed once. This is a light version of executeInitAndCheckPreOrderHooks where we can skip read * / write of the init-hooks related flags. * @param {?} lView The LView where hooks are defined * @param {?} hooks Hooks to be run * @param {?=} nodeIndex 3 cases depending on the value: * - undefined: all hooks from the array should be executed (post-order case) * - null: execute hooks only from the saved index until the end of the array (pre-order case, when * flushing the remaining hooks) * - number: execute hooks only from the saved index until that node index exclusive (pre-order * case, when executing select(number)) * @return {?} */ function executeCheckHooks(lView, hooks, nodeIndex) { callHooks(lView, hooks, 3 /* InitPhaseCompleted */, nodeIndex); }
函数wrapOnChangesHook_inPreviousChangesStorage:
function ɵɵNgOnChangesFeature(definition) { if (definition.type.prototype.ngOnChanges) { definition.setInput = ngOnChangesSetInput; ((/** @type {?} */ (definition))).onChanges = wrapOnChanges(); } } // This option ensures that the ngOnChanges lifecycle hook will be inherited // from superclasses (in InheritDefinitionFeature). /** @nocollapse */ // tslint:disable-next-line:no-toplevel-property-access ((/** @type {?} */ (ɵɵNgOnChangesFeature))).ngInherit = true; /** * @return {?} */ function wrapOnChanges() { return (/** * @this {?} * @return {?} */ function wrapOnChangesHook_inPreviousChangesStorage() { /** @type {?} */ const simpleChangesStore = getSimpleChangesStore(this); /** @type {?} */ const current = simpleChangesStore && simpleChangesStore.current; if (current) { /** @type {?} */ const previous = (/** @type {?} */ (simpleChangesStore)).previous; if (previous === EMPTY_OBJ) { (/** @type {?} */ (simpleChangesStore)).previous = current; } else { // New changes are copied to the previous store, so that we don't lose history for inputs // which were not changed this time for (let key in current) { previous[key] = current[key]; } } (/** @type {?} */ (simpleChangesStore)).current = null; this.ngOnChanges(current); } }); }
/** * Wraps an event listener with a function that marks ancestors dirty and prevents default behavior, * if applicable. * * @param {?} tNode The TNode associated with this listener * @param {?} lView The LView that contains this listener * @param {?} listenerFn The listener function to call * @param {?} wrapWithPreventDefault Whether or not to prevent default behavior * (the procedural renderer does this already, so in those cases, we should skip) * @return {?} */ function wrapListener(tNode, lView, listenerFn, wrapWithPreventDefault) { // Note: we are performing most of the work in the listener function itself // to optimize listener registration. return (/** * @param {?} e * @return {?} */ function wrapListenerIn_markDirtyAndPreventDefault(e) { // Ivy uses `Function` as a special token that allows us to unwrap the function // so that it can be invoked programmatically by `DebugNode.triggerEventHandler`. if (e === Function) { return listenerFn; } // In order to be backwards compatible with View Engine, events on component host nodes // must also mark the component view itself dirty (i.e. the view that it owns). /** @type {?} */ const startView = tNode.flags & 2 /* isComponentHost */ ? getComponentLViewByIndex(tNode.index, lView) : lView; // See interfaces/view.ts for more on LViewFlags.ManualOnPush if ((lView[FLAGS] & 32 /* ManualOnPush */) === 0) { markViewDirty(startView); } /** @type {?} */ let result = executeListenerWithErrorHandling(lView, listenerFn, e); // A just-invoked listener function might have coalesced listeners so we need to check for // their presence and invoke as needed. /** @type {?} */ let nextListenerFn = ((/** @type {?} */ (wrapListenerIn_markDirtyAndPreventDefault))).__ngNextListenerFn__; while (nextListenerFn) { // We should prevent default if any of the listeners explicitly return false result = executeListenerWithErrorHandling(lView, nextListenerFn, e) && result; nextListenerFn = ((/** @type {?} */ (nextListenerFn))).__ngNextListenerFn__; } if (wrapWithPreventDefault && result === false) { e.preventDefault(); // Necessary for legacy browsers that don't support preventDefault (e.g. IE) e.returnValue = false; } return result; }); }
来到forms.js的_handleInput(value):