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

Dynamically load template for a component #15275

Open
KarlXOL opened this Issue Mar 18, 2017 · 117 comments

Comments

Projects
None yet
@KarlXOL

KarlXOL commented Mar 18, 2017

I'm submitting a
[X] feature request

I have a component which provides some generic business logic. Let's say:

  • CRUD functionality
  • Productmanagement (product catalogue)
  • Cross department business management (separate layout for each department)
  • ...

Depending on a condition I want to show a layout (template) which is specific for a type of product, related to the user department, etc.
Right now I can't load a template on the fly for doing this.
I have read through all the issues here and on stackoverflow for possible ways on doing this.

The most practicabel way is the ngTemplateLayout. But in real life if you have dozens of different layout types, it blows up your template file and make unmaintenable.

If you use the dynamiccomponentloader which is the most recommend way by the angular team, it adds an huge overhead of code for generating the component dynamically, even if the functionality for component and module creation is encapsulated. In addition it doesn't allow a real generic solution, because in the ngModule which has also be created dynamically you have to provide all imports, exports, providers, ... to make the component work. So if you don't want to have a huge overhead in your generic component builder, you have to implement a generic componentbuilder for each "type" of your form types. Which is not practicable and increases the danger of recoding all after the next angular release

Expected behavior
Provide a way of loading a template dynamically for a component. As it is available in Angular 1
Please don't be dogmatic on this issue and consider supporting real world requirements in Angular 2
Thanks

@Toxicable

This comment has been minimized.

Contributor

Toxicable commented Mar 18, 2017

Why not just have two components then load on whatever one corresponds to the situation that you need. For example: AdminDisplayComponent and NotAdminDisplayComponent?.
It's not possible to make templates dynamic, a template must be tied to a component

@KarlXOL

This comment has been minimized.

KarlXOL commented Mar 18, 2017

I know the template can't be dynamic right now and that's a pain as I outlined. I don't want to implement two, more or hundreds of components with the same logic.
That's why I request this feature!

@Toxicable

This comment has been minimized.

Contributor

Toxicable commented Mar 18, 2017

@KarlXOL What I mean by it not being possible is that it would be impossible to make it dynamic without breaking AOT.
As for a solution to your problem, if you want to share common code then why not use a service? or if you don't think a service will do make a component then inherit from it

@KarlXOL

This comment has been minimized.

KarlXOL commented Mar 18, 2017

@DzmitryShylovich: I know you don't want to provide this feature. But as I tried to explain all other ways for doing this, are crutches and not practicable.
If I do this by using angular features, I have also to use the compiler which also breaks AOT-capability. Right? So what.
Please, don't be dogmatic and rethink your approach. I/we are providing solutions and need a platform which is supporting us in doing this.

@KarlXOL

This comment has been minimized.

KarlXOL commented Mar 18, 2017

@Toxicable : If you have an example on how to encapsulate e.g. CRUD functionality in a service, I'd like to see. Even if this possible there is the need for implementing for each e.g. product a component.

@mlc-mlapis

This comment has been minimized.

mlc-mlapis commented Mar 18, 2017

@KarlXOL I do not understand why CRUD functionality could not be implemented as a service. Probably it is the best way how to do it and a common pattern.

If I understand correctly that you do not care about AOT at all and you want to do all in JIT mode with Compiler module loaded in runtime. Then you can do it right now. Look at for eaxmple at https://www.ag-grid.com/ag-grid-angular-aot-dynamic-components, https://eyalvardi.wordpress.com/2016/09/04/injecting-components-in-runtime or even http://blog.assaf.co/angular-2-harmony-aot-compilation-with-lazy-jit-2.

@KarlXOL

This comment has been minimized.

KarlXOL commented Mar 18, 2017

@mlc-mlapis : Of course the actual data operations (new, update, etc.) are in a service. But they get triggered by user interactions (click, ...) which are part of a component.

The link you are referring to, just shows how deal with multiple components. And that's the point, I don't want to have multiple components for red, green, blue. I just need one component and change the template/layout. In my feature request I've explained why

@mlc-mlapis

This comment has been minimized.

mlc-mlapis commented Mar 18, 2017

@KarlXOL But this example is exactly what you want, yes or no (to understand your point of view)? https://eyalvardi.wordpress.com/2016/09/04/injecting-components-in-runtime

Right, the clicks, ... are there but do not understand why you mention them because your components inject the service through DI (it should be a singleton) and will provide a necessary interface.

Of course the actual data operations (new, update, etc.) are in a service. But they get triggered by user interactions (click, ...) which are part of a component.

@KarlXOL

This comment has been minimized.

KarlXOL commented Mar 18, 2017

@mlc-mlapis This is one of the possible solutions I refer to in my request ( "dynamiccomponentloader"). The downside of this approach is

  1. it requires to much additional lines of code for each component which has dynamic content
  2. the dynamic module creation needs to adapted (imports) to make it work (directives)
    As a consequence you have a dynamic module with a huge overhead or you implement several dynamic module builders

In summary this solution is to heavy-weight, very low-level, risky that id needs to adapted as angular evolves,... and it is not competitive to the lean, quick approach Angular 1 provides for this requirement.
Just imagine you want to present a different "title" for users belonging to different departments to give just a imple example.

That's why I request the feature to change/load the template on the fly. The framework should deal with this low level stuff, not the business application.

@mlc-mlapis

This comment has been minimized.

mlc-mlapis commented Mar 18, 2017

It is clear now what you mean. I can imagine that type of simplification and having so high level of functionality. It is also necessary say that this would be strictly limited only for JIT mode.

Actually there is not any attribute in Angular which would differenciate its functionalities between JIT and AOT mode. There are stable, experimental, depreciated.

Another question is the priority of such functionality in timeline and the standpoint of Angular core team.

@KarlXOL

This comment has been minimized.

KarlXOL commented Mar 18, 2017

@mlc-mlapis Thanks for your feedback. I'm happy I was able to bring the message across :-)

I know from many other issues here, stackoverflow, there is a big need for this feature as it is a very common requirement in real business applications. In the past and even more in the future.

@KarlXOL

This comment has been minimized.

KarlXOL commented Mar 18, 2017

@DzmitryShylovich I can't see from the ng-descendant proposal it will be a solution for my request.

I'd recommend you rethink the angular architectural approach to make it more flexible and usable for big enterprise application scenarios. It can't be an impossible mission for the angular framework and team to load templates dynamically for a component.

@mlc-mlapis

This comment has been minimized.

mlc-mlapis commented Mar 18, 2017

@KarlXOL Enterprise application scenarios are the reason why AOT exists. 😄 The dynamic templates are just shortcuts and a cheaper solution. So probably the recommendation about rethinking that is unrealistic.

@KarlXOL

This comment has been minimized.

KarlXOL commented Mar 18, 2017

@mlc-mlapis I expected an answer like this. The more I'm wondering why the angular team isn't providing what is needed by enterprises. And making applications faster and cheaper is key in enterprise environment. But you confirmed this is not what you are supporting with angular 2.

@mlc-mlapis

This comment has been minimized.

mlc-mlapis commented Mar 18, 2017

@KarlXOL Just out of curiosity, do you have any info about and how Google develops its global applications?

@KarlXOL

This comment has been minimized.

KarlXOL commented Mar 18, 2017

@mlc-mlapis Not sure about. Is it Angular 1 or Angular 2??

@Toxicable

This comment has been minimized.

Contributor

Toxicable commented Mar 18, 2017

@KarlXOL Because this isn't the only thing needed by enterprises, just because it's something you want to do doesn't mean it's something that everyone needs. Everything they have done lately is to make applications faster and easier to make applications, just take a look at the bundle reductions from v2 -> v4.
I would suggest rethinking how you're trying to accomplish your goal, methods that worked in AngularJS aren't always the best approach when working in Angular.

cc @robwormald

@mlc-mlapis

This comment has been minimized.

mlc-mlapis commented Mar 18, 2017

@Toxicable

This comment has been minimized.

Contributor

Toxicable commented Mar 18, 2017

@KarlXOL here's is a small example I made https://plnkr.co/edit/kz2XKSKWSWZhPoncDQhG?p=preview
All you'd do with this is provide the UI/config then hook into the methods to hit the backend

@KarlXOL

This comment has been minimized.

KarlXOL commented Mar 19, 2017

@Toxicable I appreciate your plunker. My implementation looks like this. This part of the application is not related to the actual problem as I explained in my initial post.

@Toxicable You are right. My post was inaccurate regarding the service.

@Toxicable

This comment has been minimized.

Contributor

Toxicable commented Mar 19, 2017

@KarlXOL I don't get what you want then. I don't see how being able to swap out templates on the fly can be any better than using a generic setup like this. Since with this you only have to defined a component which is only 20 LOC

@KarlXOL

This comment has been minimized.

KarlXOL commented Mar 19, 2017

@mlc-mlapis Thanks for your lessin in web development. As you mentioned it is about how google develop web frameworks. I have to admit that my request is from a perspective on how to provide business solutions for enterprises and how to utilize tools and frameworks for doing this. In addition to developing frameworks as you mentioned there arise some other requirements and needs. I'd appreciate if you would also give these kind of requirements some valuation. At the end, we all would benefit more from each other and angular 2 could be a more powerful framework.

@KarlXOL

This comment has been minimized.

KarlXOL commented Mar 19, 2017

@Toxicable Your sample is not about swapping out templates on the fly but for providing a data access service All other implementations for swapping templates (which is not supported actually) is done by dynamic module/component creation, which is much more complicated and low-level angular implementation.

@Toxicable

This comment has been minimized.

Contributor

Toxicable commented Mar 19, 2017

@KarlXOL Yes I know, my sample wasn't about swapping out templates at all. You said:

If you have an example on how to encapsulate e.g. CRUD functionality in a service, I'd like to see. Even if this possible there is the need for implementing for each e.g. product a component.

This is what I did. I provided a solution to your problem, not in the way you wanted to solve it, but it's a solution none the less. However, my point remains; I don't see what you're gaining by having templates swapped in and out if it were somehow possible, in both cases you'd still have to write the template code, so what exactly do you gain for the largely extensive amount of work it would take to get even close to implementing something like this in the framework?

@KarlXOL

This comment has been minimized.

KarlXOL commented Mar 19, 2017

@Toxicable Exact. You got the point. I only have to write the template code. All the other code remains unchanged!! Imagine the general implementation of a master data management, including your service from above. Just change the template and another table (business object) is implemented. At least this is possible in Angular 1.

@mlc-mlapis

This comment has been minimized.

mlc-mlapis commented Mar 19, 2017

@KarlXOL And this is a problem in reality if you apply that principe in a large scope. Because you can not make proper tests as a lot of code is compiled at runtime, so you can find a lot of hidden errors only at runtime. And this is unacceptable. You can play with that concept only in a small scale and still you will risc a lot of unpredictability.

Just change the template and another table (business object) is implemented.

@KarlXOL

This comment has been minimized.

KarlXOL commented Mar 19, 2017

@mlc-mlapis Sure, you are a right. But consequently you have to remove the compiler from angular 2 then, because in principle I can already achieve what I want to do. It's just way too complicated.

I overstress your argument, but it includes also all kind of data (database, etc.) you are processing with an application. You can't test everything and there will always be something left to the responsibility of an application manager.
Back to our discussion, what is needed - in my opinion - is good mix of pragmatism and architectural vision. The vision is given, that's for sure. And some pragmatism could also be a win for angular 2. My requirement is not exotic and needed by many people out there.

@mlc-mlapis

This comment has been minimized.

mlc-mlapis commented Mar 19, 2017

@KarlXOL I can agree with some of your arguments. But on the other side I see also places on your side where you should re-think your application concepts simply because you will get better apps.

We are probably on the end of our discussion because the standpoint of Angular core team is important from this point of view. I just wanted to express some background arguments why AOT is a prefered way now.

@ngbot ngbot bot added this to the Backlog milestone Jan 23, 2018

@nsksaisaravana

This comment has been minimized.

nsksaisaravana commented Feb 8, 2018

Preview of Angular Ivy renderer- Angular's new view engine !!!

https://github.com/robwormald/ivy-code-size

@nsksaisaravana

This comment has been minimized.

nsksaisaravana commented Apr 24, 2018

image

@scottseeker

This comment has been minimized.

scottseeker commented Jul 6, 2018

Any updates on when this feature will be available?

@mlc-mlapis

This comment has been minimized.

mlc-mlapis commented Jul 6, 2018

@scottseeker ... you have to wait still for Ivy if current possibilities are not enough.

@sunilpes

This comment has been minimized.

sunilpes commented Jul 6, 2018

@mlc-mlapis we are awaiting for this feature. This feature has great potential to develop runtime pluggable modules. lets see how it goes ...

@ElzioJunior

This comment has been minimized.

ElzioJunior commented Aug 17, 2018

@KarlXOL If this still makes sense for you: create a v2Component with templateUrl = yourNewHTML... and extends your v1Component

@artaommahe

This comment has been minimized.

artaommahe commented Aug 17, 2018

@KarlXOL If this still makes sense for you: create a v2Component with templateUrl = yourNewHTML... and extends your v1Component

there are a lot of cases when markup comes from back-end (e.x. lesson content with many different exercises) and changes often via separate admin dashboard

@mastronardif

This comment has been minimized.

mastronardif commented Sep 16, 2018

I too would love this feature.
To be able to change the component "angular-html" at run time.

@softwareCobbler

This comment has been minimized.

softwareCobbler commented Oct 7, 2018

@mastronardif +1

@alarm9k

This comment has been minimized.

alarm9k commented Oct 31, 2018

I had a task where I needed to render arbitraty templates supplied from the server to display a custom form. Component had to be created and compiled at runtime to enable the bindings in the templates.

The steps to achieve that:

  • Create parent component that would create and render the dynamically compiled child component inside its ViewContainerRef.
  • Inject a compiler into it using Compiler token.
  • Use the Component and NgModule decorators (which are just functions under the hood) to define the component and the module that will declare it.
  • Compile both. This will give you the component factory that can be used to create the component instance.
  • Use the component factory to create the component inside the view container.
  • OPTIONAL: assign values to component inputs if it has any.
import {AfterViewInit, Compiler, CompilerFactory, Component, NgModule, ViewChild, ViewContainerRef} from '@angular/core';
import {CommonModule} from '@angular/common';
import {JitCompilerFactory} from '@angular/platform-browser-dynamic';

@Component({
    selector: 'app-parent',
    template: '<div #container></div>'
})
class ParentComponent implements AfterViewInit {
    @ViewChild('container', {read: ViewContainerRef}) container: ViewContainerRef;
    constructor(private compiler: Compiler) {}

    ngAfterViewInit() {
        // Must clear cache.
        this.compiler.clearCache();

        // Define the component using Component decorator.
        const component = Component({
            template: '<div>This is the dynamic template</div>',
            styles: [':host {color: red}']
        })(class {});

        // Define the module using NgModule decorator.
        const module = NgModule({
            declarations: [component]
        })(class {});

        // Asynchronously (recommended) compile the module and the component.
        this.compiler.compileModuleAndAllComponentsAsync(module)
            .then(factories => {
                // Get the component factory.
                const componentFactory = factories.componentFactories[0];
                // Create the component and add to the view.
                const componentRef = this.container.createComponent(componentFactory);
            });
    }
}

Some things to keep in mind:

  • Remember to call clearCache on the compiler. If you don't, you will only be able to render your component once. When you try to compile it the second time Angular will complain about the same component existing in two different modules.
  • You don't have to use anonymous classes in component and module declarations. I actually used the class I already had in my application to declare the component.
  • The compiler is normally not available if the app was compiled with AOT compiler. To solve this we have to provide the compiler explicitly:
export function createCompiler(compilerFactory: CompilerFactory) {
    return compilerFactory.createCompiler();
}

@NgModule({
    imports: [
        CommonModule
    ],
    exports: [],
    providers: [
        // Compiler is not included in AOT-compiled bundle.
        // Must explicitly provide compiler to be able to compile templates at runtime.
        {provide: COMPILER_OPTIONS, useValue: {}, multi: true},
        {provide: CompilerFactory, useClass: JitCompilerFactory, deps: [COMPILER_OPTIONS]},
        {provide: Compiler, useFactory: createCompiler, deps: [CompilerFactory]}    ],
    declarations: []
})
export class CoreModule {}

@mlc-mlapis

This comment has been minimized.

mlc-mlapis commented Oct 31, 2018

@alarm9k ... yep, this way to use JIT compiler and combine it with AOT core app is open and works ... we will see what dynamic things will be opened with Ivy ... even with superior performance.

@BKHines

This comment has been minimized.

BKHines commented Oct 31, 2018

@alarm9k I have very similar requirements and tried this same approach last year but could never get it to work. I think the thing I was missing was the changes to the module. So I'm trying it out real quick and the only thing I can't figure out from your code snippet is where this "createQuestionFunction" function comes from:

const componentFactory = this.createQuestionContent(factories.componentFactories[0]);

@alarm9k

This comment has been minimized.

alarm9k commented Oct 31, 2018

@BKHines Thanks for noticing that. I recklessly copy/pasted that from my app. Fixed.

@BKHines

This comment has been minimized.

BKHines commented Oct 31, 2018

@alarm9k haha, that's what I set it up as temporarily; I just didn't run it. I would have seen it work if I had just run it! Awesome stuff man! Thanks!

@emilio-martinez

This comment has been minimized.

Contributor

emilio-martinez commented Oct 31, 2018

@alarm9k that's really interesting. Thank you! Do you have any stats for the cost of not having the compiler tree-shaken away from your bundle?

