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
Comments
Blocked on #6270 as it touches the same code. |
(only the first part of #6270) |
+1, Thanks Tobias for the hack session today. |
Thanks for the help with a workaround on this Tobias! |
A workaround can be found here: http://plnkr.co/edit/O0KRZHUz5jdW40mY1EgA?p=preview |
@tbosch is the plunkr saved correctly? inside it looks very basic and doesn't have multiple instances of the app. |
dup of #915 ? |
@episodeyang here's a slightly expanded example with multiple instances: http://plnkr.co/edit/iMZH4O47GU4LsqSq9yvL?p=preview. |
|
I manually readded it to /*
* 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. |
Got it now. The key was to change 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 |
@tbosch how to get this to work in rc.1? |
In RC1 this is partially supported as This was changed a lot on the current master but this solution (or rather a hack) overrides the selector member of the 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')); |
I can confirm that this works. Thanks a lot for this intermediate solution! |
And with the current rc it seems to be broken again.
|
As of rc.2 you can do this instead let app = ReflectiveInjector 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 |
Thanks, seems promising. But this causes a different error now:
|
Changed it a bit. The following code works, but as you already suggested, bootstrapping via ComponentResolver now is broken.
Or am I diverging too far from the original goal, to pass a custom element selector to the bootstrap process? |
That is broken for me too. You can follow this tracking issue here #9726 |
Has any work been done on this? If not, I'd pick this up. |
@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. |
With a "hack" https://plnkr.co/edit/ANBEdnGQCaJ4NX6DUC6X?p=preview |
@DzmitryShylovich Thanks, didn't see that when browsing through the code. Btw, thanks also for the PR that you referenced above. |
@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: I've attached a simple quickstart project to demo this.
Any idea how to get around this issue? console shows that factory is actually type ComponentFactoryBoundToModule. I've tried casting |
@jonilko it's a private property, you can't access it directly. |
perfect... that works. Thank you. |
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")
Next week I should have time to create PR for this. |
Any idea when this feature might make it to release? |
Assuming that @mhevery and @IgorMinar are both at @ngconf this week, I only expect feedback on the PR by next week. |
@playground Where exactly (and why) would you like to pass in a templateUrl? |
@chrisse27
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:
|
@playground Sounds a bit like dynamic components and dynamic content projection |
@chrisse27 cool, thanks I will give that a try. |
I implemented a bootstrap hook that initializes all entry components defined on the page, but it still uses the 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 code used to work, but versions of angular (between 5.1 and 5.2) made the we have to set 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 Don't use the hack anymore. See #15668 |
This issue has been automatically locked due to inactivity. Read more about our automatic conversation locking policy. This action has been performed automatically by a bot. |
This is important if there are multiple applications on the same page...
The text was updated successfully, but these errors were encountered: