114 lines
		
	
	
		
			3.8 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
		
		
			
		
	
	
			114 lines
		
	
	
		
			3.8 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
|  | # Appendix: Ivy Compatibility Examples
 | ||
|  | 
 | ||
|  | This appendix is intended to provide more background on Ivy changes. Many of these examples list error messages you may see, so searching by error message might be a good idea if you are debugging. | ||
|  | 
 | ||
|  | <div class="alert is-critical"> | ||
|  | NOTE: Most of these issues affect a small percentage of applications encountering unusual or rare edge cases. | ||
|  | </div> | ||
|  | 
 | ||
|  | 
 | ||
|  | {@a content-children-descendants} | ||
|  | ## @ContentChildren Queries Only Match Direct Children By Default
 | ||
|  | 
 | ||
|  | 
 | ||
|  | ### Basic example of change 
 | ||
|  | 
 | ||
|  | Let's say a component (`Comp`) has a `@ContentChildren` query for `'foo'`: | ||
|  | 
 | ||
|  | ```html | ||
|  | <comp> | ||
|  |     <div> | ||
|  |          <div #foo></div>   <!-- matches in old runtime, not in new runtime --> | ||
|  |     </div> | ||
|  | </comp> | ||
|  | ``` | ||
|  | 
 | ||
|  | In the previous runtime, the `<div>` with `#foo` would match.  | ||
|  | With Ivy, that `<div>` does not match because it is not a direct child of `<comp>`. | ||
|  | 
 | ||
|  | 
 | ||
|  | ### Background
 | ||
|  | 
 | ||
|  | By default, `@ContentChildren` queries have the `descendants` flag set to `false`.  | ||
|  | 
 | ||
|  | Previously, "descendants" referred to "descendant directives".  | ||
|  | An element could be a match as long as there were no other directives between the element and the requesting directive. | ||
|  | This made sense for directives with nesting like tabs, where nested tab directives might not be desirable to match. | ||
|  | However, this caused surprising behavior for users because adding an unrelated directive like `ngClass` to a wrapper element could invalidate query results. | ||
|  | 
 | ||
|  | For example, with the content query and template below, the last two `Tab` directives would not be matches: | ||
|  | 
 | ||
|  | ``` | ||
|  | @ContentChildren(Tab, {descendants: false}) tabs: QueryList<Tab> | ||
|  | ``` | ||
|  | 
 | ||
|  | ``` | ||
|  | <tab-list> | ||
|  |   <div> | ||
|  |     <tab> One </tab>     <!-- match (nested in element) --> | ||
|  |   </div> | ||
|  |   <tab>                  <!-- match (top level) --> | ||
|  |     <tab> A </tab>       <!-- not a match (nested in tab) --> | ||
|  |   </tab>   | ||
|  |   <div [ngClass]="classes"> | ||
|  |     <tab> Two </tab>     <!-- not a match (nested in ngClass) --> | ||
|  |   </div> | ||
|  | </tab-list> | ||
|  | ``` | ||
|  | 
 | ||
|  | In addition, the differences between type and string predicates were subtle and sometimes unclear. | ||
|  | For example, if you replace the type predicate above with a `'foo'` string predicate, the matches change: | ||
|  | 
 | ||
|  | ``` | ||
|  | @ContentChildren('foo', {descendants: false}) foos: QueryList<ElementRef> | ||
|  | ``` | ||
|  | 
 | ||
|  | ``` | ||
|  | <tab-list> | ||
|  |   <div> | ||
|  |     <div #foo> One </div>     <!-- match (nested in element) --> | ||
|  |   </div> | ||
|  |   <tab #foo>                  <!-- match (top level) --> | ||
|  |     <div #foo> A </div>       <!-- match (nested in tab) --> | ||
|  |   </tab>   | ||
|  |   <div [ngClass]="classes"> | ||
|  |     <div #foo> Two </div>     <!-- match (nested in ngClass) --> | ||
|  |   </div> | ||
|  | </tab-list> | ||
|  | ``` | ||
|  | 
 | ||
|  | Because the previous behavior was inconsistent and surprising to users, we did not want to reproduce it in Ivy. | ||
|  | Instead, we simplified the mental model so that "descendants" refers to DOM nesting only.  | ||
|  | Any DOM element between the requesting component and a potential match will invalidate that match. | ||
|  | Type predicates and string predicates also have identical matching behavior. | ||
|  | 
 | ||
|  | 
 | ||
|  | ### Example of error
 | ||
|  | 
 | ||
|  | The error message that you see will depend on how the particular content query is used in the application code. | ||
|  | Frequently, an error is thrown when a property is referenced on the content query result (which is now `undefined`).  | ||
|  | 
 | ||
|  | For example, if the component sets the content query results to a property, `foos`, `foos.first.bar` would throw the error: | ||
|  | 
 | ||
|  | ``` | ||
|  | Uncaught TypeError: Cannot read property 'bar' of undefined | ||
|  | ``` | ||
|  | 
 | ||
|  | If you see an error like this, and the `undefined` property refers to the result of a `@ContentChildren` query, it may well be caused by this change.  | ||
|  | 
 | ||
|  | 
 | ||
|  | ### Recommended fix
 | ||
|  | 
 | ||
|  | You can either add the `descendants: true` flag to ignore wrapper elements or remove the wrapper elements themselves. | ||
|  | 
 | ||
|  | Option 1:  | ||
|  | ``` | ||
|  | @ContentChildren('foo', {descendants: true}) foos: QueryList<ElementRef>; | ||
|  | ``` | ||
|  | 
 | ||
|  | Option 2:  | ||
|  | ``` | ||
|  | <comp> | ||
|  |    <div #foo></div>   <!-- matches in both old runtime and  new runtime --> | ||
|  | </comp> | ||
|  | ``` |