Skip to content
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

Final statement about ngInclude alternative #7596

Closed
PavelPZ opened this issue Mar 14, 2016 · 87 comments
Closed

Final statement about ngInclude alternative #7596

PavelPZ opened this issue Mar 14, 2016 · 87 comments
Labels
area: core Issues related to the framework runtime

Comments

@PavelPZ
Copy link

PavelPZ commented Mar 14, 2016

I am very sorry for making dupl of #2753. But after closing this issue on 2 Jul 2015, there are a lot of compelling arguments from community for allowing this feature.

I read this article with statement: It is no longer possible to implement ng-include....
I only want to make sure that:

  • angular team is really not interested in supporting any "html template as data or as parameter" scenario (for incoming angular2 release candidate)
  • there cannot be provided any official workaround, avoiding to have single angular2 component for every product in catalogue, for every web page, for every eLearning exercise etc.?
@brian428
Copy link

Also, if this just absolutely won't be supported, maybe an explanation of why would help. I've seen references to "security concerns", but what those concerns are is not clear. I don't really understand how this is different than using a templateUrl in a component.

Dealing this this limitation has been a real struggle. Consider something as basic as a menu that creates tabs. There are two layers to the problem: 1) Getting a handle on the component you need to add, and 2) Actually adding the component once you have a handle on it.

As far as I can tell, the only options for "Step 1" (obtaining the Component Type) to add are:

  • Switch/Case statements to manually get a reference to the proper component Type.
  • Configuring an Injector (via Providers, string identifiers, and useValue:) to map the Types, then using injector.get() to locate them.
  • Non-trivial SystemJS calls to normalize module paths, load a module, and extract the Component Type reference from the module.

For "Step 2" (adding the component), we need to use loadIntoLocation(). However, this requires a local template variable anchor name. And local template variable names can't be dynamic. Which means we must resort to bizarre workarounds like this to create a fake wrapper component with a known (and isolated) anchor to target.

Basically, this whole situation is way too complicated for something as basic as adding a dynamic tab. And obviously, the problem goes way beyond tabs. Tabs are just an obvious way to demonstrate the problem.

I think this requires some thought from the team, because this simply should not be as difficult as it currently seems to be. That said, if I'm missing some key motivation for why this complexity is deliberate, I'm open to hear more details on why.

@jpleclerc
Copy link

IMHO ngInclude is only a nice to have since we can already generate components dynamically and load them with the DynamicComponentLoader. That said, it would be a good idea to have a (third party?) service to facilitate this case or at least have examples in the doc for those who come from ng1.

@brian428
Copy link

That's the thing. Yes, it's possible to create components dynamically. The problem is that it's currently far more complicated than it should be for something this fundamental and necessary. (See my earlier comment.)

@jpleclerc
Copy link

@PavelPZ Maybe I am missing something but it seems pretty straightforward. Why not something like that:

@RouteConfig([
  new AsyncRoute({
    path: '/article/:id',
    component: ArticleComponent,
    name: 'article'
  })
])
...

@Component({ selector: 'base-article', template: '<div id="here"></div>', ... })
class ArticleComponent {
    public constructor(private params: RouteParams, private loader: DynamicComponentLoader, private injector: Injector){

    }

    ngOnInit() {
      var id = this.params.get('id');
      @Component({ selector: 'article-' + id, templateUrl: 'article-' + id + '.html' })
      class ArticleFakeComponent{}

      this.loader.loadAsRoot(
          ArticleFakeComponent, 
          '#here'
          injector
      );
    }
}

It might not work out of the box but you get the idea. You should also cache your components in a service instead of creating a new one on every init.

EDIT: @PavelPZ Deleted his comment but this is a reply to that: http://stackoverflow.com/questions/36008476/how-to-realize-website-with-hundreds-of-pages-in-angular2

@brian428
Copy link

Again, this whole "manually-create-a-fake-wrapper-component-on-the-fly" approach is decidedly un-straightforward. If you already know about this trick you can obviously make it work, but very few people would ever realize this on their own.

If this is really the way the Angular team think this should work, then I'd say the DynamicComponentLoader (or something else in the library) should expose a method to wrap up this logic so that people aren't left pulling their hair out trying to figure this out. Maybe a loadTemplateUrl() method that takes a template path instead of a Type reference?

@PavelPZ
Copy link
Author

PavelPZ commented Mar 15, 2016

@jpleclerc thanks a lot. I will try your tricky solution.

The most interesting aspect of that solution is that during multiple calls of ngOnInit there exist multiple declarations of ArticleFakeComponent ES6 class. What is the JavaScript strategy when instantiating this class? Maybe "last declaration wins" and previous declarations are released?

When a user loads 100 web articles by this hack, what happens with the previous ArticleFakeComponent declarations (containing decorator with template)? Are all resources released correctly or do they remain in the memory?

As @brian428 mentioned, some official support or confirmation form Angular team will help a lot.

@jpleclerc
Copy link

@brian428 I agree with you. But I feel that the real problem is the lack of documentation in this regard and not the lack of feature. Implementing a generic service to manage that use case would take at most 4 hours of work.

@brian428
Copy link

@jpleclerc that may well be true. But it still seems silly to have this work manually re-done over and over, when it could (and should, IMO) just be built into the framework.

If this was some bizarre edge case, having people handle this themselves might make sense. But for something as widely needed as this is, it really seems like it should be part of the framework.

@robwormald
Copy link
Contributor

@mhevery mentioned this in the other issue, but i'll repeat it here for clarity - a "template" is no longer a standalone thing in Angular2 - the base unit is component

The major difference to understand is that in real-world angular2 application, template compilation will happen offline, that is, as a build-step - where your template string will be transformed into executable javascript. This provides a major boost in performance as well as meaning we don't have to ship the compiler code to a real application, saving bytes.

@jpleclerc's example works, but again, would require the template compiler to be shipped to the browser at runtime.

A component is just a javascript class. Yes, you could use the System loader to load them dynamically (again - you'd be loading a pre-compiled component, ideally...), but equally you could import a module that exported any number of Components and simply reference them by name - see an example here: https://plnkr.co/edit/C0U5IIflrMnqxuXVbdw8?p=preview

As far "should be built into the framework" - DynamicComponentLoader is a low-level service, and it exists so that higher level services (like our own Router) can leverage it. It's impossible for us to cover all use cases, and so we expose the same primitive the framework uses for your own usage.

@brian428
Copy link

Thanks, Rob. This helps, as I hadn't thought of the "import *" approach, and also hadn't realized that you can specify the same local template variable name as the target location for multiple consecutive components. I had (wrongly) thought this required unique target locations.

Obviously a big chunk of the problem (for me at least) is the lack of understanding about what the various options are. So far, it's just been a lot of blind experimentation. Having these in the docs would probably help a lot. I'll see if I can spend some time over the weekend to update the docs for DynamicComponentLoader to include this example (along with one or two others, possibly).

@david-gang
Copy link

Hi,
Hi @robwormald,

Thanks for the very helpful comment #7596 (comment).

So let's recap.

Let's say that i have 2 different components with exact the same controller but different templates. In angular1 i would make the template url dynamic or use ng-include.

In angular2 what would be the best way to reuse the same class?

Is there a better way than inheritance. As i understand the injection of parameters and inputs don't work with base classes and this would lead to code duplication.

So if we take the plunkr https://plnkr.co/edit/C0U5IIflrMnqxuXVbdw8?p=preview it would be nice to add that foo-component and bar-component should have the same inputs and also a logic in the controller. If yes then what would be the best way for the code sharing.

Thanks,
David

@tolemac
Copy link
Contributor

tolemac commented Mar 27, 2016

I'm currently deciding if we switch to Angular 2, or not, in a project that we are starting, and this is the main problem.

We are changing the UI layer of a web application, we have too many screens, we have too many entities and I we can't to write a component for each entity.

Currently we have about 400 entities and we have at least a CRUD screen for each entity, where you can query, edit, create and delete entities.

The project is started using angular 1, one month ago, we have "only one" component (1.5 angular.component()), called CrudComponent and have an input value: the entity name.
This way I have only a html file for each entity and looks like it:

<script type="text/ng-template" id="customerQueryTemplate">
    <input type="text" ng-model="query.name"/>
    <input type="text" ng-model="query.surname"/>
</script>

<script type="text/ng-template" id="customerRowTemplate">
    {{entity.name}} - {{entity.surname}}
</script>

<script type="text/ng-template" id="customerEditTemplate">
    <input type="text" ng-model="entity.name"/>
    <input type="text" ng-model="entity.surname"/>
</script>

<crud entity="customer"></crud>

CrudComponent search for [entityName]QueryTemplate, [entityName]RowTemplate and [entityName]EditTemplate for compose the CRUD screens for any entity.

Well, I'm trying to build this system over angular 2 and I'm having problems, of course. I could assume to have a component for each enity, although this means having 800 files instead of 400 ... but the problem is to compose the component template, using the speciallization of each entity.

When I see this thread, this issue, I'm starting to think that angular 2 team don't want to support it and I'm searching for other ways to implement it.

Perhaps we could compose the final template for component if I could use a promise for template option in component. Then I could write my components like it:

@Component({
    selector: "customer",
    template: new Promise((ok, reject) => {
        // Use a custom system to compose the final template for customer CRUD
    })
})
export class Customer extends CrudComponent {
    // ...
    // ...
}

This way I could to build the final template for my crud component without use any "tricky" solution.

What do you think about?

@damiandennis
Copy link

use multi-content projection. http://blog.thoughtram.io/angular/2015/04/09/developing-a-tabs-component-in-angular-2.html
also you can use the # on the parent to reference the component itself.

<custom-component #myRef>


@tolemac
Copy link
Contributor

tolemac commented Mar 28, 2016

@damiandennis yes, I have taken this way, but continue having problems.
See this plunkr: https://plnkr.co/edit/IOq9zn?p=preview
For example, on "template" I can't access to each entity of ngFor.

@zoechi
Copy link
Contributor

zoechi commented Mar 28, 2016

@tolemac If you have more than one <ng-content> with the same select="..." in the template, all matching elements are projected to the first <ng-content> with matching select="..." only.

@mhevery
Copy link
Contributor

mhevery commented Mar 28, 2016

Something like ng-include can not be supported for several reasons:

  • In Angular 2 directives are declared on per component. Having ng-include would mean that the same ng-include would behave differently depending where it is included. The same goes for variable declarations.
  • It is a security risk, in the sense of you may point it to a user input.
  • It prevents Angular from doing offline compilation, hence speed / size improvements.

The solution is that you need to wrap your templates into components, and then you can lazy load the components. This will work with offline compilation, does not have security concerns and still allows for offline compilation.

@wardbell Could we turn this into a cookbook? As in how do I do ng-include equivalent in Angular 2.

@zoechi
Copy link
Contributor

zoechi commented Mar 28, 2016

Will there be an equivalent Dart solution? I don't see how to translate the explained solution to Dart.

@mhevery
Copy link
Contributor

mhevery commented Mar 29, 2016

Dart ang JS are identical in the source code hence in the reasoning.

@tolemac
Copy link
Contributor

tolemac commented Mar 30, 2016

Anyway, I think this issue will be the biggest stumbling block for many people ...
I think it needs to be documented, we need documentation about how we have to approach each use of old "ng-include".

@wardbell
Copy link
Contributor

wardbell commented Apr 2, 2016

I'd like to document. But I don't think i understand the scenarios well enough.

Many ways to skin this cat. When the variations are few, IMO, its not a big deal to write a component per variation and use ngSwitch. In my A1 life I only used ngInclude for small variation sets and so I don't miss it in A2

The volume requirements from @tolemac beg for a different kind of approach such as the entity metadata driven ideas in the "Dynamic Forms Cookbook" (see the docs).

But that may be too general. I sense that @tolemac would like to write custom screens for each of the 400 entities.

That need not imply 800 files; with such small html, inline templates should be fine, at least most of the time.

I think the inheritance/mixin issues can be addressed adequately. It will take some more thought and help from this group.

I'm noodling it!

@brian428
Copy link

brian428 commented Apr 6, 2016

Now that the discussion is veering into "how-do-I-do-this" territory, I think you want to move over to the Angular mailing list. Github issues are really for discussing bugs and feature requests.

@Blasz
Copy link

Blasz commented Jun 11, 2016

I feel like dynamic components/templates are a prominent enough use case to warrant high level support instead of relying on low-level services (such as the now deprecated DynamicComponentLoader or ViewContainerRef) that don't seem to support:

@user414
Copy link

user414 commented Jun 11, 2016

I'm also thinking of this. Never having used angular1 before I don't miss ng-include :-) However, I can't put my finger on it exactly but there are times where I wish there was something like ng-include. For example, ngSwitchWhen is a good example. ngSwitchWhen doesn't support multiple case like a standard switch, some people said you can restructure your switch with an if but that doesn't seem very elegant, you just wish there was a way to reuse that html without having a component. This situation reminds me of the beginning of OO when everyone would turn everything into a class, you would get software with a billion class with just a few lines. Even in OO there are times where it doesn't make sense to have something in a class on it's own, it seems that angular2 is at that point by forcing the use of a component even if another solution might be more elegant. Again, can't put my finger on it yet, and even though it always seem possible to refactor to make the code work using the component approach, sometimes it feels like it doesn't end up with the most elegant, clear and easily maintainable design but just the most componentized solution.

