( node: RNode | null, nodeType: number, tagName: string | null, lView: LView, tNode: TNode, isViewContainerAnchor = false, )
| 57 | * Validates that provided nodes match during the hydration process. |
| 58 | */ |
| 59 | export function validateMatchingNode( |
| 60 | node: RNode | null, |
| 61 | nodeType: number, |
| 62 | tagName: string | null, |
| 63 | lView: LView, |
| 64 | tNode: TNode, |
| 65 | isViewContainerAnchor = false, |
| 66 | ): void { |
| 67 | if ( |
| 68 | !node || |
| 69 | (node as Node).nodeType !== nodeType || |
| 70 | ((node as Node).nodeType === Node.ELEMENT_NODE && |
| 71 | (node as HTMLElement).tagName.toLowerCase() !== tagName?.toLowerCase()) |
| 72 | ) { |
| 73 | const expectedNode = shortRNodeDescription(nodeType, tagName, null); |
| 74 | let header = `During hydration Angular expected ${expectedNode} but `; |
| 75 | |
| 76 | const hostComponentDef = getDeclarationComponentDef(lView); |
| 77 | const componentClassName = hostComponentDef?.type?.name; |
| 78 | |
| 79 | const expectedDom = describeExpectedDom(lView, tNode, isViewContainerAnchor); |
| 80 | const expected = `Angular expected this DOM:\n\n${expectedDom}\n\n`; |
| 81 | |
| 82 | let actual = ''; |
| 83 | const componentHostElement = unwrapRNode(lView[HOST]!); |
| 84 | if (!node) { |
| 85 | // No node found during hydration. |
| 86 | header += `the node was not found.\n\n`; |
| 87 | |
| 88 | // Since the node is missing, we use the closest node to attach the error to |
| 89 | markRNodeAsHavingHydrationMismatch(componentHostElement, expectedDom); |
| 90 | } else { |
| 91 | const actualNode = shortRNodeDescription( |
| 92 | (node as Node).nodeType, |
| 93 | (node as HTMLElement).tagName ?? null, |
| 94 | (node as HTMLElement).textContent ?? null, |
| 95 | ); |
| 96 | |
| 97 | header += `found ${actualNode}.\n\n`; |
| 98 | const actualDom = describeDomFromNode(node); |
| 99 | actual = `Actual DOM is:\n\n${actualDom}\n\n`; |
| 100 | |
| 101 | // DevTools only report hydration issues on the component level, so we attach extra debug |
| 102 | // info to a component host element to make it available to DevTools. |
| 103 | markRNodeAsHavingHydrationMismatch(componentHostElement, expectedDom, actualDom); |
| 104 | } |
| 105 | |
| 106 | const footer = getHydrationErrorFooter(componentClassName); |
| 107 | let message = header + expected + actual + getHydrationAttributeNote() + footer; |
| 108 | |
| 109 | // Check both when a mismatching node is found AND when the expected node is missing, |
| 110 | // since third-party scripts can both inject extra nodes and remove existing ones. |
| 111 | if (!node || (node && isLikelyExternalSourceNode(node))) { |
| 112 | message += |
| 113 | `Note: It looks like this mismatch may have been caused by a third-party script or ` + |
| 114 | `browser extension that modified the DOM outside of Angular's control. ` + |
| 115 | `Angular hydration does not support nodes injected or removed outside of the Angular-managed DOM. ` + |
| 116 | `Note: If you know which element in the DOM this will be inserted, consider adding ngSkipHydration to prevent this error. \n\n`; |
no test coverage detected
searching dependent graphs…