question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Imperative View & Template Composition APIs

See original GitHub issue

Note 1: This proposal has its origin in RFC: Exploration of use-cases for Angular JIT compilation mode and aims to address the concerns around AOT compiler not being flexible enough for very dynamic, or heavily data-driven UIs.

Note 2: 📺 Check out the video recording of a community discussion about this proposal @ angularnation.net

Description

While declarative templating is great for most cases, developers often need more flexibility to create UIs dynamically, using components and directives (often) not known at the time of writing the component that generates this UI. Common use cases include data driven UIs (including forms), A/B experiments, UIs generated via plugin systems, etc.

Angular currently enables dynamic view or template creation only with major drawbacks in two ways:

  1. Via the JIT compiler, which can take any string and compile it on the fly within the browser. This approach compromises security of the application (there is a high risk of introducing XSS and compromising the overall security model of Angular) and also comes with a major performance penalty due to the runtime cost of the compilation, and payload size of the overhead of the compiler.

  2. Via ViewContainerRef#createComponent and similar APIs, which don’t offer enough flexibility to dynamically compose views out of a mixture of static HTML, HTML with dynamically created bindings, components, and directives.

Proposed solution

With Ivy rendering/compilation pipeline being the default and ViewEngine being removed in v13, we can finally start taking advantage of flexibility of Ivy without having to worry about compatibility with ViewEngine, this opens up new possibilities including the follow…

We could introduce a new set of APIs that are built specifically for dynamic view composition. A proper research needs to be conducted to determine the shape of the API, but we can use the following PLACEHOLDER APIs to demonstrate the functionality and serve as a foundation for discussion, brainstorming, and actual API design research…

@Component({
   ...
   template: '<button (click)="renderDynamically()">render</button>'
   ...
})
class DemoComponent {
  constructor(private viewContainerRef: ViewContainerRef) {}

  protected async renderDynamically() {

    const myCustomView = await createMyCustomView('./myBlue.component',
      {userName: 'Kahless', myRoutePath: ['my-route'], showDetails: true});

    // appends the view
    viewContainerRef.appendEmbeddedView(myCustomView);
  }
}

Where the templateRef is generated dynamically as follows:

// this doesn't exist yet + API design is to be determined
import {createView, html, component, directive, text, binding, template} from "@angular/core/compose";
import {NgIf} from "@angular/core";
import {RouterLink} from "@angular/router";
import {MyRedComponent} "./myRed.component";
import {MyYellowComponent} "./myYellow.component";

async function createMyCustomView(blueComponentESModulePath, myContext) {
  const blueComponent = (await import(blueComponentESModulePath)).default;

  // this is where the magic happens
  return createView([

    html("<div>some static html, that will be automatically serialized by the `createTemplate` call</div>"),
    component(MyYellowComponent, { projection: [
      text("html string followed by a binding"),

      // {{ userName }}
      binding(() => myContext.userName),

      // you can use javascript expressions instead of `NgIf` to conditionally add view "nodes":
      (Math.random() > 0.5) && text(" and followed by another component"),

      // <my-red [routeLink]="myRoutePath"></my-red>
      component(MyRedComponent, { directives: [
        directive(RouterLink , {binding: () => myContext.myRoutePath})
      ]}),

      // but NgIf works as well:
      // <p *ngIf="showDetails">Some details</p>
      template([
       text("some details")
       ], {directives: [
         directive(NgIf, {binding: () => myContext.showDetails})
       ]})

       // NgForOf, NgSwitch, and any custom regular or structural directives would work as well
       // TODO: update the proposal with a built-in support for structural directives in `@angular/common`,
       //              e.g. `ngIfTemplate` - see suggestion from @sg-gdarnell in the comments below.
       // TODO: update the proposal with an example of how to use pipes in bindings as requested by @ha100790tag 
    ]})
  ]);
}

How would this be implemented?

Functions template, component, directive, text would be just higher level wrappers around the private Ivy instruction APIs.

Risks & unresolve stuff

There is a lot that needs to be researched in this area to determine the complexity of the implementation and overall viability. The following is an incomplete list of unresolved questions / issues:

  • The existing ViewContainerRef APIs accept only TemplateRefs or Components and not views directly. Is this a problem?
  • Do we need to support dynamic template composition (outputting TemplateRef) as well? Or is view composition sufficient?
  • Are current Ivy instruction APIs flexible enough to accomodate this feature or do we need to tweak them?
  • Can we keep all existing security guarantees to prevent XSS?
  • How to attach the new view to the correct injector in the parent?
  • Is the existing $localize API sufficient to support localization?
  • Should we limit this API just to upcoming “standalone” components, directives, and pipes, or can we make this API work with entities that are part of an NgModule without a major hassle?
  • Could this composition API eventually replace the “partial compilation” used to publish Angular libraries? Maybe, it’s a bit of a stretch, but it’s worth a further exploration…
  • and many more…

As a result of these, you can expect major revisions of the proposed solution. The goal of this effort and use-cases this functionality would support should however remain the same.

Are there any trade offs?

Of course, there are always some trade offs.