@brian428
Copy link

I also think this deserves further thought by the team, if not for 1.0 then after the 1.0 release. Clearly, dynamic components are a necessity. And yes, it's possible to do this now (http://blog.lacolaco.net/post/dynamic-component-creation-in-angular-2/), but it's way too complicated and unintuitive for something this fundamental. It seems like there must be a way to streamline this.

@Toxicable
Copy link

@darkguy2008 one method of creating mostly dynamic is by using [innerHTML] with Html loaded off your backend, then replace any tags referring to Angular components with actual already compiled Angular components. An example of doing this can be found here: https://github.com/angular/angular/blob/master/aio/src/app/layout/doc-viewer/doc-viewer.component.ts#L68
While this wont allow you to create new components at runtime, it does allow you to have a suitable outlet for CMS type applications, since why would users be allowed to define new components anyway.
If you want more dynamic than that I have built a proof of concept here https://github.com/Toxicable/module-loading that allows you to build as dynamic as you want applications all with AOT, meaning that you can build you "Core" application independent of each of the modules being loaded in. However, do note I don't recommend using this since there is almost always a way to avoid it, but the point im proving here is that it's possible.

if you have any the questions or issues I havn't addressed then feel free to contact me on the Angular Gitter https://gitter.im/angular/angular with @Toxicable

@tolemac
Copy link
Contributor

tolemac commented Jul 11, 2017

So, you do have the knowledge to tell us how to solve the issue where we can't use ng-include in a more modern/performant way? The old pattern is awesome, single line, ng-include your template and off you go. What are the alternatives? The lack of documentation or simple solutions (by simple I mean something less tan 200 lines for something we could previously do in 1) is surprising.

@darkguy2008 I was very angry with this subject, but I understood the reasons for the change and I found how to implement the old ng-include in Angular 2/4.

Please, read my comments on this thread and you can see how to do.

@mlc-mlapis
Copy link
Contributor

mlc-mlapis commented Jul 11, 2017

@darkguy2008 The most based fact is that you have or don't have a compiler in a browser to be able to compile HTML code with bindings and so on. If you have to have a compiler you have to load it first and then compile HTML templates. Only then your app will run.

It is possible to create an abstracted API to have just one line to do it and combine that way even with AOT mode (instead of ^^^ 200) but you can't change the base fact related to the compiler.

Or as the second way it is possible to develop some abstracted API and background build processes to create AOT compiled modules and components on a server on-demand based on a user HTML code (taken from CMS). Then there will not be the problem with the compiler and the app is fully AOT compatible without any exceptions.

@KarlXOL
Copy link

KarlXOL commented Jul 11, 2017

@Toxicable right, rethinking implementation approach and architecture is a continuous process and requirement. The best solution I've found is to work with "component inheritance" (which had a lot of bugs) in the beginning) but is much better than other approaches found here.
Nevertheless, the need for implementing dumb components holding just fpr the layout remains. As other posters mentioned this doesn't fit for large ERP/CMS, ... applications

@zoechi If you tell us that angular isn't the right for dynamic UI you are maybe/probably right. But if you take a look out there that's the need for most of the applications I know. Instead of blocking users requests and defending your approach listening to users and thinking about solutions and how the framework could be adapted, extended to be useful for a wider audience is usually the way I know and would expect

The excellent angular team is going to have the most performant, most sophisticated framework, but is in the danger to loose their users because of not listening to them

@zoechi
Copy link
Contributor

zoechi commented Jul 11, 2017

@KarlXOL he Angular team not listening to their users is in my opinion a completely wrong assumption.
There are tons of people complaining about Angular not meeting their expectations without even trying to understand what Angular is.
There are about 2 dozen developers on the Angular team and millions of people who try Angular and quite a lot, after a brief look, complain and threaten either implement feature X or I'll use framework Y instead.
The Angular team responding to every complain just doesn't scale. Their time is better used improving the framework.
This doesn't mean they're not listening. They are involved in discussions all the time.

As I mentioned above, if a dynamic UI is your main requirement, Angular probably isn't the best choice for you.
Angular is about static compilation, which doesn't work well with dynamic UIs.
As others mentioned above, it can be done, but it's not a beaten path.
If this feature were essential for the majority of applications like you claim, then Angular wouldn't be that successful.

@Toxicable
Copy link

@zoechi think ya tagged the wrong person there :p

@zoechi
Copy link
Contributor

zoechi commented Jul 11, 2017

@Toxicable thanks, sorry, fixed

@KarlXOL
Copy link

KarlXOL commented Jul 11, 2017

@zoechi Sad, to hear from you there are "tons of people" complaining about angular. Maybe you should take this is an indicator there might be something "wrong" with your approach on managing framework development and "selling" it to the community. I'd suggest to shift your focus a bit on the user and their needs. It looks like you are obsessed by "Compiler, AOT, ...". Honestly, that's not the top priority for most of us.

@zoechi
Copy link
Contributor

zoechi commented Jul 11, 2017

@KarlXOL

quite a lot, after a brief look, complain

If people have questions, lots of people are prepared to answer them and guide them towards a solution.
There isn't much one can do about complaints though, because complaining, in contrary to asking, is more about others solving a problem while the complainer sits back and waits.
This is usually not how people developing open source software imagine their interaction with users of their framework.

Just my opinion. I'm not in any way related to Google or the Angular team.

@egucciar
Copy link

I am dynamically creating components just fine as long as we include the compiler in the runtime. I don't even mind the few extra lines of code needed to do it. It is possible.

So if a "large dynamic UI" is not possible to implement in Angular 2/4 without the compiler, then how does angular team suggest those who wish to Port large dynamic UIs to Angular 2/4? Just don't do it?

@Toxicable
Copy link

@egucciar take a read up this issue, there are many ways around this issue that preserve the performance of your application.
The issue with the Compiler is that it's big and slow, depending on how much you're compiling it can lock up your users UI even.

@egucciar
Copy link

@Toxicable thanks for responding. I read the entire thread and thought the gist was all the ways of doing this dynamically which don't involve switch or if statements which aren't really dynamic. Also there is an option to generate on the backend and lazy load. That seemed to be interesting. Was that the option you were thinking? Because the way I read there thread there's no pure front end option which doesn't involve using compiler or if/switch

@Toxicable
Copy link

@egucciar the one I recommend would be the top half of this here #7596 (comment)

@Toxicable
Copy link

@darkguy2008
Copy link

@egucciar well I'm not sure if it helps but it solved my ngInclude issue - although I had to compile a component instead, it allowed me to include dynamically-created components and insert them inside a div. So you can do an ngInclude in two steps: 1. Compile your component, 2. Set a placeholder div and paste your component there once it's compiled. I made an article about it here along with sample code: https://medium.com/@dark.guy.2008/compile-for-angular-4-ff800de4a77b

@Toxicable although I appreciate your explanation my Angular 4 knowledge isn't as good to understand either the explanation or the sample code you have linked, so I solved it that way :P

@Toxicable
Copy link

@darkguy2008 If you want help understanding it then jump on the Angular Gitter and I could give you a run down of what that code is doing https://gitter.im/angular/angular

@egucciar
Copy link

@darkguy2008

Yep, I got the dynamic component and template working with the angular two syntax thank to runtime compiler. It's awesome! But if angular team does not recommend to include this in your front end, it is sort of a dead end if I desire to follow in only recommended angular approaches.

@Toxicable I'll take a look at this asap. Thanks so much

@egucciar
Copy link

@darkguy2008 I can see you added to the DOM directly. We did it a bit differently and it's angular 2 syntax. We leveraged the "view child". I believe some code with that was also posted here. Component factory code is similar thought. This does require compiler :(

@jrmcdona
Copy link

jrmcdona commented Aug 26, 2017

I am receiving external HTML that I am modifying the hrefs to be [routerLinks] on the server side. Since SafeHTML doesn't process or bind, the links do not work.

@Toxicable did one of your solutions work for this scenario? Or do I need to include a compiler option for this in order for Angular to process my HTML that contains routerLinks's.

I plunkr'd the basic scenario which doesn't work and looking for a solution that could work.

http://plnkr.co/edit/2CC7VruA0ffYhtOe9Ndm

@ptollena - did you ever solve your external HTML? I believe you had similar issue to what I just described.

Thanks

@seanspyder
Copy link

I am not interested in Dynamically loaded HTML , I am interested in having statically loaded HTML the equivalent to a #include statement or c# razor HTML helper , such that repeatative HTML (with bindings) can be used in multiple places.
A good example would be a set of say 10 different grids that displayed different data where the last 5 columns were common between all the grids. Or where inherited components need a lot of the common html to the parent class.
an example of a c# helper for razor would be in razor.txt and the columns i want repeated in columns.txt
building in columns.txt at compile time would present zero security risks.
It would save a lot of code........

razor.txt
columns.txt

@Toxicable
Copy link

@seanspyder then make a component

@bbottema
Copy link

bbottema commented Apr 2, 2018

I'm dealing with a complex responsive HTML template, which has a lot of alternative HTML markup based on media queries. That is to say, the Angular component is the same (data, interaction and functionality), but has many gradual steps of presentation as a visual component on screen.

For this use case I would want to be able to split up my HTML so I can focus on the various screen sizes of the visual components in my template. This is currently not possible with Angular, and I don't see how I can make components from this, seeing as their inclusion is media-query based.

Because of this, I'm forced to deal with a 6 page long jumble of HTML. I don't want to go into the manual dynamic component trickery, I came to Angular to avoid that sort of juggling. Same goes for rewiring component state based on visibility state based on media queries.

Currently the only sane way I see is to eject to Webpack and do some pre-processing to include HTML fragments. This too is not acceptable.

@mlc-mlapis
Copy link
Contributor

@bbottema ... it looks like you have just one big component. Maybe a composition which includes more elementary components would simplify your design?

@bbottema
Copy link

bbottema commented Apr 2, 2018

@mlc-mlapis, thanks for the suggestion, but as I said, I don't see how making a component tree based on media queries makes sense (how would you even do that, monitor style changes that are based on media queries?).

@trotyl
Copy link
Contributor

trotyl commented Apr 2, 2018

@bbottema Reaction on media query is a common behavior and also officially supported by CDK: https://github.com/angular/material2/tree/master/src/cdk/layout

@bbottema
Copy link

bbottema commented Apr 2, 2018

@trotyl Interesting indeed, thanks for the link. Still though, this complicates my situation more than it simplifies (I would have to rewrite my components to fit this layout model). I just want to organize my HTML a bit more sanely.

@woppa684
Copy link

woppa684 commented Apr 2, 2018

@bbottema had the same issue, started hostbinding a CSS class to the width of the of a component. I needed to have this also for separate components because I'm building a dynamic grid of windows that can be resized individually and I need each of them to be responsive.

In this way you can use this in CSS (using host-context) as well as in the code (because of the binding). In this way it's easier to split up your application into components. Unfortunately you're not using media queries then, but you can reach a similar effect by binding a class to your body element (or main component) for each break point.

@angular-automatic-lock-bot
Copy link

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.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Sep 13, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area: core Issues related to the framework runtime
Projects
None yet
Development

No branches or pull requests