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

Support passing in an element or a custom selector to `bootstrap` #7136

Closed
tbosch opened this Issue Feb 17, 2016 · 44 comments

Comments

Projects
None yet
@tbosch
Member

tbosch commented Feb 17, 2016

This is important if there are multiple applications on the same page...

@tbosch

This comment has been minimized.

Show comment
Hide comment
@tbosch

tbosch Feb 17, 2016

Member

Blocked on #6270 as it touches the same code.

Member

tbosch commented Feb 17, 2016

Blocked on #6270 as it touches the same code.

@tbosch

This comment has been minimized.

Show comment
Hide comment
@tbosch

tbosch Feb 17, 2016

Member

(only the first part of #6270)

Member

tbosch commented Feb 17, 2016

(only the first part of #6270)

@playground

This comment has been minimized.

Show comment
Hide comment
@playground

playground Feb 18, 2016

+1, Thanks Tobias for the hack session today.

playground commented Feb 18, 2016

+1, Thanks Tobias for the hack session today.

@mrjmd

This comment has been minimized.

Show comment
Hide comment
@mrjmd

mrjmd Feb 18, 2016

Thanks for the help with a workaround on this Tobias!

mrjmd commented Feb 18, 2016

Thanks for the help with a workaround on this Tobias!

@tbosch

This comment has been minimized.

Show comment
Hide comment
@tbosch
Member

tbosch commented Feb 18, 2016

A workaround can be found here: http://plnkr.co/edit/O0KRZHUz5jdW40mY1EgA?p=preview

@episodeyang

This comment has been minimized.

Show comment
Hide comment
@episodeyang

episodeyang Feb 26, 2016

@tbosch is the plunkr saved correctly? inside it looks very basic and doesn't have multiple instances of the app.

episodeyang commented Feb 26, 2016

@tbosch is the plunkr saved correctly? inside it looks very basic and doesn't have multiple instances of the app.

@zoechi

This comment has been minimized.

Show comment
Hide comment
@zoechi

zoechi Feb 26, 2016

Contributor

dup of #915 ?

Contributor

zoechi commented Feb 26, 2016

dup of #915 ?

@mrjmd

This comment has been minimized.

Show comment
Hide comment
@mrjmd

mrjmd Mar 8, 2016

@episodeyang here's a slightly expanded example with multiple instances: http://plnkr.co/edit/iMZH4O47GU4LsqSq9yvL?p=preview.

mrjmd commented Mar 8, 2016

@episodeyang here's a slightly expanded example with multiple instances: http://plnkr.co/edit/iMZH4O47GU4LsqSq9yvL?p=preview.

@roben

This comment has been minimized.

Show comment
Hide comment
@roben

roben Mar 16, 2016

import {APP_COMPONENT_REF_PROMISE} from 'angular2/src/core/application_tokens'; does not work in 2.0.0-beta.9. Has it been removed?

roben commented Mar 16, 2016

import {APP_COMPONENT_REF_PROMISE} from 'angular2/src/core/application_tokens'; does not work in 2.0.0-beta.9. Has it been removed?

@roben

This comment has been minimized.

Show comment
Hide comment
@roben

roben Mar 17, 2016

I manually readded it to application_tokens.d.ts as it is still defined in the .js code, but nevertheless the provider is not called anymore. Here is my adaption of the code for the webpack main.ts:

/*
 * Bootstrap our Angular app with a top level component `App` and inject
 * our Services and Providers into Angular's dependency injection
 */
export function main(e: CustomEvent) {
    // see https://github.com/angular/angular/issues/7136 for this, may be simplified by angular in future releases
    var selector = 'div#sams-web-plugin-component-' + e.detail.appId;
    var bootstrapProviders = [
        ngCore.provide(APP_COMPONENT_REF_PROMISE,
            {
                useFactory: (dynamicComponentLoader: ngCore.DynamicComponentLoader, appRef: ngCore.ApplicationRef,
                    injector: ngCore.Injector) => {
                        console.log("Factory called");
                    var ref: ngCore.ComponentRef;
                    return dynamicComponentLoader.loadAsRoot(AppComponent, selector, injector, () => { console.log("should unload now"); }).then((componentRef) => ref = componentRef);
                },
                deps: [ngCore.DynamicComponentLoader, ngCore.ApplicationRef, ngCore.Injector]
            }),
        ...APPLICATION_PROVIDERS,
        ngCore.provide(ngCore.PLATFORM_DIRECTIVES, { useValue: APPLICATION_DIRECTIVES, multi: true }),
        ngCore.provide(ngCore.PLATFORM_PIPES, { useValue: APPLICATION_PIPES, multi: true }),
    ];
    return browser.bootstrap(AppComponent, bootstrapProviders).catch(err => console.error(err));
}

The main component is created but its selector is not touched. "Factory called" is never logged to the console. appRef._unloadComponent is also not available anymore, but I guess this should not affect bootstrapping.

roben commented Mar 17, 2016

I manually readded it to application_tokens.d.ts as it is still defined in the .js code, but nevertheless the provider is not called anymore. Here is my adaption of the code for the webpack main.ts:

/*
 * Bootstrap our Angular app with a top level component `App` and inject
 * our Services and Providers into Angular's dependency injection
 */
export function main(e: CustomEvent) {
    // see https://github.com/angular/angular/issues/7136 for this, may be simplified by angular in future releases
    var selector = 'div#sams-web-plugin-component-' + e.detail.appId;
    var bootstrapProviders = [
        ngCore.provide(APP_COMPONENT_REF_PROMISE,
            {
                useFactory: (dynamicComponentLoader: ngCore.DynamicComponentLoader, appRef: ngCore.ApplicationRef,
                    injector: ngCore.Injector) => {
                        console.log("Factory called");
                    var ref: ngCore.ComponentRef;
                    return dynamicComponentLoader.loadAsRoot(AppComponent, selector, injector, () => { console.log("should unload now"); }).then((componentRef) => ref = componentRef);
                },
                deps: [ngCore.DynamicComponentLoader, ngCore.ApplicationRef, ngCore.Injector]
            }),
        ...APPLICATION_PROVIDERS,
        ngCore.provide(ngCore.PLATFORM_DIRECTIVES, { useValue: APPLICATION_DIRECTIVES, multi: true }),
        ngCore.provide(ngCore.PLATFORM_PIPES, { useValue: APPLICATION_PIPES, multi: true }),
    ];
    return browser.bootstrap(AppComponent, bootstrapProviders).catch(err => console.error(err));
}

The main component is created but its selector is not touched. "Factory called" is never logged to the console. appRef._unloadComponent is also not available anymore, but I guess this should not affect bootstrapping.

@roben

This comment has been minimized.

Show comment
Hide comment
@roben

roben Mar 31, 2016

Got it now. The key was to change return browser.bootstrap(AppComponent, bootstrapProviders).catch(err => console.error(err)); to:

let app = ngCore.platform(browser.BROWSER_PROVIDERS).application([browser.BROWSER_APP_PROVIDERS]);
return app.bootstrap(AppComponent, bootstrapProviders).catch(err => console.error(err));

Anyways, is it possible to remove the @internal annotation from APP_COMPONENT_REF_PROMISE in application_tokens.ts until the main issue is cleared and this can be handled in a more elegant way? Manually re-adding the export to the .d.ts file does not make this better and the functionality still is a key requirement.

roben commented Mar 31, 2016

Got it now. The key was to change return browser.bootstrap(AppComponent, bootstrapProviders).catch(err => console.error(err)); to:

let app = ngCore.platform(browser.BROWSER_PROVIDERS).application([browser.BROWSER_APP_PROVIDERS]);
return app.bootstrap(AppComponent, bootstrapProviders).catch(err => console.error(err));

Anyways, is it possible to remove the @internal annotation from APP_COMPONENT_REF_PROMISE in application_tokens.ts until the main issue is cleared and this can be handled in a more elegant way? Manually re-adding the export to the .d.ts file does not make this better and the functionality still is a key requirement.

@playground

This comment has been minimized.

Show comment
Hide comment
@playground

playground May 8, 2016

@tbosch how to get this to work in rc.1?

playground commented May 8, 2016

@tbosch how to get this to work in rc.1?

@gionkunz

This comment has been minimized.

Show comment
Hide comment
@gionkunz

gionkunz May 24, 2016

Contributor

In RC1 this is partially supported as ComponentFactory.create supports both a selector or a DOM element for creating a component. However, ApplicationRef.bootstrap does not support an override selector or element and therefore uses the component factory default which is the selector property of the resolved component.

This was changed a lot on the current master but this solution (or rather a hack) overrides the selector member of the ComponentFactory before doing an application bootstrap. It works, however, this will change with the next release. Also, it's rather hacky to override the selector of ComponentFactory. Since this component will only be created by our bootstrapping, I guess it's kind of safe.

import {Component} from '@angular/core';

import {platform, ApplicationRef, Provider, Type, ComponentRef, ComponentResolver, ReflectiveInjector} from '@angular/core';
import {BROWSER_APP_DYNAMIC_PROVIDERS} from '@angular/platform-browser-dynamic';
import {browserPlatform} from '@angular/platform-browser';

// TODO: This works for RC1 but is likely to change
export function bootstrapWithElement(componentType: Type, element: Element, providers?: Array<any>): Promise<ComponentRef<any>> {
  const appInjector = ReflectiveInjector.resolveAndCreate(
    [BROWSER_APP_DYNAMIC_PROVIDERS, providers ? providers : []], browserPlatform().injector);
  const appRef: ApplicationRef = appInjector.get(ApplicationRef);

  return appRef.run(() => {
    var componentResolver: ComponentResolver = appInjector.get(ComponentResolver);
    return Promise
      .all([componentResolver.resolveComponent(componentType), appRef.waitForAsyncInitializers()])
      .then((arr) => {
        const [componentFactory] = arr;
        // TODO: This is rather hacky
        componentFactory.selector = element;
        appRef.bootstrap(componentFactory);
      });
  });
}

@Component({
  selector: 'irrelevant',
  template: 'Hello World'
})
export class App {}


bootstrapWithElement(App, document.querySelector('my-app'));
Contributor

gionkunz commented May 24, 2016

In RC1 this is partially supported as ComponentFactory.create supports both a selector or a DOM element for creating a component. However, ApplicationRef.bootstrap does not support an override selector or element and therefore uses the component factory default which is the selector property of the resolved component.

This was changed a lot on the current master but this solution (or rather a hack) overrides the selector member of the ComponentFactory before doing an application bootstrap. It works, however, this will change with the next release. Also, it's rather hacky to override the selector of ComponentFactory. Since this component will only be created by our bootstrapping, I guess it's kind of safe.

import {Component} from '@angular/core';

import {platform, ApplicationRef, Provider, Type, ComponentRef, ComponentResolver, ReflectiveInjector} from '@angular/core';
import {BROWSER_APP_DYNAMIC_PROVIDERS} from '@angular/platform-browser-dynamic';
import {browserPlatform} from '@angular/platform-browser';

// TODO: This works for RC1 but is likely to change
export function bootstrapWithElement(componentType: Type, element: Element, providers?: Array<any>): Promise<ComponentRef<any>> {
  const appInjector = ReflectiveInjector.resolveAndCreate(
    [BROWSER_APP_DYNAMIC_PROVIDERS, providers ? providers : []], browserPlatform().injector);
  const appRef: ApplicationRef = appInjector.get(ApplicationRef);

  return appRef.run(() => {
    var componentResolver: ComponentResolver = appInjector.get(ComponentResolver);
    return Promise
      .all([componentResolver.resolveComponent(componentType), appRef.waitForAsyncInitializers()])
      .then((arr) => {
        const [componentFactory] = arr;
        // TODO: This is rather hacky
        componentFactory.selector = element;
        appRef.bootstrap(componentFactory);
      });
  });
}

@Component({
  selector: 'irrelevant',
  template: 'Hello World'
})
export class App {}


bootstrapWithElement(App, document.querySelector('my-app'));
@roben

This comment has been minimized.

Show comment
Hide comment
@roben

roben Jun 6, 2016

I can confirm that this works. Thanks a lot for this intermediate solution!

roben commented Jun 6, 2016

I can confirm that this works. Thanks a lot for this intermediate solution!

@roben

This comment has been minimized.

Show comment
Hide comment
@roben

roben Jul 15, 2016

And with the current rc it seems to be broken again.

const appRef: ApplicationRef = appInjector.get(ApplicationRef) causes a No provider for ApplicationRef exception.

roben commented Jul 15, 2016

And with the current rc it seems to be broken again.

const appRef: ApplicationRef = appInjector.get(ApplicationRef) causes a No provider for ApplicationRef exception.

@playground

This comment has been minimized.

Show comment
Hide comment
@playground

playground Jul 15, 2016

As of rc.2 you can do this instead

let app = ReflectiveInjector
.resolveAndCreate([
BROWSER_APP_PROVIDERS
])
.get(ApplicationRef);

However, depending on how you're bootstrapping your app, If you're doing custom bootstrap using ComponentResolver, looks like it has be deprecated in rc.4

playground commented Jul 15, 2016

As of rc.2 you can do this instead

let app = ReflectiveInjector
.resolveAndCreate([
BROWSER_APP_PROVIDERS
])
.get(ApplicationRef);

However, depending on how you're bootstrapping your app, If you're doing custom bootstrap using ComponentResolver, looks like it has be deprecated in rc.4

@roben

This comment has been minimized.

Show comment
Hide comment
@roben

roben Jul 15, 2016

Thanks, seems promising. But this causes a different error now:

zone.js:260Uncaught No provider for PlatformRef_! (ApplicationRef -> ApplicationRef_ -> PlatformRef_)

roben commented Jul 15, 2016

Thanks, seems promising. But this causes a different error now:

zone.js:260Uncaught No provider for PlatformRef_! (ApplicationRef -> ApplicationRef_ -> PlatformRef_)

@roben

This comment has been minimized.

Show comment
Hide comment
@roben

roben Jul 15, 2016

Changed it a bit. The following code works, but as you already suggested, bootstrapping via ComponentResolver now is broken.

export function bootstrapWithElement(componentType: Type, selector: string): Promise<ComponentRef<any>> {

    const platform = createPlatform(ReflectiveInjector.resolveAndCreate([BROWSER_PLATFORM_PROVIDERS]));

    const appRef: ApplicationRef = ReflectiveInjector
        .resolveAndCreate([
            ALL_PROVIDERS
        ], platform.injector)
        .get(ApplicationRef);

    return appRef.run(() => {
        const appInjector = ReflectiveInjector.resolveAndCreate([ALL_PROVIDERS], platform.injector);
        var componentResolver: ComponentResolver = appInjector.get(ComponentResolver);
        return Promise
            .all([componentResolver.resolveComponent(componentType), appRef.waitForAsyncInitializers()])
            .then((arr: any) => {
                const [componentFactory] = arr;
                componentFactory.selector = selector;
                appRef.bootstrap(componentFactory);
            }).catch(err => console.error(err));
    });
}

componentResolver.resolveComponent(componentType) causes No precompiled component AppComponent found. Any ideas?

Or am I diverging too far from the original goal, to pass a custom element selector to the bootstrap process?

roben commented Jul 15, 2016

Changed it a bit. The following code works, but as you already suggested, bootstrapping via ComponentResolver now is broken.

export function bootstrapWithElement(componentType: Type, selector: string): Promise<ComponentRef<any>> {

    const platform = createPlatform(ReflectiveInjector.resolveAndCreate([BROWSER_PLATFORM_PROVIDERS]));

    const appRef: ApplicationRef = ReflectiveInjector
        .resolveAndCreate([
            ALL_PROVIDERS
        ], platform.injector)
        .get(ApplicationRef);

    return appRef.run(() => {
        const appInjector = ReflectiveInjector.resolveAndCreate([ALL_PROVIDERS], platform.injector);
        var componentResolver: ComponentResolver = appInjector.get(ComponentResolver);
        return Promise
            .all([componentResolver.resolveComponent(componentType), appRef.waitForAsyncInitializers()])
            .then((arr: any) => {
                const [componentFactory] = arr;
                componentFactory.selector = selector;
                appRef.bootstrap(componentFactory);
            }).catch(err => console.error(err));
    });
}

componentResolver.resolveComponent(componentType) causes No precompiled component AppComponent found. Any ideas?

Or am I diverging too far from the original goal, to pass a custom element selector to the bootstrap process?

@playground

This comment has been minimized.

Show comment
Hide comment
@playground

playground Jul 15, 2016

That is broken for me too. You can follow this tracking issue here #9726

playground commented Jul 15, 2016

That is broken for me too. You can follow this tracking issue here #9726

@gionkunz

This comment has been minimized.

Show comment
Hide comment
@gionkunz

gionkunz Oct 6, 2016

Contributor

Has any work been done on this? If not, I'd pick this up.

Contributor

gionkunz commented Oct 6, 2016

Has any work been done on this? If not, I'd pick this up.

@gionkunz

This comment has been minimized.

Show comment
Hide comment
@gionkunz

gionkunz Oct 6, 2016

Contributor

Currently we're using something like this as a broker to load and instantiate modules. There's still some work for AoT compilation that needs to be done:

import {Injector, NgModule, Component, Compiler} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';

@NgModule({
  imports: [BrowserModule]
})
export class CoreModule {
  constructor(private compiler: Compiler, private injector: Injector) {}

  ngDoBootstrap() {
    this.load(
      './app/sample-module.ts',
      'SampleModule', 
      'SampleModuleComponent',
      document.querySelector('.boot-container')
    );
  }

  load(modulePath: string, moduleName: string, componentName: string, node: HTMLElement) {
    System
      .import(modulePath)
      .then((module) => module[moduleName])
      .then((AppModule) => 
        this.compiler.compileModuleAndAllComponentsAsync(AppModule)
      )
      .then((factories) => {
        const appModuleInstance = factories.ngModuleFactory.create(this.injector);

        const appComponentFactory = 
          factories.componentFactories.find(
            (componentFactory) => componentFactory.componentType.name === componentName);

        appComponentFactory.create(appModuleInstance.injector, null, node);
      });
  }
}

http://plnkr.co/edit/R10OjPcIK7RXCskzfq3O

Contributor

gionkunz commented Oct 6, 2016

Currently we're using something like this as a broker to load and instantiate modules. There's still some work for AoT compilation that needs to be done:

import {Injector, NgModule, Component, Compiler} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';

@NgModule({
  imports: [BrowserModule]
})
export class CoreModule {
  constructor(private compiler: Compiler, private injector: Injector) {}

  ngDoBootstrap() {
    this.load(
      './app/sample-module.ts',
      'SampleModule', 
      'SampleModuleComponent',
      document.querySelector('.boot-container')
    );
  }

  load(modulePath: string, moduleName: string, componentName: string, node: HTMLElement) {
    System
      .import(modulePath)
      .then((module) => module[moduleName])
      .then((AppModule) => 
        this.compiler.compileModuleAndAllComponentsAsync(AppModule)
      )
      .then((factories) => {
        const appModuleInstance = factories.ngModuleFactory.create(this.injector);

        const appComponentFactory = 
          factories.componentFactories.find(
            (componentFactory) => componentFactory.componentType.name === componentName);

        appComponentFactory.create(appModuleInstance.injector, null, node);
      });
  }
}

http://plnkr.co/edit/R10OjPcIK7RXCskzfq3O

@vicb vicb removed the state: Blocked label Oct 7, 2016

@chrisse27

This comment has been minimized.

Show comment
Hide comment
@chrisse27

chrisse27 Nov 7, 2016

Contributor

Here is a Plunker that shows how we are currently solving the issue with the latest Angular 2:

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

Contributor

chrisse27 commented Nov 7, 2016

Here is a Plunker that shows how we are currently solving the issue with the latest Angular 2:

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

@roben

This comment has been minimized.

Show comment
Hide comment
@roben

roben Nov 7, 2016

Thanks for the insight! But this seems like a lot of boiler plate code for this single requirement.

It's sad that the angular team removed the "P2: required" label even though the effort is set to be easy. So angular team, could you give us a status update?

roben commented Nov 7, 2016

Thanks for the insight! But this seems like a lot of boiler plate code for this single requirement.

It's sad that the angular team removed the "P2: required" label even though the effort is set to be easy. So angular team, could you give us a status update?

@chrisse27

This comment has been minimized.

Show comment
Hide comment
@chrisse27

chrisse27 Nov 7, 2016

Contributor

I agree, I removed some stuff, but there are still a few classes necessary.

Contributor

chrisse27 commented Nov 7, 2016

I agree, I removed some stuff, but there are still a few classes necessary.

@chrisse27

This comment has been minimized.

Show comment
Hide comment
@chrisse27

chrisse27 Nov 8, 2016

Contributor

@roben I found some more time to reduce the Plunker to the minimum I can currently think of: https://plnkr.co/edit/I6kxKa78Np73sIWbbYHz?p=preview

Contributor

chrisse27 commented Nov 8, 2016

@roben I found some more time to reduce the Plunker to the minimum I can currently think of: https://plnkr.co/edit/I6kxKa78Np73sIWbbYHz?p=preview

@roben

This comment has been minimized.

Show comment
Hide comment
@roben

roben Nov 8, 2016

@chrisse27 Again, thank you very much for your work, that's a really sleek solution now! So for me, this suffices. Maybe it can be seen as a solution for the whole issue itself?

roben commented Nov 8, 2016

@chrisse27 Again, thank you very much for your work, that's a really sleek solution now! So for me, this suffices. Maybe it can be seen as a solution for the whole issue itself?

DzmitryShylovich added a commit to DzmitryShylovich/angular that referenced this issue Feb 26, 2017

@jonilko

This comment has been minimized.

Show comment
Hide comment
@jonilko

jonilko Mar 25, 2017

How would this work with Angular 4.x? ComponentFactory.selector is now read only. How can you set the selector in ngDoBootstrap?

After updating my code based on this solution to Angular 4.x, I get this error when I compile:

error TS2540: Cannot assign to 'selector' because it is a constant or a read-only property.

This Plunker no longer works: https://plnkr.co/edit/I6kxKa78Np73sIWbbYHz?p=preview

jonilko commented Mar 25, 2017

How would this work with Angular 4.x? ComponentFactory.selector is now read only. How can you set the selector in ngDoBootstrap?

After updating my code based on this solution to Angular 4.x, I get this error when I compile:

error TS2540: Cannot assign to 'selector' because it is a constant or a read-only property.

This Plunker no longer works: https://plnkr.co/edit/I6kxKa78Np73sIWbbYHz?p=preview

@chrisse27

This comment has been minimized.

Show comment
Hide comment
@chrisse27

chrisse27 Mar 25, 2017

Contributor

@tbosch Do you see any possibilities how we could get the plunkr above working again? We have several projects running that rely on this feature.

Contributor

chrisse27 commented Mar 25, 2017

@tbosch Do you see any possibilities how we could get the plunkr above working again? We have several projects running that rely on this feature.

@DzmitryShylovich

This comment has been minimized.

Show comment
Hide comment
Contributor

DzmitryShylovich commented Mar 25, 2017

@chrisse27

This comment has been minimized.

Show comment
Hide comment
@chrisse27

chrisse27 Mar 25, 2017

Contributor

@DzmitryShylovich Thanks, didn't see that when browsing through the code.

Btw, thanks also for the PR that you referenced above.

Contributor

chrisse27 commented Mar 25, 2017

@DzmitryShylovich Thanks, didn't see that when browsing through the code.

Btw, thanks also for the PR that you referenced above.

@jonilko

This comment has been minimized.

Show comment
Hide comment
@jonilko

jonilko Mar 26, 2017

@tbosch @DzmitryShylovich - thanks so much for the quick response.

I'm having trouble getting this 'hack' to work outside of Plunker. Typescript compilation fails with:
src/app.ts(38,19): error TS2339: Property 'factory' does not exist on type 'ComponentFactory<any>'.

I've attached a simple quickstart project to demo this.

npm install
npm start

> angular-quickstart@1.0.0 prestart /Users/jonilko/temp/quickstart
> npm run build

> angular-quickstart@1.0.0 build /Users/jonilko/temp/quickstart
> tsc -p src/

src/app.ts(38,19): error TS2339: Property 'factory' does not exist on type 'ComponentFactory<any>'.

npm ERR! Darwin 16.4.0
npm ERR! argv "/usr/local/Cellar/node/7.4.0/bin/node" "/usr/local/bin/npm" "run" "build"
...
quickstart.zip

Any idea how to get around this issue?

console shows that factory is actually type ComponentFactoryBoundToModule. I've tried casting (<ComponentFactoryBoundToModule>factory).factory.selector but ComponentFactoryBoundToModule is not exported.

jonilko commented Mar 26, 2017

@tbosch @DzmitryShylovich - thanks so much for the quick response.

I'm having trouble getting this 'hack' to work outside of Plunker. Typescript compilation fails with:
src/app.ts(38,19): error TS2339: Property 'factory' does not exist on type 'ComponentFactory<any>'.

I've attached a simple quickstart project to demo this.

npm install
npm start

> angular-quickstart@1.0.0 prestart /Users/jonilko/temp/quickstart
> npm run build

> angular-quickstart@1.0.0 build /Users/jonilko/temp/quickstart
> tsc -p src/

src/app.ts(38,19): error TS2339: Property 'factory' does not exist on type 'ComponentFactory<any>'.

npm ERR! Darwin 16.4.0
npm ERR! argv "/usr/local/Cellar/node/7.4.0/bin/node" "/usr/local/bin/npm" "run" "build"
...
quickstart.zip

Any idea how to get around this issue?

console shows that factory is actually type ComponentFactoryBoundToModule. I've tried casting (<ComponentFactoryBoundToModule>factory).factory.selector but ComponentFactoryBoundToModule is not exported.

@DzmitryShylovich

This comment has been minimized.

Show comment
Hide comment
@DzmitryShylovich

DzmitryShylovich Mar 26, 2017

Contributor

@jonilko it's a private property, you can't access it directly.
(<any>factory).factory.selector or factory["factory"].selector

Contributor

DzmitryShylovich commented Mar 26, 2017

@jonilko it's a private property, you can't access it directly.
(<any>factory).factory.selector or factory["factory"].selector

@jonilko

This comment has been minimized.

Show comment
Hide comment
@jonilko

jonilko Mar 26, 2017

perfect... that works.

Thank you.

jonilko commented Mar 26, 2017

perfect... that works.

Thank you.

@chrisse27

This comment has been minimized.

Show comment
Hide comment
@chrisse27

chrisse27 Mar 31, 2017

Contributor

I guess what would solve our problems would be to add the optional parameter "rootSelectorOrNode" of the component factory to the bootstrap method ("https://github.com/angular/angular/blob/master/packages/core/src/application_ref.ts#L494")

bootstrap<C>(componentOrFactory: ComponentFactory<C>|Type<C>, rootSelectorOrNode?: string | any): ComponentRef<C> {
  ...
  const selector = rootSelectorOrNode || componentFactory.selector;
  const compRef = componentFactory.create(Injector.NULL, [], selector, ngModule);
  ...
}

Next week I should have time to create PR for this.

Contributor

chrisse27 commented Mar 31, 2017

I guess what would solve our problems would be to add the optional parameter "rootSelectorOrNode" of the component factory to the bootstrap method ("https://github.com/angular/angular/blob/master/packages/core/src/application_ref.ts#L494")

bootstrap<C>(componentOrFactory: ComponentFactory<C>|Type<C>, rootSelectorOrNode?: string | any): ComponentRef<C> {
  ...
  const selector = rootSelectorOrNode || componentFactory.selector;
  const compRef = componentFactory.create(Injector.NULL, [], selector, ngModule);
  ...
}

Next week I should have time to create PR for this.

chrisse27 pushed a commit to chrisse27/angular that referenced this issue Mar 31, 2017

chrisse27 pushed a commit to chrisse27/angular that referenced this issue Mar 31, 2017

chrisse27 pushed a commit to chrisse27/angular that referenced this issue Mar 31, 2017

chrisse27 pushed a commit to chrisse27/angular that referenced this issue Mar 31, 2017

@mashaalmemon

This comment has been minimized.

Show comment
Hide comment
@mashaalmemon

mashaalmemon Apr 5, 2017

Any idea when this feature might make it to release?

mashaalmemon commented Apr 5, 2017

Any idea when this feature might make it to release?

@chrisse27

This comment has been minimized.

Show comment
Hide comment
@chrisse27

chrisse27 Apr 5, 2017

Contributor

Assuming that @mhevery and @IgorMinar are both at @ngconf this week, I only expect feedback on the PR by next week.

Contributor

chrisse27 commented Apr 5, 2017

Assuming that @mhevery and @IgorMinar are both at @ngconf this week, I only expect feedback on the PR by next week.

chrisse27 pushed a commit to chrisse27/angular that referenced this issue Apr 27, 2017

chrisse27 pushed a commit to chrisse27/angular that referenced this issue Apr 27, 2017

@mhevery mhevery closed this in 900a88b Apr 28, 2017

@playground

This comment has been minimized.

Show comment
Hide comment
@playground

playground May 3, 2017

@mrjmd @tbosch I noticed this got closed recently. I'm wondering passing in/specifying a templateUrl is supported?

playground commented May 3, 2017

@mrjmd @tbosch I noticed this got closed recently. I'm wondering passing in/specifying a templateUrl is supported?

@chrisse27

This comment has been minimized.

Show comment
Hide comment
@chrisse27

chrisse27 May 3, 2017

Contributor

@playground Where exactly (and why) would you like to pass in a templateUrl?

Contributor

chrisse27 commented May 3, 2017

@playground Where exactly (and why) would you like to pass in a templateUrl?

@playground

This comment has been minimized.

Show comment
Hide comment
@playground

playground May 3, 2017

@chrisse27
We have widgets that others can embed as such:

<my-app data-config='{"language": "de-DE", "units": "m", "lat": "33.75", "lng": "-84.39"}'>  Loading...
</my-app>

By passing in certain configurations, the child component will get initialized based on those values. I was wondering if we can do something like to allow different view of the component based on specified template:

<my-app data-config='{"language": "de-DE", "units": "m", "lat": "33.75", "lng": "-84.39", "templateUrl"="use-this-template"}'>
  Loading...
</my-app>

playground commented May 3, 2017

@chrisse27
We have widgets that others can embed as such:

<my-app data-config='{"language": "de-DE", "units": "m", "lat": "33.75", "lng": "-84.39"}'>  Loading...
</my-app>

By passing in certain configurations, the child component will get initialized based on those values. I was wondering if we can do something like to allow different view of the component based on specified template:

<my-app data-config='{"language": "de-DE", "units": "m", "lat": "33.75", "lng": "-84.39", "templateUrl"="use-this-template"}'>
  Loading...
</my-app>
@chrisse27

This comment has been minimized.

Show comment
Hide comment
Contributor

chrisse27 commented May 3, 2017

@playground

This comment has been minimized.

Show comment
Hide comment
@playground

playground May 3, 2017

@chrisse27 cool, thanks I will give that a try.

playground commented May 3, 2017

@chrisse27 cool, thanks I will give that a try.

@denissb

This comment has been minimized.

Show comment
Hide comment
@denissb

denissb May 30, 2017

I implemented a bootstrap hook that initializes all entry components defined on the page, but it still uses the (<any>factory).factory.selector hack. Does anyone have a better way of achieving this?
Is the change to rootSelectorOrNode still planned?

const entryComponents = [
  TestComponent,
  Test2Component
];

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

  ngDoBootstrap(appRef: ApplicationRef) {
    entryComponents.forEach((component: any) => {
      const factory = this.resolver.resolveComponentFactory(component);
      const elements = document.getElementsByTagName(factory.selector);
      if (elements.length > 0) {
        if (elements.length > 1) {
          const originalSelector = factory.selector;
          for (let i = 0; i < elements.length; i++) {
            elements[i].id = originalSelector + '_' + i;
            // FIXME: this property is read-only
            (<any>factory).factory.selector = '#' + elements[i].id;
            appRef.bootstrap(factory);
          }
          (<any>factory).factory.selector = originalSelector;
        } else {
          appRef.bootstrap(factory);
        }
      }
    });
  }
}

denissb commented May 30, 2017

I implemented a bootstrap hook that initializes all entry components defined on the page, but it still uses the (<any>factory).factory.selector hack. Does anyone have a better way of achieving this?
Is the change to rootSelectorOrNode still planned?

const entryComponents = [
  TestComponent,
  Test2Component
];

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

  ngDoBootstrap(appRef: ApplicationRef) {
    entryComponents.forEach((component: any) => {
      const factory = this.resolver.resolveComponentFactory(component);
      const elements = document.getElementsByTagName(factory.selector);
      if (elements.length > 0) {
        if (elements.length > 1) {
          const originalSelector = factory.selector;
          for (let i = 0; i < elements.length; i++) {
            elements[i].id = originalSelector + '_' + i;
            // FIXME: this property is read-only
            (<any>factory).factory.selector = '#' + elements[i].id;
            appRef.bootstrap(factory);
          }
          (<any>factory).factory.selector = originalSelector;
        } else {
          appRef.bootstrap(factory);
        }
      }
    });
  }
}
@jmbarbier

