11

I am preparing SPA website containing hundreds of article-like pages (apart from eCommerce, login etc.). Every article has its own URL. I want to realize it using Angular2. The only solution I found so far is:

1. to prepare hundreds of Agular2 components, one component for every article...

...with templateUrl pointing to article markup. So I will need hundreds of components similar to:

@core.Component({
  selector: 'article-1',
  templateUrl: 'article1.html'
})
export class Article1 {}

2. to display an article using AsyncRoute

see Lazy Loading of Route Components in Angular2

@core.Component({
  selector: 'article-wrapper',
  template: '<router-outlet></router-outlet>'
})
@router.RouteConfig([
  new router.AsyncRoute({
    path: '/article/:id',
    loader: () => {
      switch (id) {
        case 1: return Article1;
        case 2: return Article2;
          //... repeat it hundreds of times
      }
    },
    name: 'article'
  })
])
class ArticleWrapper { }

In Angular1 there was ngInclude directive, which is missing in Angular2 due to the security issues (see here).

[Edit 1] There is not only problem with the code itself. Problem is also with static nature of this solution. If I need website with sitemap and dynamic page structure - adding a single page needs recompilation of the whole ES6 JavaScript module.

[Edit 2] The concept "markup x html as data" (where markup is not only static HTML but also HTML with active components) is basic concept of whole web (every CMS has its markup data in database). If there does not exist Angular2 solution for it, it denies this basic concept. I believe that there must exist some trick.

Pavel Zika
  • 207
  • 2
  • 7
  • So this is all about missing support of binding to user-supplied templates, right? – Günter Zöchbauer Mar 15 '16 at 10:43
  • I am confused with the point of this question. So if you had a `/articles/articleId` route, and you make a request to get that specific content by Id, why do you need all the static hundred pages? To not talk about the size your javascript will get. – Joel Almeida Mar 15 '16 at 10:55
  • Gunter: Thats true. Similar to ngInclude in Angular1. Joel: Curiously, it seems that Angular2 does not alow "Content by Id" scenario. All content have to be wraped by component (statically defined in code). – Pavel Zika Mar 15 '16 at 10:55
  • I agree with @JoelAlmeida. For what you explained, since your presentation will depend on `id`, one html file and one route will do the job. What's shown in the html file will depend on the ArticleWrapper component. – Eric Martinez Mar 15 '16 at 11:00
  • Thanks Eric. But how do you solve ArticleWrapper component for "articles in database"? As I wrote "it seems that Angular2 does not allow Content-by-Id scenario". All content have to be wrapped by component (statically defined in code). When I realize it, I cannot believe. And that is a reason for this article. There must exist some solution!!! – Pavel Zika Mar 15 '16 at 11:07
  • @PavelZika make the request to your database through your component and then print what's returned. – Eric Martinez Mar 15 '16 at 11:21
  • 1
    Thanks @Eric, but I can print static text only (maybe with some passive HTML tags). What if the article is product description from Product Catalogue with some active part (= with some Angular2 component)? Or if the article is eLearning exercise (with GapFill, Pairing, Single selection etc. components)? Imagine system with directory of reusable components, by means of which can user publish articles, exercises, product descriptions etc. I supposed that Angular2 is correct tool for realizing such system - it seems that it is not possible now. See my [Edit 2] above. – Pavel Zika Mar 15 '16 at 11:42
  • @PavelZika I see, I understand and there's no easy way. The only workaround I know of it's in the same issue you referenced in your question, by Alex, see his [comment](https://github.com/angular/angular/issues/2753#issuecomment-137778267). So, basically, use one route, one component and Alex's hack. With that you can avoid writing 100 differents routes, or more... – Eric Martinez Mar 15 '16 at 11:47
  • @EricMartinez thanks for confirmation that there's no easy and elegant way how to solve it. Unfortunately, this feature is so fundamental to me that I have to choose React instead. I'm just surprised that the lack of dynamic template doesn't bother other developers, so that Angular team comes up with some solution. – Pavel Zika Mar 15 '16 at 12:21
  • @PavelZika just for information, what does it look like in React? Is it a built-in feature? – Melou Mar 15 '16 at 15:14
  • Just make a generic template and fetch the correct article which populates the template, what's the problem? ;s – Chrillewoodz Apr 05 '17 at 07:04

1 Answers1

6

All following solutions are tricky. Official Angular team support issue is here.

Thanks to @EricMartinez for pointing me to @alexpods solution:

this.laoder.loadIntoLocation(
  toComponent(template, directives), 
  this.elementRef,
  'container'
);

function toComponent(template, directives = []) {
  @Component({ selector: 'fake-component' })
  @View({ template, directives })
  class FakeComponent {}

  return FakeComponent;
}

And another similar (from @jpleclerc):

@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
      );
    }
}

A bit different (from @peter-svintsitskyi):

// Faking class declaration by creating new instance each time I need.
        var component = new (<Type>Function)();
        var annotations = [
            new Component({
                selector: "foo"
            }),
            new View({
                template: text,
                directives: [WordDirective]
            })
        ];

        // I know this will not work everywhere
        Reflect.defineMetadata("annotations", annotations, component);

        // compile the component
        this.compiler.compileInHost(<Type>component).then((protoViewRef: ProtoViewRef) => {
            this.viewContainer.createHostView(protoViewRef);
        });
Pavel Zika
  • 207
  • 2
  • 7
  • +1 to this answer because it is linked from this answer: http://stackoverflow.com/a/37994020/1175496 , which is all about the reason for `platform-browser` vs `plastform-browser-dynamic`; is the latter necessary to do these? – Nate Anderson Mar 17 '17 at 07:34