The main ones that come to my mind:

  • We don’t plan on supporting templates as strings. Only static html or text (without bindings, Angular components or directives) could be provided as string) — such feature would require the availability of the entire JIT compiler at runtime, which would go against the very core goal of this proposal.
  • Performance of creation of dynamically composed views will likely not be as good as that of AOT compiled code. The update performance would be less impacted. There are ways we could improve perf of dynamically created views (e.g. by supporting dynamic template creation), but more research is needed.
  • The API is more verbose than the declarative template syntax. Since the goal here is to enable dynamic view creation using imperative JavaScript/TypeScript code, we are trading off some verbosity for flexibility of the imperative coding.
  • Limited tree-shaking impact — the higher level API might need to retain a few instructions that would otherwise be tree-shaken away if the template was created using AOT compilation, but the impact should be very limited, especially if each higher level API is a standalone tree-shakable function (as proposed in the example above).

What’s the goal of this issue?

The goal is to write down some of the ideas that have been made possible thanks to Ivy and start collecting early feedback that could be used as an input for future RFC.

Alternatives considered

Expose the Ivy instructions as a public API. This has a major drawback that these APIs are low level (not developer friendly) and not stable (it would be hard to evolve the rendering stack if these APIs were public).

Issue Analytics

  • State:open
  • Created 2 years ago
  • Reactions:210
  • Comments:53 (33 by maintainers)

github_iconTop GitHub Comments

13reactions
IgorMinarcommented, Aug 12, 2021

@SanderElias thanks for all the questions. I think @alxhub fielded all/most of them already.

I just want to chime in on:

    tempateText(`
          <app-user [userId]="userId"><app-user>
           <ul>
              <li *nfFor="let pref of preferences>{{pref.name} - {{pref.value}}</li>
           </ul>`)
     } ) ; 

Dynamic template creation out of templates is something that I’d really like to avoid - it would basically be just a glorified way of using the current JIT compiler in production, which has lots and lots of problems (critical security, performance, and maintenance issues). I have an RFC coming out today, exploring the possibility of eventually removing JIT compilation mode entirely from Angular. I should post it within the next few hours — please stay tuned.

The goal of this proposal is to enable dynamic composition of views in a way that is performant and secure. Many of us would like to have the good old $compile back from the AngularJS days, and this proposal tries to strike the balance between the flexibility of $compile and security and performance of AOT compilation.

9reactions
gmaggiodevcommented, Nov 16, 2021

View Composition is something that we are waiting since 2016 so this is a great news. But (always there is a “but” we use to say), the flat structure is absolutely not enough for us. I try to explain our project.

We created a UI Framework Platform, based on Angular, that allow Application Developers to create pages using an interactive designer at runtime without the need to code the corresponding components and compile it.

Simplifying a lot to understand the concept:

  • Our UI designer tool will generate this JSON inside the DB:
{
    "type": "grid-layout",
    "items": [
        {
            "type": "card",
            "properties": {
                "cols": 4,
                "title": "Alex Dornick",
                "subtitle": "Front-End Developer",
                "imgSrc": "/assets/images/alex.JPG",
                "imgMode": "avatar"
            },
            "items": [{
                "type": "simple-panel",
                "properties": {
                    "position": "footer"
                },
                "items": [
                    {
                        "type": "label",
                        "properties": {
                            "repeat": "let i of [1,2,3]",
                            "text": "Label {{i}}"
                        }
                    }
                ]
            }]
        }
    ]
}

When a user access to this page…

  • Our UI Engine transform JSON into angular HTML that will be incorporated as view inside a runtime compiled component/module:
<tf-grid-layout id="dfd32969-0bc9-4fa7-b57e-02f7ddd7cfc3">
    <tf-card cols="4" title="Alex Dornick" subtitle="Front-End Developer" imgSrc="/assets/images/alex.JPG" imgMode="avatar" id="cc33f245-dcfc-4d39-b24d-291430a38048">
        <tf-simple-panel footer id="807e08ab-8a93-44e0-8544-3af2f9ff057d">
            <ng-template ngFor let-let i [ngForOf]="[1,2,3]" let-i="index">
                <tf-label text="Label {{i}}" id="4ef3dfb0-e3d8-4a0a-b255-ff173c188ed0"></tf-label>
            </ng-template>
        </tf-simple-panel>
    </tf-card>
</tf-grid-layout>
  • And the component is rendered immediatly and interact with the application

image

All this work, obviously, is based on JIT. A flat imperative view is not enough.
Please, @IgorMinar consider it in your work.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Vue.js Composition API with Templates - GeeksforGeeks
In the Composition API with Templates, we declare the variables ... Step 4: Run the project using the following command and see the...
Read more >
Vue 3 - The Composition API (Part 1) -- newline - Fullstack.io
What is the Composition API?# ... A single file component consists of HTML-based markup ( <template /> ), CSS-based styles ( <style />...
Read more >
Template Refs - Vue.js
Accessing the Refs #. To obtain the reference with Composition API, we need to declare a ref with the same name: vue <script ......
Read more >
Why you should be using Vue's new Composition API
Previously in Vue, you'd get an error if you tried to render a component with more than one top-level element under the template...
Read more >
Vue 3 Reactivity - Learn Composition Functions API - Netlify
See how Vue 3 changes the way we write and reason about Vue moving forward. Check out this post that dives into the...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found