This comment has been minimized.

Show comment
Hide comment
@jmbarbier

jmbarbier Feb 14, 2018

@denissb code used to work, but versions of angular (between 5.1 and 5.2) made the (<any>factory).factory.selector hack not working

we have to set (<any>factory).selector directly.

following is part of my setup to enable lazy-loading multiple components on the same page, working today on 5.2.4

export class AppModule {
  constructor(private injector: Injector, private moduleLoader: SystemJsNgModuleLoader,
              private componentFactoryResolver: ComponentFactoryResolver, private ga: AnalyticsService) {

  }

  ngDoBootstrap(appRef: ApplicationRef) {
    console.log(entryComponents);
    for (const w of entryComponents) {
      this.customBootstrapComponent(appRef, this.componentFactoryResolver.resolveComponentFactory(<any>w));
    }

    const widgets = document.querySelectorAll("[data-ng-load]");
    for (const i in widgets) {
      if (widgets.hasOwnProperty(i)) {
        const modulePath = widgets[i].getAttribute("data-ng-load");
        if (modulePath) {
          console.log("Loading module", modulePath);
          this.moduleLoader.load(modulePath)
            .then((moduleFactory: NgModuleFactory<any>) => {
              const ngModuleRef = moduleFactory.create(this.injector);
              console.log("Loading module components");
              ngModuleRef.injector.get<any>(LAZY_ROOT_COMPONENTS).forEach((components: Type<{}>[]) => {
                components.forEach((component: Type<{}>) => {
                  const factory = ngModuleRef.componentFactoryResolver.resolveComponentFactory(component);
                  this.customBootstrapComponent(appRef, factory);
                });
              });
            });
        }
      }
    }
  }

  private customBootstrapComponent(appRef: ApplicationRef, factory: ComponentFactory<{}>): void {
    // Bootstrap one or many components if page contains their selector
    const elements = document.getElementsByTagName(factory.selector);
    if (elements.length > 0) {
      if (elements.length > 1) {
        // Multiple elements, make unique selectors for each
        const originalSelector = factory.selector;
        for (let j = 0; j < elements.length; j++) {
          elements[j].id = originalSelector + "_" + j;
          (<any>factory).selector = "#" + elements[j].id;  // <<<-- THIS IS BAD !!
          console.log(factory);
          appRef.bootstrap(factory);
        }
        (<any>factory).selector = originalSelector;
      } else {
        console.log("One only element, bootstraping it", factory.selector);
        appRef.bootstrap(factory);
      }
    }
  }
}

jmbarbier commented Feb 14, 2018

@denissb code used to work, but versions of angular (between 5.1 and 5.2) made the (<any>factory).factory.selector hack not working

we have to set (<any>factory).selector directly.

following is part of my setup to enable lazy-loading multiple components on the same page, working today on 5.2.4

export class AppModule {
  constructor(private injector: Injector, private moduleLoader: SystemJsNgModuleLoader,
              private componentFactoryResolver: ComponentFactoryResolver, private ga: AnalyticsService) {

  }

  ngDoBootstrap(appRef: ApplicationRef) {
    console.log(entryComponents);
    for (const w of entryComponents) {
      this.customBootstrapComponent(appRef, this.componentFactoryResolver.resolveComponentFactory(<any>w));
    }

    const widgets = document.querySelectorAll("[data-ng-load]");
    for (const i in widgets) {
      if (widgets.hasOwnProperty(i)) {
        const modulePath = widgets[i].getAttribute("data-ng-load");
        if (modulePath) {
          console.log("Loading module", modulePath);
          this.moduleLoader.load(modulePath)
            .then((moduleFactory: NgModuleFactory<any>) => {
              const ngModuleRef = moduleFactory.create(this.injector);
              console.log("Loading module components");
              ngModuleRef.injector.get<any>(LAZY_ROOT_COMPONENTS).forEach((components: Type<{}>[]) => {
                components.forEach((component: Type<{}>) => {
                  const factory = ngModuleRef.componentFactoryResolver.resolveComponentFactory(component);
                  this.customBootstrapComponent(appRef, factory);
                });
              });
            });
        }
      }
    }
  }

  private customBootstrapComponent(appRef: ApplicationRef, factory: ComponentFactory<{}>): void {
    // Bootstrap one or many components if page contains their selector
    const elements = document.getElementsByTagName(factory.selector);
    if (elements.length > 0) {
      if (elements.length > 1) {
        // Multiple elements, make unique selectors for each
        const originalSelector = factory.selector;
        for (let j = 0; j < elements.length; j++) {
          elements[j].id = originalSelector + "_" + j;
          (<any>factory).selector = "#" + elements[j].id;  // <<<-- THIS IS BAD !!
          console.log(factory);
          appRef.bootstrap(factory);
        }
        (<any>factory).selector = originalSelector;
      } else {
        console.log("One only element, bootstraping it", factory.selector);
        appRef.bootstrap(factory);
      }
    }
  }
}
@chrisse27

This comment has been minimized.

Show comment
Hide comment
@chrisse27

chrisse27 Feb 15, 2018

Contributor

@jmbarbier Don't use the hack anymore. See #15668

Contributor

chrisse27 commented Feb 15, 2018

@jmbarbier Don't use the hack anymore. See #15668

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment