Skip to content

fix(ivy): take components into account when discovering directives #26113

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Conversation

pkozlowski-opensource
Copy link
Member

This PR fixes an issue where the includeComponents argument to the
discoverDirectiveIndices was not taken into account.

Additionally it returns components as part of directives list when
calling getDirectives (please not that we can't parametrize this
function with includeComponents as results are cached).

@pkozlowski-opensource pkozlowski-opensource force-pushed the discovery_util_fixes_featqures branch 2 times, most recently from 83e6567 to 5be12f8 Compare September 26, 2018 10:36
@mary-poppins
Copy link

You can preview 23a9b33 at https://pr26113-23a9b33.ngbuilds.io/.

@mary-poppins
Copy link

You can preview 83e6567 at https://pr26113-83e6567.ngbuilds.io/.

@mary-poppins
Copy link

You can preview 5be12f8 at https://pr26113-5be12f8.ngbuilds.io/.

@pkozlowski-opensource pkozlowski-opensource added action: review The PR is still awaiting reviews from at least one requested reviewer target: major This PR is targeted for the next major release comp: ivy labels Sep 26, 2018
@@ -353,21 +353,22 @@ function getLNodeFromViewData(lViewData: LViewData, lElementIndex: number): LEle
* (which is referenced by the lNodeIndex)
*/
export function discoverDirectiveIndices(
lViewData: LViewData, lNodeIndex: number, includeComponents?: boolean): number[]|null {
lViewData: LViewData, lNodeIndex: number, includeComponents: boolean = false): number[]|null {
const directivesAcrossView = lViewData[DIRECTIVES];
const tNode = lViewData[TVIEW].data[lNodeIndex] as TNode;
if (directivesAcrossView && directivesAcrossView.length) {
Copy link
Contributor

@kara kara Sep 26, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that we know the component is stored first, I'm not sure this loop is necessary anymore. I think to get the directives list in discoverDirectives, we can just copy the existing directives array using the start/end indices:

function discoverDirectives(nodeIndex: number, lViewData: LViewData, includeComponents: boolean = false) {
  const directiveInstances = lViewData[DIRECTIVES];
  if (directiveInstances != null) {
     const tNode = lViewData[TVIEW].data[nodeIndex] as TNode;
     const startIndex = 
         includeComponents ? getDirectiveStartIndex(tNode) : getDirectiveStartIndex(tNode) + 1;
     const endIndex = getDirectiveEndIndex(tNode, startIndex);
     return directiveInstances.slice(startIndex, endIndex);
  }
  return null;
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, right! This is much simpler indeed, thnx!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, so finally it is slightly more complex as we can't simply do const startIndex = includeComponents ? getDirectiveStartIndex(tNode) : getDirectiveStartIndex(tNode) + 1; const endIndex = getDirectiveEndIndex(tNode, startIndex); as we don't know if a given element has a component on it or not.

Anyway, I've decided to change this method to always return components and people can filter them out if not needed.

@@ -353,21 +353,22 @@ function getLNodeFromViewData(lViewData: LViewData, lElementIndex: number): LEle
* (which is referenced by the lNodeIndex)
*/
export function discoverDirectiveIndices(
lViewData: LViewData, lNodeIndex: number, includeComponents?: boolean): number[]|null {
lViewData: LViewData, lNodeIndex: number, includeComponents: boolean = false): number[]|null {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The default assignment generates some additional code, especially in ES5 targets. Why not simply leave it optional?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changing back to includeComponents?: boolean

const context = loadContext(target) !;

if (context.directives === undefined) {
context.directiveIndices = discoverDirectiveIndices(context.lViewData, context.lNodeIndex);
context.directiveIndices =
discoverDirectiveIndices(context.lViewData, context.lNodeIndex, true);
Copy link
Contributor

@matsko matsko Sep 27, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this now mean that getDirectives will return any components inside no matter what? Can there not be an if statement that checks the first value to see if it's a component instance? There should be a new function here if you want to have a combined list of of the component + directives.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding the includeComponents flag (falsy by default) to this method so we can decide if components should be returned or not.

Copy link
Contributor

@mhevery mhevery left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please take care of the comments. LGTM otherwise.

@@ -98,11 +98,12 @@ export function getInjector(target: {}): Injector|null {
* Returns a list of all the directives that are associated
* with the underlying target element.
*/
export function getDirectives(target: {}): Array<{}> {
export function getDirectives(target: {}): any[] {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think {}[] is better than any[] as it is more restrictive. For example array of numbers will not match {} but will match any

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, changing it back

@mhevery mhevery added action: cleanup The PR is in need of cleanup, either due to needing a rebase or in response to comments from reviews and removed action: review The PR is still awaiting reviews from at least one requested reviewer labels Sep 27, 2018
@pkozlowski-opensource pkozlowski-opensource force-pushed the discovery_util_fixes_featqures branch from 5be12f8 to 1d68e58 Compare September 27, 2018 12:46
@mary-poppins
Copy link

You can preview 1d68e58 at https://pr26113-1d68e58.ngbuilds.io/.

@pkozlowski-opensource pkozlowski-opensource removed the action: cleanup The PR is in need of cleanup, either due to needing a rebase or in response to comments from reviews label Sep 27, 2018
@pkozlowski-opensource
Copy link
Member Author

Marking as merge-assistance since:

@pkozlowski-opensource pkozlowski-opensource added the merge: caretaker note Alert the caretaker performing the merge to check the PR for an out of normal action needed or note label Sep 27, 2018
@@ -89,15 +89,18 @@ export function getContext(target: any): LContext|null {
}
directiveIndices = discoverDirectiveIndices(lViewData, lNodeIndex);
directives = directiveIndices ? discoverDirectives(lViewData, directiveIndices) : null;
if (directives && directives.length && isComponentInstance(directives[0])) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can avoid the megamorphic lookup in isComponentInstance if we use the isComponent method and pass in the tNode

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed - didn't know about this util!

const result = context.directives || [];

// discard first directive if it is a component and we should not include components
if (!includeComponents && result.length && isComponentInstance(result[0])) {
Copy link
Contributor

@kara kara Sep 27, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here. I think we can just check the isComponent flag using the isComponent method. We can do it inside discoverDirectives maybe, since we already have the tNode there?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I don't think we've got access to TNode at this point any more...

Copy link
Contributor

@kara kara Sep 27, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can look it up using context.lNodeIndex, see discoverDirectiveIndices. I believe this should still be better than the megamorphic lookup.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kara see alternative proposal in https://github.com/angular/angular/pull/26113/files#r221942800

If we could store directives start index, no of directives we could easily just slice lViewData here. And we could use TNode's isComponent to adjust slicing start. We could even store a isComponent flag on LContext to avoid TNode lookup all the time.

WDYT?

@pkozlowski-opensource pkozlowski-opensource force-pushed the discovery_util_fixes_featqures branch from 1d68e58 to 04ed6af Compare September 27, 2018 16:18
@mary-poppins
Copy link

You can preview 04ed6af at https://pr26113-04ed6af.ngbuilds.io/.


const result = Array.from({length: idxEnd - idxStart}, (v, k) => idxStart + k);

return result && result.length ? result : null;
Copy link
Contributor

@kara kara Sep 27, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I understand why we still need to generate an array of indices when we could just slice from the directives array directly to retrieve the instances?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kara I think I might know where the confusion is coming from - you comment seems to imply that the discoverDirectiveIndices is used only to get directive instances but the way I understood the existing code is that discoverDirectiveIndices and discoverDirectives are 2 independent functions that could, but don't have to be used together.

In fact for the TestBed implementation I don't care about directive instances but need to get directive definitions. For this case the discoverDirectiveIndices comes handy and in this particular case I just don't want to slice LViewData to get directive instances as I don't care about instances.

In the similar spirit I've interpreted discoverDirectives as a utility function that can take any set of indices (not all directives present on a node need to be present in this set) in any particular order. Maybe this wasn't the intention of those 2 functions.

Having said all this I very much dislike the fact that we've got both directive indices and directive instances on LContext as those things can easily get out of sync.

Maybe a better approach would be to store sth like directvesStartIndex and directivesCount and have higher-level utility functions working on those 2 pieces of data? If we store the above:

  • we don't have to generate an array of indices here;
  • utility methods to get a set of directive instances / directive defs become trivial (array slicing as you suggest).

WDYT?

Copy link
Contributor

@kara kara Oct 2, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you need the defs in TestBed and don't want to look up the TNode, I'd prefer storing the start index/count or the TNode itself (which contains the start index/count) in LContext. Otherwise we are unnecessarily allocating an extra array in memory, as well as looping over the directives array twice for no good reason.

IIRC, the original intent of discoverDirectiveIndices was simply to grab non-component directive indices when we couldn't assume the component would be first in the list. I don't think it's particularly useful externally, so I think we should get rid of it and discoverDirectives/TestBed should use start/end indices to slice what they need directly.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, so based on your comment it seems to me like I'm trying to re-purpose the discoverDirectives / discoverDirectiveIndices in the way that is different from the initial / intended design. As such I think that I will just add the needed code to the TestBed impl (I've got access to TNode).

I'm going to close this PR for now. In the future, if there is some code that can be shared / refactored I will pull it into LContext. Thnx for taking time to review and bearing with me!

@kara kara added the action: review The PR is still awaiting reviews from at least one requested reviewer label Sep 27, 2018
@pkozlowski-opensource pkozlowski-opensource removed the merge: caretaker note Alert the caretaker performing the merge to check the PR for an out of normal action needed or note label Oct 2, 2018
@pkozlowski-opensource
Copy link
Member Author

@kara I've got an impression that we think differently about role of the discoverDirectiveIndices / discoverDirectives functions, see my comment here: #26113 (comment)

I think that we can simplify the whole system, there is a proposal in the above comment. Let's discuss this in person later today.

@pkozlowski-opensource pkozlowski-opensource force-pushed the discovery_util_fixes_featqures branch from 04ed6af to 2d71f63 Compare October 2, 2018 13:17
@mary-poppins
Copy link

You can preview 2d71f63 at https://pr26113-2d71f63.ngbuilds.io/.

@pkozlowski-opensource pkozlowski-opensource force-pushed the discovery_util_fixes_featqures branch from 2d71f63 to 8210f54 Compare October 2, 2018 13:52
This PR fixes an issue where the includeComponents argument to the
discoverDirectiveIndices was not taken into account.

Additionally it returns components as part of directives list when
calling getDirectives (please not that we can't parametrize this
function with includeComponents as results are cached).
@mary-poppins
Copy link

You can preview 8210f54 at https://pr26113-8210f54.ngbuilds.io/.

@pkozlowski-opensource
Copy link
Member Author

Closing based on #26113 (comment)

@angular-automatic-lock-bot
Copy link

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Sep 14, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
action: review The PR is still awaiting reviews from at least one requested reviewer cla: yes target: major This PR is targeted for the next major release
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants