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

Removal of compileComponentAsync in RC6 #11277

Closed
simon-sharp opened this issue Sep 2, 2016 · 7 comments
Closed

Removal of compileComponentAsync in RC6 #11277

simon-sharp opened this issue Sep 2, 2016 · 7 comments

Comments

@simon-sharp
Copy link

simon-sharp commented Sep 2, 2016

I'm submitting a ... (check one with "x")

[ ] bug report => search github for a similar issue or PR before submitting
[x] feature request
[ ] support request => Please do not submit support request here, instead see https://github.com/angular/angular/blob/master/CONTRIBUTING.md#question

Current behavior
With RC6 the method compiler.compileComponentAsync(...) has been replaced with compiler.compileModuleAsync(...). That allows me to render Modules dynamically.

Problem is that if I want to render a single component, I would have to create a module class for each component that should be available dynamically.

Im not saying that rendering modules isn't a good thing. I would just prefer if it would also be possible to render components directly without the additional overhead.

Expected/desired behavior
Would it be possible to bring the old behaviour back?

Reproduction of the problem
what I am currently doing:

import {
  Component, Input, ViewChild, ViewContainerRef, Compiler, OnDestroy, OnChanges,
  SimpleChange
} from '@angular/core';

@Component({
  selector: 'dynamic',
  template: `<span #dynComp></span>`
})
export class DynamicComponent implements OnDestroy, OnChanges {
  @Input() dynamicComponent: any;
  @Input() inputs: JSON;
  @Input() outputs: JSON;
  @ViewChild('dynComp', {read: ViewContainerRef}) dynComp;

  public component;

  constructor(
    public view: ViewContainerRef,
    public compiler: Compiler
  ) {}

  public ngAfterViewInit() {
    this.compiler.compileComponentAsync(this.dynamicComponent).then(
      factory => {
        this.component = this.dynComp.createComponent(factory);
        this.bindVariables();
      }
    );
  }

  public ngOnChanges(changes: {[propertyName: string]: SimpleChange}) {
    if (this.component) {
      this.bindVariables();
    }
  }

  public ngOnDestroy() {
    this.component.destroy();
  }

  public bindVariables() {
    if (this.inputs !== undefined) {
      let bindingKeys = Object.keys(this.inputs);
      for (let bindingName of bindingKeys) {
        this.component.instance[bindingName] = this.inputs[bindingName];
      }
    }

    if (this.outputs !== undefined) {
      let eventKeys = Object.keys(this.outputs);
      for (let eventName of eventKeys) {
        this.component.instance[eventName] = this.outputs[eventName];
      }
    }
  }
}

What is the expected behavior?
Make the rendering of components possible again without the overhead of writing a module for each component.

What is the motivation / use case for changing the behavior?
Im not saying that rendering modules isn't a good thing. I would just prefer if it would also be possible to render components directly without the additional overhead.

Please tell us about your environment:

  • Angular version: 2.0.0-rc.6
  • Browser: [all]
  • Language: [all]
@IgorMinar
Copy link
Contributor

There are two possible solutions to your problem:

1/ if your components are not lazily loaded, then you should list all of your dynamic components in the entryComponents list of your module and then use ComponentFactoryResolver to get hold of the factories.

@NgModule({
   ...
   entryComponents: [MyDynamicComponent]
})
class MyModule {}

// in your container component inject componentFactoryResolver and then
   ...
   let myComponentFactory = this.componentFactoryResolver.resolveComponentFactory(MyDynamicComponent);

2/ if your components are lazily loaded, since you are doing a lot of dynamic work already, you might as well create the modules dynamically.

let module = generateModule(Component)
let moduleFactory = this.compiler.compileComponentAsync(module);

More background info: We can't bring the old behavior back because compiling individual components requires the compiler to know the context in which the component should be compiled. e.g. what directives and pipes should be considered when compiling the component's template.

before we introduced (now removed) platform directives, it was possible to safely compile individual components because you had to specify all the imported directives and pipes via directives and pipes component metadata fields. People however complained that this was too verbose. So we fixed it via NgModules. But now that we have NgModules, we shouldn't have component level imports because that's just too confusing.

So while the more common use-case got much simpler, your case got more difficult because you now have to create modules for your components. I just don't see how we can satisfy both scenarios.

@doubletriplezero
Copy link

Igor,

Can you expand a bit on the second solution you propose where modules are created dynamically? I don't see any documentation on any kind of 'generateModule' method. Is this something that is currently supported in Angular 2 or are you suggesting it as a hypothetical method one might implement to get around the issue? I imagine the general idea would be to create a basic NgModule scaffolding around a component at runtime to enable dynamic compilation. Is this what you had in mind or have I misunderstood?

Thanks

@zoechi
Copy link
Contributor

zoechi commented Sep 6, 2016

I assume it's something like a component can be created dynamically as shown in #7596 (comment)

@rolandjitsu
Copy link

@IgorMinar this.compiler.compileComponentAsync(module); has been removed in RC.6. It seems to me that creating dynamic components is close to impossible.

I've tried the following approach: how-can-i-use-create-dynamic-template-to-compile-component-with-angular2-rc6. And it seems to work ok, until I had a template that uses routerLink within the dynamic component.

Now I had to provide the dynamically created NgModule with the router module, but by using imports: [RouterModule] inside the dynamic module I only managed to make the router links render properly, but not trigger the navigation.

The problem is that even if I click on a route, it only changes the URL, but the view does not get updated.

Creating a plunk with this issue is not trivial. But I must ask, is there any easy way of having a component with a dynamic template? Or is there away to change the HTML of a template and recompile it or refresh it?

I tried [innerHtml], but that does not compile the contents.

@simon-sharp
Copy link
Author

simon-sharp commented Sep 13, 2016

Hi Igor

Sorry for the late answer. I was on holiday. I have played around with the new Settings you have proposed and could not get it to work:

about 1/: if I run
this.componentFactoryResolver.resolveComponentFactory(MyDynamicComponent);

it tells me that there is no componentFactory associated with MyDynamicComponent. I couldn't find a way to write or generate such a factory.

about 2/: where does generateModule come from? I also found no reference to anything like that.

about my original way that I wanted to avoid: When I am trying to write a module for every Component I get it to work.

This time I have to hand over the Module and the Component. Which is too much overhead.
Is there a possibility to get the currently used Module from the context and just use this one? That would also avoid that I have to write a new Module for each dynamically used Component. Plus if I have to generate a component anyway and can not use the bootstrap one, all the Information is already available anyway in the one that already exists.

@Component({
  moduleId: module.id,
  selector: 'dynamic',
  template: `<span #dynComp></span>`,
})
export class DynamicComponent implements OnDestroy, OnChanges {
  @Input() dynamicModule: any;
  @Input() dynamicComponent: any;
  @Input() inputs: JSON;
  @Input() outputs: JSON;
  @ViewChild('dynComp', {read: ViewContainerRef}) dynComp;

  public component: any = new EmptyComponent();

  constructor(
    public view: ViewContainerRef,
    public compiler: Compiler,
    public componentFactoryResolver: ComponentFactoryResolver,
    public injector: Injector
  ) {}

  public ngAfterViewInit() {
    this.compiler.compileModuleAsync(this.dynamicModule).then(
      factory => {
        let moduleInjector: any = factory.create(this.injector);
        this.component = this.dynComp.createComponent(moduleInjector.componentFactoryResolver.resolveComponentFactory(this.dynamicComponent));
        this.bindVariables();
      }
    );
  }

  public ngOnChanges(changes: {[propertyName: string]: SimpleChange}) {
    // if (this.component) {
     //  this.bindVariables();
    // }
  }

  public ngOnDestroy() {
    if (typeof this.component.destroy === 'function') {
      this.component.destroy();
    } else {
      this.component = null;
    }
  }

  public bindVariables() {
    if (this.inputs !== undefined) {
      let bindingKeys = Object.keys(this.inputs);
      for (let bindingName of bindingKeys) {
        this.component.instance[bindingName] = this.inputs[bindingName];
      }
    }

    if (this.outputs !== undefined) {
      let eventKeys = Object.keys(this.outputs);
      for (let eventName of eventKeys) {
        this.component.instance[eventName] = this.outputs[eventName];
      }
    }
  }
}

@simon-sharp
Copy link
Author

Created a new Feature Request:
#11626

@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 9, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants