Skip to content

viewContainerRef.renderComponent should support hook on hostElement like createComponent function #51491

Closed

Description

@hiepxanh

Which @angular/* package(s) are relevant/related to the feature request?

core

Description

hi @AndrewKushnir, look like ngx-dynamic-hooks work correctly and it using latest version of Angular core I just update. I tried to get rid of flick and still experience that issue. I guess since they using renderer2 to render html direct instead of component render,so it cause this problem. So I decide do my own. look at the test file detail give me a lot of clue. But it not cover the case that component selector mix with normal html.

AndrewKushnir@f9d02a7#diff-af4c8e2af18bf92515f7cb47ed0e7a8be798116a9ca5c1e15f2ba4c3aa7b79b6

Which is the real world use case. For example I have a html code send from server:

content = `
<h1> hello world 1</h1>  
  <dynamic1> </dynamic1> 
  <dynamic2> </dynamic2>  
  <h1> hello world 2</h1>
  `;
  

CONTEXT

  • I have a dictionary file to know tag-selector pair with which component.
  • I'm using RenderComponent below to render it and it work but it only able to render 1 component. and append it to the last of component as default.
  • I want to use selector as the hook and render component to that position but I'm confuse about this place. Now I have two option

TWO OPTION

  • using angular createComponent() function from angular/core to hook and create, then attachView as tutorial. It look like very useful and promising with hostElement to use as hook. But it just not working... https://angular.io/api/core/createComponent

  • use vcr.createComponent which is work geat. But it only append to last entry. See the index option parameter (index: the index at which to insert the new component's host view into this container. If not specified, appends the new view as the last entry). But this index is angular render level index instead of string position index. What should I do? How can I use that index? Or maybe we can have other solution, since there is no DOM on server to hook on. Maybe I can use renderer3 or something?

  • since the htmlContent come from server, I can do some regex to mark on selector target like <dynamic #youShouldHookHere></dynamic1> or <ng-content> <dynamic1> </dynamic1> <ng-content>

@Component({
  standalone: true,
  selector: 'dynamic1',
  template: `
  <ng-content />
`,
})
class Dynamic1Component {
}

@Component({
  standalone: true,
  selector: 'dynamic2',
  template: `
  <ng-content />
`,
})
class Dynamic2Component {
}

@Component({
  standalone: true,
  selector: 'app',
  template: `<div [innerHTML]="content"></div>`,
})
class RenderComponent {
  content = `<h1> hello world 1</h1>  
  <dynamic1> </dynamic1> 
  <dynamic2> </dynamic2>  
  <h1> hello world 2</h1>
  `;
  vcr!: ViewContainerRef;
  envInjector = inject(EnvironmentInjector);

  ngAfterViewInit() {
    // solution 1: this one work but only auto append at last
     const compRef = this.vcr.createComponent(Dynamic1Component);
     compRef.changeDetectorRef.detectChanges();
     
    // QUESTION: but how to render on dynamic1 hook? It will be perfect if we can give some instruction to hook on that to render....
    
    // solution 2: use application ref or use environmentInjector not work at all.
    const compRef = createComponent(Dynamic1Component, {
      // environmentInjector: this.envInjector, // not work
        environmentInjector: this.applicationRef.injector,
    });
  }
}

Proposed solution

If this one work, it can open a new world for SSR and hydration. Look at the insertImpl function I can see that index is the list of render command. Maybe we can add some renderId to mark on it?

image

@Component({
  standalone: true,
  selector: 'app',
  template: `<main>Hi! This is the main content.<dynamic></dynamic> </main>`,
})
class AppComponent {
  vcr = inject(ViewContainerRef);

  ngAfterViewInit() {
    const compRef = this.vcr.createComponent(DynamicComponent, { 
    hostElement: // propose 1: this one will look like createComponent function,use DOCUMENT in angular/common support by domnio on server 
    selector: 'dynamic', //  propose 2:  this solution, so domino will search for hostElement use querySelectorAll
    stringReplace: '<dynamic></dynamic>' // propose 3:  let developer parse job, angular just do string replace
     } );
    compRef.changeDetectorRef.detectChanges();
  }
}

Alternatives considered

Thank for your support, I will close this issue once I found the necessary suggestion.

I'm thinking of recursion component. It will try to render it self. For example:

  content = `<h1> hello world 1</h1>  
  <dynamic1> </dynamic1> 
  <dynamic2> </dynamic2>  
  <h1> hello world 2</h1>
  `;

will be convert to:

  content = `<h1> hello world 1</h1>  
  <recursion-component [content]="'<dynamic1> <\/dynamic1>'"> </recursion-component> 
  <recursion-component [content]="'<dynamic1> <\/dynamic1>'"> </recursion-component> 
  <h1> hello world 2</h1>
  `;

So in theory, we only render one and only component recursion-component it will detect if it this component is only single component selector content or mix html content and decide to render or continue the recursion loop.

Activity

  1. hiepxanh commented on Aug 24, 2023

    @hiepxanh
    ContributorAuthor

    image

    I also research the test file but it seem like not exist, I think it not work correctly. Maybe can create new issue since I have example repo to reproduce not working version: https://codesandbox.io/p/sandbox/relaxed-feather-27fv8y?file=/src/app/app.component.ts:2,3

    created to this issue: #51492

  2. changed the title [-]viewContainerRef.renderComponent should support hook render [/-] [+]viewContainerRef.renderComponent should support hook on hostElement like renderComponent function[/+] on Aug 24, 2023
  3. changed the title [-]viewContainerRef.renderComponent should support hook on hostElement like renderComponent function[/-] [+]viewContainerRef.renderComponent should support hook on hostElement like createComponent function[/+] on Aug 24, 2023
  4. hiepxanh commented on Aug 24, 2023

    @hiepxanh
    ContributorAuthor

    I can understand why ngx-dynamic-hooks cause flickering. Because it use renderer2 to insert html, and on the client it not notice about exist html and try to render again. So the problem is about this issue: how to parse the mix html to render correct component and render it by the hook. Or warn renderer2 to skip hydration content or something

  5. added this to the needsTriage milestone on Aug 24, 2023
  6. AndrewKushnir commented on Aug 26, 2023

    @AndrewKushnir
    Contributor

    @hiepxanh thanks for creating a repro. The problem is that you've used a binding value that would always be different for each change detection cycle, i.e. <div [innerHTML]="domSanitizer.bypassSecurityTrustHtml(content)"></div>. The bypassSecurityTrustHtml would return a new object each time it's invoked. You can try adding a new property to a class that would store a reference to the function call result and use it in a template instead. For example:

    @Component({
      standalone: true,
      selector: 'my-app',
      template: `<div [innerHTML]="sanitizedContent"></div>`,
    })
    export class AppComponent {
      domSanitizer = inject(DomSanitizer);
    
      content = `<h1> hello world 1</h1>  
        <dynamic1> </dynamic1> 
        <dynamic2> </dynamic2>  
        <h1> hello world 2</h1>`;
    
      // Store a reference to the computed result and use it in a template
      sanitizedContent = this.domSanitizer.bypassSecurityTrustHtml(this.content);
    

    Thank you.

  7. hiepxanh commented on Aug 27, 2023

    @hiepxanh
    ContributorAuthor

    Thank you so much sir, it work and really interesting. I think this is a little trick and should be more clear on the document. Do you think can I add one more example to document?

    Close issue since I can work with createComponent, not necessary to add that feature

  8. angular-automatic-lock-bot commented on Sep 27, 2023

    @angular-automatic-lock-bot

    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.

  9. locked and limited conversation to collaborators on Sep 27, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area: coreIssues related to the framework runtime

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions