【问题标题】:Bootstrap the same angular2 module multiple times in a page在一个页面中多次引导同一个 angular2 模块
【发布时间】:2017-04-08 13:37:36
【问题描述】:

我有一个 Web 表单应用程序,它根据配置将用户控件动态加载到页面上(就像带有可重复使用小部件的 CMS)。我想使用 Angular2 组件实现用户控件/小部件之一。

<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="Ng2Widget.ascx.cs" Inherits="Namespace.Ng2Widget" %>
<div>
     <app-root inputValue="<%= settingsValue %>"></app-root>
</div>

“settingsValue”将是一个服务器端变量,其中包含服务器想要传递给组件的一些值。

因此,如果管理员使用这个新实现的小部件的多个小部件配置页面,则呈现的页面将具有多个相同类型的 Angular2 组件。

渲染出来的页面基本会有

<div>
         <app-root inputValue="settingsValue1"></app-root>
</div>
<!-- Some other widgets -->
<div>
         <app-root inputValue="settingsValue2"></app-root>
</div>

为了将值传递给根组件,我使用了本文中提到的方法。 Angular 2 input parameters on root directive

在页面底部,我放置了引导模块的 js 文件。

<script type="text/javascript" src="ng2/dist/inline.bundle.js"></script>
<script type="text/javascript" src="ng2/dist/styles.bundle.js"></script>
<script type="text/javascript" src="ng2/dist/main.bundle.js"></script>

一旦页面被加载,Angular 只会将模块引导到标签的第一个实例,而完全忽略第二个。

我在网上找到的所有示例都试图引导多个不同模块类型的模块,这似乎工作正常。

在 Angular1 中,根应用程序的作用域是一个 dom 元素,任何带有 ng-controller 属性的 dom 元素都将组件加载到各自的位置。

在 Angular2 的早期版本中,我们能够引导组件。但是随着 NgModule 的引入,一个模块正在被引导。但即便如此,我也不认为同一个组件可以引导到多个实例。(即标签的所有实例,都会呈现组件。)

我看到了关于相同的讨论 https://www.reddit.com/r/Angular2/comments/424nwn/using_angular_2_without_it_being_a_single_page_app/

我觉得这与旧的服务器端驱动应用程序尝试使用 Angular2 等客户端框架有关。我能够非常轻松地使用 React 组件,因为它们在运行时通过简单的渲染方法调用被渲染并附加到 dom 元素。

有什么方法/解决方法可以在一个页面中引导同一模块的多个实例? IE。在所有出现的标签中加载组件。

【问题讨论】:

  • 有一些变通方法或技巧。我想记住一个带有完整示例的 GitHub 问题中的评论,但不知道如何找到它的更多细节。似乎有计划最终更好地支持这一点。
  • 你实现过这个吗?

标签: angular


【解决方案1】:

希望在此处包含此作为完整模块示例。这可以通过在 NgModule ngDoBootstrap 方法中手动引导您的根级组件来完成。

(请注意,在 Angular 5+ 中可能不再需要此方法,请参阅 this Angular PR

我们首先找到我们想要引导的所有根元素,并给它们一个唯一的 ID。然后对于每个实例,使用新 ID 破解组件工厂选择器并触发引导程序。

const entryComponents = [
  RootComponent,
];

@NgModule({
  entryComponents,
  imports: [
    BrowserModule,
  ],
  declarations: [
    RootComponent,
  ],
})
export class MyModule {
  constructor(private resolver: ComponentFactoryResolver) {}

  ngDoBootstrap(appRef: ApplicationRef) {
    entryComponents.forEach((component: any) => {
      const factory = this.resolver.resolveComponentFactory(component);
      let selectorName;
      let elements;

      // if selector is a class
      if (factory.selector.startsWith('.')) {
        selectorName = factory.selector.replace(/^\./, '');
        elements = document.getElementsByClassName(selectorName);

      // else assume selector is an element
      } else {
        selectorName = factory.selector;
        elements = document.getElementsByTagName(selectorName);
      }

      // no elements found, early return
      if (elements.length === 0) {
        return;
      }

      // more than one root level componenet found, bootstrap unique instances
      if (elements.length > 1) {
        const originalSelector = factory.selector;

        for (let i = 0; i < elements.length; i += 1) {
          elements[i].id = selectorName + '_' + i;
          (<any>factory).factory.selector = '#' + elements[i].id;
          appRef.bootstrap(factory);
        }

        (<any>factory).factory.selector = originalSelector;

      // only a single root level component found, bootstrap as usual
      } else {
        appRef.bootstrap(factory);
      }
    });
  }
}

现在,假设我们的 RootComponent 的选择器是“.angular-micro-app”,这将按预期工作:

<body>
    <div class="angular-micro-app"></div>
    ...
    <div class="angular-micro-app"></div>
</body>

【讨论】:

  • 它可以工作,但路由器插座不工作。你有什么想法让路由器插座也被隔离吗?
【解决方案2】:

这适用于 angular5:

export class DetailOfferAppModule {

    constructor(private resolver: ComponentFactoryResolver) {

    }

    ngDoBootstrap(appRef: ApplicationRef) {

        ...

        //find matching elements on the page and bootstrap each one
        var elements = $('my-widget').toArray();
        if (elements && elements.length > 0) {

            elements.forEach(sectionElement => {
                appRef.bootstrap(MyComponent, sectionElement);
            });
        }
    }
}

HTML

<my-widget id="widgetA"></my-widget>
<my-widget id="widgetB"></my-widget>

【讨论】:

  • 它可以工作,但路由器插座不起作用。你有什么想法让路由器插座也被隔离吗?
【解决方案3】:

我也遇到了完全一样的问题,挖了网,终于在这个plnkr中找到了解决方法:

https://plnkr.co/edit/I6kxKa78Np73sIWbbYHz?p=preview

神奇之处在于 ngDoBootstrap 函数:

ngDoBootstrap(appRef : ApplicationRef) {
    this.components.forEach((componentDef : {type: Type<any>, selector: string}) => {
      const factory = this.resolver.resolveComponentFactory(componentDef.type);
      factory.selector = componentDef.selector;
      appRef.bootstrap(factory);
    });
}

想法是在包装模块中注入您的组件实例列表,并引导 ComponentFactoryResolver 检索到的每个组件。

希望对您有所帮助。

【讨论】:

  • 为此,需要在开发时知道要使用多少个实例,对吗?
  • @Arjan 这很完美。您不需要知道在开发时要引导的组件。当你的页面被渲染(由 CMS 驱动)时,你将组件推送到一个数组中并在这里使用它来循环。
  • @rmchndrng & mth,很好。下一个问题:您使用的是 AOT 吗? (我正要(ab)使用不同的方法,使用@Component({ selector: 'body', template: document.body.innerHTML})。效果出奇的好,除非该主体包含 JavaScript,并且它使 AOT 编译器非常不高兴。)
  • @mth.vidal 上述解决方案在最新版本中失败,因为“factory.selector”是一个常数。有没有相同的更新方法???
  • @RahulBhooteshwar 看看blog.novatec-gmbh.de/angular-2-in-a-multi-page-application。但是,它只会引导每个选择器一次。
猜你喜欢
  • 1970-01-01
  • 2013-07-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-05-05
  • 2012-08-23
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多