2

In order to integrate with an exising native JavaScript library that does DOM manipulation, I'd want to be able to attach an Angular-managed view to any DOM element in document.

Let's say that SomeLibrary creates a container, somewhere in the DOM (we give it a target node where it can inject its HTML):

<body>
    <angular-app-root>
        <!-- here... -->
        <div id="some-library-created-this"></div>
    </angular-app-root>

    <!-- or more likely, here... -->
    <div id="some-library-created-this"></div>
</body>

I have a reference to the div#some-library-created-this DOM node.

Now, I have an Angular component that can be used like this:

<angular-component [angularInput]="value">
    <!-- what follows isn't the `template`, but what would get set in a ng-content inside of AngularComponent's template -->
    <h1>Embedded Content Children</h1>
    <hr>
    <another-angular-component></another-angular-component>
</angular-component>

This AngularComponent should now take the content that has been set inside of it, and display it inside the div#some-library-created-this node we've got.

That is, the content of the component won't be displayed where the component has been declared, but somewhere else in any given native element.

Something like that:

<body>
    <angular-app-root></angular-app-root>

    <div id="some-library-created-this">
        <some-kind-of-wrapper> <!-- ? -->
            <h1>Embedded Content Children</h1>
            <hr>
            <another-angular-component></another-angular-component>
        </some-kind-of-wrapper>
    </div>
</body>

Is that even possible? Are there equivalent solutions that would let me benefit from SomeLibrary's unmanaged DOM manipulation logics while taking advantage of Angular's features?

I've read a few posts that show similar and advanced use cases, like these:

But those only speak about creating components that are known in advance, and creating them specifically for a given view, not just take them and throw them elsewhere on the screen.

Morgan Touverey Quilling
  • 4,181
  • 4
  • 29
  • 41
  • `But those only speak about creating components that are known in advance, and creating them specifically for a given view` but before you wrote `Now, I have an Angular component that can be used like this` so I can conclude that your component is known in advance – yurzui Oct 27 '17 at 21:08
  • `appRef.bootstrap` and `componentFactory.create` have optional parameter `selectorOrNode` that can help you – yurzui Oct 27 '17 at 21:10
  • https://stackoverflow.com/questions/44187413/angular-2-create-viewref-from-markup-injected-into-dynamic-template/44189586#44189586 or https://stackoverflow.com/questions/45896367/is-it-possible-to-run-one-angular-2-app-several-times-in-one-page/45897001#45897001 – yurzui Oct 27 '17 at 21:12
  • I meant: the content of that AngularComponent isn't known in advance. I didn't put its `template` in the example, but what an API consumer would pass. Basically what goes inside `template: ''`. – Morgan Touverey Quilling Oct 27 '17 at 21:21
  • We can't transclude content in a root component. JitCompiler might help you. but you aot in this case – yurzui Oct 27 '17 at 21:27
  • Just thought about something. If we pass s to that AngularComponent via , could it retrieve them, then call a service, that creates a "WrapperComponent" into the native DOM node (using techniques linked above), and that WrapperComponent would display the template(s) passed to it via the service. Does that makes sense? – Morgan Touverey Quilling Oct 27 '17 at 21:47
  • Did it using this technique (a better variant, see my answer), it's working very well! I wonder why I didn't thought about that sooner, as it's been a while that I'm seeking (in background) for a solution to implement that. – Morgan Touverey Quilling Oct 30 '17 at 22:49

1 Answers1

1

I managed to do it with <ng-template>s, which is even better because the view inside the component isn't instantiated before the native library displays its DOM element. Also, that lets me target different DOM nodes of the native library inside the same component body, as you can see below:

<angular-component [angularInput]="value">
  <form *angularComponentPartial [formControl]="myForm">
    ...
  </form>

  <ng-container *angularComponentPartial="angularComponentTargets.otherKnownDomNode">
    Send ({{ secondsLeft }} seconds left)
  </ng-container>
</angular-component>

The custom *angularComponentPartial structural directive is used to get a TemplateRef of the user-defined content. Then, it creates a special component and mounts it on the library DOM node, with the help of ComponentFactoryResolver. It passes the TemplateRef to that component whose only role is to display the template, thanks to a simple template outlet:

<ng-container *ngTemplateOutlet="template"></ng-container>

The AngularComponent don't even have to handle that fake-transclusion logic. Only the structural directive and a tiny template holder component are involved.

If you're interested about the implementation in the details, that's open source! I use it in toverux/ngsweetalert2 (at time of writing, it's still in the next branch). It's used to display dynamic content inside a SweetAlert2 modal.

Morgan Touverey Quilling
  • 4,181
  • 4
  • 29
  • 41