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
Imperative View & Template Composition APIs #43120
Comments
Question: Isn't it possible to create a small public layer on top of the Ivy instruction set? |
Another question: We can already mix-and-match more dynamic templates by using Angular Elements(web components generated by angular). |
@IgorMinar Thanks for this proposal! It really looks good. // this is where the magic happens
return createView([
html("<div>some static html, that will be automatically serialized by the `createTemplate` call</div>"),
tempateText(`
<app-user [userId]="userId"><app-user>
<ul>
<li *nfFor="let pref of preferences>{{pref.name} - {{pref.value}}</li>
</ul>`)
} ) ;
} ])
] ) ; Where it uses the same context as the Adding support for "normal" templates will name the use of this API a lot simpler. I know using it will need to ship the full Ivy compiler to the client, but I would like to be able to make that trade-off. |
I think that's exactly what this is - it's an API to imperatively construct an Ivy template function, but which doesn't require you to have specific knowledge about how to call the actual template instructions.
I don't think so, because such components still need to be written statically, even if their instantiation is more dynamic. This proposal allows for very dynamic constructions of views, based on runtime data structures.
This is a cool idea, too. One of the concerns here is that it would require some notion of a template scope (what |
@alxhub Thanks for the quick answers! It is good to have this cleared out of the way.
This would be the same thing as it is for stand-alone(module-less) components, or am I looking from the wrong side? Those problems need to be solved anyway for truly dynamic components as far as I can see. |
Sort of. The problem is that this scope has to be defined at runtime instead of compile time. One of the challenges here is that Angular actually erases scope information for |
@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 |
I added a new section to the issue above describing the trade offs of this proposal. |
@petebacondarwin kindly pointed out that my example TS code was not syntactically valid due to typos, misaligned braces, and most problematically the use of |
@IgorMinar the first snippet has a const named protected async renderDynamically() {
const myCustomView = await createMyCustomView('./myBlue.component',
{userName: 'Kahless', myRoutePath: ['my-route'], showDetails: true});
// appends the view
viewContainerRef.appendEmbeddedView(myCustomView);
}
async function createMyCustomView(blueComponentESModulePath: string, myContext: {[key: string]: any}) {
const blueComponent = (await import(blueComponentESModulePath)).default;
// this is where the magic happens
return createView(...);
} |
@jnizet thanks you so much for pointing this out! I think I updated the code with all of your suggestions. I expected the code to have some minor syntax flaws when I was typing it out in the github issue editor, but I see that the next time I do something like this I should instead create a stackblitz that compiles but doesn't run. |
I love this, it's basically what I always hoped Ivy would enable. However, I'm confused about how updates will work. I realize the syntax is nowhere close to final, but I'll use it as a reference for discussion. In the example, if you used plain JavaScript expressions ( If we do need to limit ourselves to using structural directives over plain expressions then it may be appropriate to provide some helper functions for the built-in stuff, so maybe instead of this:
You could do:
Of course, the community could build helper functions like that themselves. My main concern is that the way expressions restrict the ability to update a view may trip people up. Thanks for sharing this openly! I'm excited for all the things a more dynamic Angular could bring. |
@sg-gdarnell great points, thanks for bringing them up! The important thing to note is that the current design doesn't rely on reevaluating the This brings me to the second part of your suggestion, it absolutely makes sense to add native support for |
Awesome proposal! Thanks! (+ thanks for the hidden Star Trek reference) |
@manfredsteyer wow, that was fast! I thought that it would take longer for someone to connect the dots. #highFive err... #elbowBump |
@IgorMinar Thanks for the additions and the answers. After having seen enough examples of how Now a new question arises: // you can use javascript expressions instead of `NgIf` to conditionally add view "nodes":
(Math.random() > 0.5) && text(" and followed by another component"), That will put |
@SanderElias this API is really just a placeholder for something more solid and refined. But a solution could be to just ignore unknown entities. I used the nested array API design because I wanted to avoid:
A lightweight and declarative-ish API design is preferable, because we don't need ability to modify or rearrange nodes, we just need an api to declare a node hierarchy and nothing else. We could experiment with "pipe-able" design like RxJS 6+, but that might be an overkill as well. All of this is still TBD. |
Very interesting proposal! Would be greate to have an example of using pipes in bindings and conditions as it's also a very important part of building templates. |
@ha100790tag good point. I'll add a todo. |
Some more points:
Not so long ago angular got template type check update that helps avoiding a lot of errors. In case we will skip type check here the idea might be miss understood as just a way to pass by type checking by beginners. *this messages was written from phone late at night but I hope it's clear) |
A great feature for dynamic This could be a visual tool or part of the cli Could the angular compiler do that @IgorMinar? |
We had a great discussion about this RFC on Angular Nation yesterday, thanks to @IgorMinar, @alxhub @SanderElias, @chaosmonster, @kroeder, @bampakoa and everyone else who joined us! Here's the link for anyone who would like to watch: https://www.youtube.com/watch?v=FbJLC7GxAAs |
I like the proposal, though it seems the usage won't be easy - then again such code should be quite localized to special dynamic components, not spread around the whole application, so it should be manageable (and I would echo the call for helper (offline) compiler that can convert string template into sample code: in a vaguely analogous situation, writing C# expression trees, being able to inspect regular compiler output saved me many hours) You are asking about the need to support dynamic TemplateRef generation - it's probably not absolutely essential, but I would really like it. In most use cases I have in mind we would want to drive our UI with data - table definitions, charts, tooltips etc. Being able to generate a template and integrate it with the existing code would be excellent. |
@SanderElias yeah. I'm sure that there is a better API to use than an array of arrays. I just used that as a place holder because it's easy to understand, but obviously it's not the most type-safe design. All of this is TBD once we actually dedicate time to design this stuff. For now I'm just trying to see if this feature is interesting to the community at all. |
@profanis @chaosmonster I think the approach in the comment above is roughly right, except that we'd most likely have helpers for directly creating elements that supports adding event bindings (e.g. One thing we do need to avoid is a pattern where a string translates to code or function name, like for example |
Interesting! Thanks for sharing @xorets. This is an interesting design direction to explore further once we are actually designing the API. |
Is it possible to create this API in a way that allows tree-shaking away the entire API or its individual parts? |
@LayZeeDK the composition API should be designed as tree-shakable API. The API sketch in the description has that property. If however you want to build an API on top of this API that creates views from either JSON or strings then you only have three options:
|
Those are interesting perspectives to keep in mind, thanks @IgorMinar! |
It might be interesting to have a playground that generates the imperative instructions for a provided template. One would put this template in the playground: <div>some static html, that will be automatically serialized by the `createTemplate` call</div>
<my-yellow>
html string followed by a binding {{ userName }} and followed by another component
<my-red [routeLink]="myRoutePath"></my-red>
<p *ngIf="showDetails">Some details</p>
</my-yellow> and get the imperative code above as a result (except the Random part obviously). |
I believe the array structure its it a bit hard to use for really dynamically assembled layers, which you for example receive from some REST-End point. I agree with Ellias, it needs to be really an API, that contains also some objects to carry on the tree. const button = api.createComponent(MyButton, context, bindingsMap)
const label = api.createComponent(MyLabel, context, bindingsMap)
button.applyDirective(MyStateColorDirective);
button.addContent(label) I think it needs to be composable by Object (Tree) instead of array, otherwise it will be hard to work with this in dynamic manner. Just like DOM API:
To make it composable by array, I am not really use about this idea and working with strings. |
We can also look at how we can use dynamic imports to load some components from the 3thparty, cuz not all the components are known and you just know that you want to use |
Proposal looks great! I can see this being very useful and open up lots of new possibilities! I realise the API above is not representative of the what it would be like if implemented, but one thing that we take advantage of is component attribute selectors and multiple selectors. I'm curious to how this would be handled with the composition api, for example: @Component({
selector: '[uxa-button]'
})
export class UxaButtonComponent {} With the following composition api: component(UxaButtonComponent, {
projection: [
text("projected content"),
]
}); Ideally we would want this to be applied to a component(UxaButtonComponent, {
element: 'button',
projection: [
text("projected content"),
]
}); And if not provided in such cases would it default to a The other related scenario is when there are multiple selectors: @Component({
selector: '[uxa-button], [uxa-icon-button]'
})
export class UxaButtonComponent {} We would potentially need to have a way to determine which selector should be used, for example I believe the material button checks which attributes are present on the host element (https://github.com/angular/components/blob/master/src/material/button/button.ts#L99), so behavior or appearance may differ based on which selector is actually used. Obviously there is no API actually designed for this yet, so this is mainly just intended to outline some additional use cases to be considered 😊. Lots of great proposals recently, the future of Angular looks very exciting, thanks for all the hard work! |
Great proposal! I hope it enables us to reuse projections. For example if I have multiple components all of them have inside them array of loaded via HtppClient. I would like to extract logic for getting list of options into separate function or service. But I have two questions:
Will it work this way?
|
Am I right that current template syntax is not going to be removed in any case in favor of any other template API? If yes, then having a good intention to enhance existing templates you just add one more API to solve the same task. Besides, in my opinion, current templates are more data driven, which means it's easier to store them, e.g. in database, while composition API is more code driven, which raises questions about how substitutable those two API can be. |
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:
{
"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...
<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>
|
Hello. Reading thought this as well as reading through all the related topics is a bit concerning to us. We are in the process of evaluating how feasible it is to finally switch from AngularJS to Angular (13 as of this writing). And we use $compile in the old solution to enable the use of AngularJS Directives/Components in content provided by the users. And so obviously JIT sounds like the thing we are looking for in Angular to replace this, and then finding this is ofc raises quite a big concern for us. (Security is of less concern to us in regards to this as it's a closed of system, but is something in the back of our minds, and I understand the argument, yet it could then be an option that is opt in). In our case, content is provided by the users via TinyMCE. So the content is provided as HTML as that is what we get out of TinyMCE. The place where AngularJS directives/components come into the picture is with a custom set of plugins to TinyMCE that allows the user to add certain blocks to the source HTML (what he/she is editing) - Once the user submits the content and it's rendered in the client, these custom blocks are actually AngularJS components/directives which are then activated. Because of this I am having a very hard time wrapping my head around doing the same thing in Angular with the constraints set forth here without a insurmountable amount of trouble. (Essentially having to build our own sort of compiler that translate into the above instructions? that seems like redoing something that is already in Angular after all) A sandboxed compiler experience would be welcome where it's possible to limit what components, directives, pipes (essentially modules?) that are recognized. But having to use the above "syntax" seems to be a very tough pill for us to swallow with what we know at this point. |
I am interested on this behaviour. It's extremely useful to have the opportunity to draw JSON data in the UI using this feature. When changing from JIT to AOT this feature will disappear? There will be an alternative used by a project for using data inside JSONs and give to the user a friendly interface? |
The use case is for MailMerge. The frontend app provides some JSON data object, and merge with a HTML template drafted by a computer literate among end users. The merged HTML result will be used as report or Email message. People with skills in MS Word MailMerge should be able to pick up MailMerge based on Angular HTML template. |
Unfortunately I didn't see the RFC in time (we just started with Angular), but for us the option to compile angular HTML string templates at runtime is very critical to our current application. We have a plugin-like system where we essentially do the following at runtime:
For us this works great and allows building really flexible dynamic plugin UI's. We haven't had any issues with the performance yet, the bundle size is obviously large due to including both the TypeScript and Angular JIT compilers, but the application runs on a local network, so that is not an issue. |
We have an application that must display SVGs (html) that we must interact with click and ngIFs and binging, the SVGs come from the db as a string, their numbers are variable, and since all these SVGs contain the same function (
As mentioned above we don't know how many svgs we have but since they all look alike we chose to create a single component which contains the 'CloseOpen()' function and the 'condition: boolean = true;' variables. and 'stats:string=open;' and to associate the svg that will be associated with this component dynamically as we click on link eye or door , or window… the proper SVG will be associated with this component, so we have a component with differentiate html (Template) associated with it dynamically, with angularjs we do $compile which works well, but since we want to switch to angular the most recent possible (currently 13 or 14) we came across the AOT constraint of ivy the SVG is displayed but there is no compilation or interaction with the dynamically generated SVG, and the API you are citing is not understandable if possible examples on its usage would be a blessing. |
As others, I unfortunatelly have missed the RFC. The concept is important for us because we have templates stored on db, where templates can be configured on gui level and then renedered on demand. This however is probably only 20% of our application, whereas the rest are static components. We are reluctant give up the benefit of having 80% of our app compiled Ahead Of Time so we are considering staying on old version until this proposed API (or an alternative) is implemented. However I would like to ask what is the state of this proposal? There has not been any update about this for quite some time. |
We have an ERP software built in Java and AngularJS, which serves as our "BASE," so to speak. It supports dynamic UIs, but through an API, and currently retrieves and compiles HTML/JS. So, generic business rules belong to the standard project, however, many resources are customized and stored within the client's DB, being loaded into AngularJS (currently) and rendered (HTML, CSS, Javascript, etc). These resources are quite complex objects, sometimes encompassing various components and screens. The base project was started in 2012/2013, using AngularJS, and we are researching to update the entire frontend structure. While researching how to achieve the same functionality in Angular, I came across this RFC, which, like many, we "missed" because we didn't follow the project so closely. Anyway, we have something very similar to what mrjfalk has developed. For us, what JIT allows would be ideal, and since these features will be discontinued in Angular, we are considering 2 hypotheses:
We understand the security issue involved with the use of JIT, but for our reality it permit the flexibility we need. |
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:
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.
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...
Where the
templateRef
is generated dynamically as follows: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:
ViewContainerRef
APIs accept only TemplateRefs or Components and not views directly. Is this a problem?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:
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).
The text was updated successfully, but these errors were encountered: