angular/packages/core/src/render3/assert.ts
Kara Erickson 1a0ee18d62 fix(ivy): run pre-order hooks in injection order (#34026)
This commit fixes a compatibility bug where pre-order lifecycle
hooks (onInit, doCheck, OnChanges) for directives on the same
host node were executed based on the order the directives were
matched, rather than the order the directives were instantiated
(i.e. injection order).

This discrepancy can cause issues with forms, where it is common
to inject NgControl and try to extract its control property in
ngOnInit. As the NgControl directive is injected, it should be
instantiated before the control value accessor directive (and
thus its hooks should run first). This ensures that the NgControl
ngOnInit can set up the form control before the ngOnInit
for the control value accessor tries to access it.

Closes #32522

PR Close #34026
2019-11-25 18:41:22 -05:00

85 lines
3 KiB
TypeScript

/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {assertDefined, assertEqual, throwError} from '../util/assert';
import {getComponentDef, getNgModuleDef} from './definition';
import {TNode} from './interfaces/node';
import {isLContainer, isLView} from './interfaces/type_checks';
import {LView, TVIEW, TView} from './interfaces/view';
export function assertTNodeForLView(tNode: TNode, lView: LView) {
tNode.hasOwnProperty('tView_') && assertEqual(
(tNode as any as{tView_: TView}).tView_, lView[TVIEW],
'This TNode does not belong to this LView.');
}
export function assertComponentType(
actual: any,
msg: string = 'Type passed in is not ComponentType, it does not have \'ɵcmp\' property.') {
if (!getComponentDef(actual)) {
throwError(msg);
}
}
export function assertNgModuleType(
actual: any,
msg: string = 'Type passed in is not NgModuleType, it does not have \'ɵmod\' property.') {
if (!getNgModuleDef(actual)) {
throwError(msg);
}
}
export function assertPreviousIsParent(isParent: boolean) {
assertEqual(isParent, true, 'previousOrParentTNode should be a parent');
}
export function assertHasParent(tNode: TNode | null) {
assertDefined(tNode, 'previousOrParentTNode should exist!');
assertDefined(tNode !.parent, 'previousOrParentTNode should have a parent');
}
export function assertDataNext(lView: LView, index: number, arr?: any[]) {
if (arr == null) arr = lView;
assertEqual(
arr.length, index, `index ${index} expected to be at the end of arr (length ${arr.length})`);
}
export function assertLContainerOrUndefined(value: any): void {
value && assertEqual(isLContainer(value), true, 'Expecting LContainer or undefined or null');
}
export function assertLContainer(value: any): void {
assertDefined(value, 'LContainer must be defined');
assertEqual(isLContainer(value), true, 'Expecting LContainer');
}
export function assertLViewOrUndefined(value: any): void {
value && assertEqual(isLView(value), true, 'Expecting LView or undefined or null');
}
export function assertLView(value: any) {
assertDefined(value, 'LView must be defined');
assertEqual(isLView(value), true, 'Expecting LView');
}
export function assertFirstCreatePass(tView: TView, errMessage?: string) {
assertEqual(
tView.firstCreatePass, true, errMessage || 'Should only be called in first create pass.');
}
/**
* This is a basic sanity check that an object is probably a directive def. DirectiveDef is
* an interface, so we can't do a direct instanceof check.
*/
export function assertDirectiveDef(obj: any) {
if (obj.type === undefined || obj.selectors == undefined || obj.inputs === undefined) {
throwError(
`Expected a DirectiveDef/ComponentDef and this object does not seem to have the expected shape.`);
}
}