@alarm9k

This comment has been minimized.

alarm9k commented Nov 3, 2018

screenshot_20181103_224754

The difference is 329 kB.

@artaommahe

This comment has been minimized.

artaommahe commented Nov 3, 2018

The difference is 329 kB.

u missed gziping

@merlinchen

This comment has been minimized.

merlinchen commented Nov 14, 2018

@alarm9k Is it possible to inject services to component created like this? When i try to inject ChangeDetectorRef and ElementRef in component constructor, i keep getting Can't resolve all parameters for ComponentClass: (?, ?). thanks

@kotproger

This comment has been minimized.

kotproger commented Nov 23, 2018

Good day! Also faced with the need to replace the template in the current components from external data sources. Now, of course, I am trying to solve this problem through the dynamic creation of a component and an attempt to transfer the current logic to them. But it would be great to be able to change exactly the pattern. I'm really looking forward to this functionality. I would like an approximate assessment of the timing of the emergence of this functional. Thank.

@SamuelMarks

This comment has been minimized.

SamuelMarks commented Dec 3, 2018

@alarm9k That works really well, except on ng build --prod. No errors, but when I load the website, the console shows:

ERROR Error: "Runtime compiler is not loaded"
Ni /main.f449713c3fb3867ad6bf.js:1:68849
compileModuleAndAllComponentsAsync /main.f449713c3fb3867ad6bf.js:1:69182
ngAfterViewInit /6.c40d8b2dc5100b1af302.js:1:560
Xs /main.f449713c3fb3867ad6bf.js:1:126416
Qs /main.f449713c3fb3867ad6bf.js:1:126176
Zs

@alarm9k

This comment has been minimized.

alarm9k commented Dec 3, 2018

@SamuelMarks This is most likely because you haven't provided the compiler in your module.

@SamuelMarks

This comment has been minimized.

SamuelMarks commented Dec 3, 2018

Where does it need to be included?

You mean in the imports: [] of the parent @NgModule? - Which module?

@BKHines

This comment has been minimized.

BKHines commented Dec 3, 2018

@SamuelMarks Have you included this code in your module? I had a similar error months ago when I couldn't get this to work and it wasn't until @alarm9k 's example that I figured out the compiler was not in runtime (despite the now obvious message in the error! lol )

export function createCompiler(compilerFactory: CompilerFactory) { return compilerFactory.createCompiler(); }

@alarm9k

This comment has been minimized.

alarm9k commented Dec 3, 2018

@SamuelMarks You have to include compiler in the providers array of your module. Please see my code example above.

@alarm9k

This comment has been minimized.

alarm9k commented Dec 3, 2018

In Angular world "providing" means adding something to module's providers.

@BKHines

This comment has been minimized.

BKHines commented Dec 3, 2018

Any tips for capturing changes in the dynamic component from the parent component? I've got component A which has Property A on it. Component A creates the dynamic component, Component B from a string of HTML returned from an HTTP call. Inside of Component B is a bunch of Component Cs which are established components inside the project. One of the input properties on Component C is Property A. I can pass the value of PropertyA to Component C by putting it on the instance of ComponentB:

componentB.instance.propertya = this.propertya;

Then Component C shows the correct value when initialized. However, when I change Property A on Component A, it doesn't reflect on Component C. I moved Property A to a common service and that works fine (so I could stick with that), but I'm curious as to how to you can make this property change in the dynamic component and in the components in the dynamic component? I tried creating an input/output variable and subscribing to the output event, but no dice there either. Also tried below when a button is clicked to change Property A. Same results.

componentB.changeDetectionRef.detectChanges();

Here's a simple GitHub repo in case that didn't make any sense: https://github.com/BKHines/dynamiccomponentexample

@SamuelMarks

This comment has been minimized.

SamuelMarks commented Dec 3, 2018

@alarm9k @BKHines thanks, almost got it working:

Uncaught (in promise): Error: Loading chunk root-root-module failed.

import { Compiler, CompilerFactory, NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router';

import { ThisComponent } from './this.component';
import { theseRoutes } from './these.routes';

export function createCompiler(compilerFactory: CompilerFactory): Compiler {
  return compilerFactory.createCompiler();
}

@NgModule({
  declarations: [ThisComponent],
  imports: [
    CommonModule, RouterModule, RouterModule.forChild(theseRoutes)
  ],
  providers: [{ provide: Compiler, useFactory: createCompiler, deps: [CompilerFactory] }]
})
export class ThisModule {}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment