Skip to content

fix(ivy): flatten template fns for nested views#24943

Closed
kara wants to merge 3 commits into
angular:masterfrom
kara:ngIf
Closed

fix(ivy): flatten template fns for nested views#24943
kara wants to merge 3 commits into
angular:masterfrom
kara:ngIf

Conversation

@kara
Copy link
Copy Markdown
Contributor

@kara kara commented Jul 18, 2018

This PR fixes contexts for recursive components and components with deeply nested ngFors. It also fixes bindings to local refs before local ref definitions.

Changes:

  • Template functions for dynamically created views are no longer nested inside each other. Instead of nesting the functions and using closures to get parent contexts, the parent contexts are re-defined explicitly through an instruction. This means we no longer create multiple function instances for loops that are nested inside other loops.

Example:

<ul *ngFor="let list of lists">
   <li *ngFor="let item of list'> {{ item }} {{ name }} </li>
</ul>

Before:

function AppComponent(rf: RenderFlags, ctx: AppComponent) {
      // some instructions here
      function ulTemplate(rf1: RenderFlags, ctx0: any) {
            // instructions

            function liTemplate(rf1: RenderFlags, ctx1: any) {...}
      }
}

After:

function ulTemplate(rf: RenderFlags, ctx: any) {...}
function liTemplate(rf: RenderFlags, ctx: any) {
  if (rf & RenderFlags.Create) {
      T(0);
  }
  if (rf & RenderFlags.Update) {
     const item = ctx.$implicit;
     const comp = x(2);
     t(0, i2('', item, ' ', comp.name, ''));
  }
}

function AppComponent(rf: RenderFlags, ctx: AppComponent) {
      // some instructions here
}
  • TView.components no longer tracks directive indices (just host element indices). We can cut the components array in half because context for components is now being stored in the component's LViewData instance and can be accessed from there.
  • We now have a new instruction, reference() (r()). Previously, we were using closures to get access to local refs in parent views. Now that nested template functions are flat, they do not have access to the local refs through a closure, so we need another way to walk the view tree. reference takes a nesting level and the local ref's index, then walks the view tree to find the correct view from which to load the local ref.

Before:

function AppComponent(rf: RenderFlags, ctx: AppComponent) {
  if (rf & RenderFlags.Create) {
      Ee(0, 'div', null, ['foo', '']);
      C(2, nestedTemplate, null, [AttributeMarker.SelectOnly, 'ngIf']);
  }
  const foo = load(1) as any;
  function IfTemplate(rf: RenderFlags, ctx: any) {
      if (rf & RenderFlags.Create) {
         T(0);
      }
     if (rf & RenderFlags.Update) {
         t(0, bind(foo));
     }
  }
}

After:

function IfTemplate(rf: RenderFlags, ctx: any) {
    if (rf & RenderFlags.Create) {
       T(0);
    }
   if (rf & RenderFlags.Update) {
      const foo = reference(1, 1) as any;
      t(0, bind(foo));
   }
}
function AppComponent(rf: RenderFlags, ctx: AppComponent) {
  if (rf & RenderFlags.Create) {
      Ee(0, 'div', null, ['foo', '']);
      C(2, nestedTemplate, null, [AttributeMarker.SelectOnly, 'ngIf']);
  }
}

@kara kara force-pushed the ngIf branch 4 times, most recently from bbcc7e6 to 90eb88f Compare July 18, 2018 05:17
@kara kara added action: review The PR is still awaiting reviews from at least one requested reviewer and removed state: WIP labels Jul 18, 2018
@kara kara requested a review from mhevery July 18, 2018 06:09
Comment thread packages/core/src/render3/instructions.ts Outdated
Comment thread packages/core/src/render3/instructions.ts Outdated
Comment thread packages/core/test/render3/component_spec.ts Outdated
Comment thread packages/core/src/render3/instructions.ts Outdated
Comment thread packages/core/src/render3/instructions.ts Outdated
Comment thread packages/core/src/render3/instructions.ts Outdated
Comment thread packages/core/src/render3/interfaces/definition.ts Outdated
Comment thread packages/core/src/render3/interfaces/view.ts Outdated
Comment thread packages/compiler/test/render3/r3_compiler_compliance_spec.ts Outdated
Comment thread packages/core/src/render3/instructions.ts Outdated
Comment thread packages/core/src/render3/instructions.ts Outdated
Comment thread packages/core/src/render3/instructions.ts Outdated
Comment thread packages/core/src/render3/instructions.ts Outdated
@kara kara force-pushed the ngIf branch 3 times, most recently from 61a7d91 to e68ff53 Compare July 23, 2018 21:39
@kara kara removed the state: WIP label Jul 23, 2018
@kara kara force-pushed the ngIf branch 3 times, most recently from c4eee9b to 4705613 Compare July 23, 2018 22:10
@angular angular deleted a comment from mary-poppins Jul 23, 2018
@angular angular deleted a comment from mary-poppins Jul 23, 2018
@angular angular deleted a comment from mary-poppins Jul 23, 2018
@angular angular deleted a comment from mary-poppins Jul 23, 2018
@mary-poppins
Copy link
Copy Markdown

You can preview d4103d6 at https://pr24943-d4103d6.ngbuilds.io/.

@mary-poppins
Copy link
Copy Markdown

You can preview 7e5595a at https://pr24943-7e5595a.ngbuilds.io/.

Comment thread packages/core/src/render3/instructions.ts Outdated
Comment thread packages/core/test/render3/common_integration_spec.ts Outdated
Comment thread packages/core/src/render3/instructions.ts Outdated
@mhevery mhevery added target: major This PR is targeted for the next major release and removed action: review The PR is still awaiting reviews from at least one requested reviewer labels Jul 29, 2018
@mary-poppins
Copy link
Copy Markdown

You can preview 26f76b1 at https://pr24943-26f76b1.ngbuilds.io/.

@kara
Copy link
Copy Markdown
Contributor Author

kara commented Jul 29, 2018

presubmit

@kara kara unassigned mhevery and alxhub Jul 29, 2018
@kara kara added the action: merge The PR is ready for merge by the caretaker label Jul 30, 2018
@trotyl
Copy link
Copy Markdown
Contributor

trotyl commented Jul 30, 2018

Before this change, private properties usage is fully supported in any template, but this change is going to make private properties usage quite inconsistent. Given the class:

class AppComponent {
  private foo = 42
}

Then using private property directly inside Component template is allowed:

<!-- Works always -->
<div>{{ foo }}</div>

But using private property inside any embedded template is NOT allowed:

<!-- Works before -->
<!-- Throws after -->
<div *ngIf="true">{{ foo }}</div>

Even supporting private property is not promised, but the implementation detail could make it confusing to users.


Update: I guess I was wrong and the condition seem even worse, given nextContext is not generic, so type-checking not happened at all?

@mhevery
Copy link
Copy Markdown
Contributor

mhevery commented Jul 30, 2018

@trotyl

  1. Private references to context should never be supported and it is not supported in current version, so I am confused about your comment.
  2. Type checking is a separate issues which is outside the scope of the runtime.

@mary-poppins
Copy link
Copy Markdown

You can preview 180082e at https://pr24943-180082e.ngbuilds.io/.

@trotyl
Copy link
Copy Markdown
Contributor

trotyl commented Jul 31, 2018

@mhevery I didn't mean it's a breaking change from view engine, but having in-fact partial support is confusing (as it's indeed supported in component template by Ivy).

Imagine a user have private property already and everything is fine in AOT, once he/she added an *ngIf then it suddenly becomes an error.

Also polluting entire module scope with additional FunctionDeclarations is dangerous and could break users' code.

So how about using IIFE while and do NOT emit code outside the ClassDeclaration like:

class MyComponent {
  static ngComponentDef = ɵdefineComponent({
    template: (() => {
      function MyComponent_li_Template_2(rf: RenderFlags, ctx: SomeContext) {
        // ...
      }
      function MyComponent_Template(rf: RenderFlags, ctx: MyComponent) {
        if (rf & 1) {
          ɵE(0, "ul", null, $c1$);
          ɵC(2, MyComponent_li_Template_2, null, $c2$);
          ɵe();
        }
        // ...
      }
      return MyComponent_Template;
    })()
  })
}

I guess this is also what TypeScript does in their code emitting.

@trotyl
Copy link
Copy Markdown
Contributor

trotyl commented Aug 1, 2018

@mhevery Please also consider the possibility of ClassExpression (or non-top level ClassDeclaration) support in the future, a ClassExpression could have same name or even no name:

const createFooComponent = (val: number) => (flag ? @Component({}) class FooComp { foo = val } : @Component({}) class FooComp { foo = -val })
const createBarComponent = (val: number) => (@Component({}) class { bar = val })

And it's impossible to emit code to their own scopes, but only at top-level. Then the compiler would need to maintain an extra class counter per module.

@angular-automatic-lock-bot
Copy link
Copy Markdown

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 13, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

action: merge The PR is ready for merge by the caretaker cla: yes target: major This PR is targeted for the next major release type: bug/fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants