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

There is no way to access inputs or outputs of Components created by NgComponentOutlet #15360

Open
mcweiss opened this issue Mar 21, 2017 · 64 comments
Assignees
Labels
area: common Issues related to APIs in the @angular/common package feature: under consideration Feature request for which voting has completed and the request is now under consideration feature Issue that requests a new feature
Milestone

Comments

@mcweiss
Copy link

mcweiss commented Mar 21, 2017

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
There is no way to set @inputs or subscribe to @output events of the Component created by NgComponentOutlet.

Expected behavior
I expect some to set values and to subscribe events.

What is the motivation / use case for changing the behavior?
At least, we should have the same level of functionality as ViewContainerRef.createComponent() which is roughly the programatic equivalent of NgComponentOutlet. That function returns a ComponentRef<any> which provides an instance property that allows direct access to the class instance of the Component. The original feature request for NgComponentOutlet defined an event on the directive that would fire and pass this ComponentRef object after creation. This did not make it into the current implementation and I see no other alternatives to access inputs/outputs.

Supporting only Components with no inputs or outputs severely limits the usefulness of NgComponentOutlet.

  • Angular version: 4.0.0-rc.1
@DzmitryShylovich
Copy link
Contributor

We can add events like created/destroyed similar to the router's activated/deactivated. It won't work with * syntax (#12121) but I think it's still better than nothing.

DzmitryShylovich added a commit to DzmitryShylovich/angular that referenced this issue Mar 21, 2017
DzmitryShylovich added a commit to DzmitryShylovich/angular that referenced this issue Mar 21, 2017
DzmitryShylovich added a commit to DzmitryShylovich/angular that referenced this issue Mar 21, 2017
@IgorMinar IgorMinar added area: common Issues related to APIs in the @angular/common package feature Issue that requests a new feature labels Mar 22, 2017
@matheo
Copy link

matheo commented Mar 28, 2017

Then there's no way to get the ComponentRef instance?
ngComponentOutlet will be limited then to dumb components, still cool hehe
Thanks for the great work!

@DzmitryShylovich
Copy link
Contributor

@matheo why do you need a ComponentRef?

@matheo
Copy link

matheo commented Mar 29, 2017

Following the Dynamic Component Loader documentation, I would like to use the ComponentRef instance to pass input variables to my dynamic components, as they do there:

(<AdComponent>componentRef.instance).data = adItem.data;

@DzmitryShylovich
Copy link
Contributor

@matheo create event will contain a component instance https://github.com/angular/angular/pull/15362/files#diff-8f1a604d8ceede1c4382f059e94239c9R110

@matheo
Copy link

matheo commented Mar 29, 2017

Sweet!! thanks a ton! :)

@kaylumah
Copy link

When would this feature be ready for release, could really use something like this :)

@DzmitryShylovich
Copy link
Contributor

If you need this feature asap you can copy paste the directive implementation until it will be merged.

@alsoLut
Copy link

alsoLut commented Apr 2, 2017

a temporary solution is to pass the data via a simple rxjs observable

@yjaaidi
Copy link
Contributor

yjaaidi commented Apr 5, 2017

Hi!

I think we really need a way to control "inputs" and "outputs". Otherwise, if we grab the reference to the component, it will just get hacky:

  • You'll have to reset the inputs manually instead of letting the change detector do that for you.
  • It will get complicated as soon as you'll have multiple dynamic components like in an "ngFor" with maybe different types of components. It will soon need more code than native JS.

@mlc-mlapis
Copy link
Contributor

But it is the same situation as in the case of dynamically created component instances, right?

@vicb
Copy link
Contributor

vicb commented Apr 6, 2017

/ref #12121

@broodjetom
Copy link

broodjetom commented Jun 6, 2017

So someone found a way to have @Output() in a dynamically loaded component with ngComponentOutlet. Can someone please give an example on how to have an simple variable passed to the component? I was trying it with provider, since the documentation says to do it that way, but this doesn't seem to work, another issue already adresses this, that how I got here. This is what I have right now.

<ng-container *ngComponentOutlet="getComponent(item.tag); providers: "myProvider"></ng-container>

This is inside a *ngFor, where every item has a tag which will be factored to a component which is loaded in the container. But I need data inside that container. It is only one object that I need to send, but I don't know how to. myProvider is an object with two properties: nothing special.

@destus90
Copy link
Contributor

@DzmitryShylovich hello. Do you have any news when PR will be merged ?

@rendmath
Copy link

Being able to select a component to instantiate based on a runtime decision is an obvious requirement for any user interface framework. Not being able to bind inputs and outputs into that component makes the feature pretty much useless at the moment.

@EricABC
Copy link

EricABC commented Jul 10, 2017

FYI I exposed @sharpangles/angular-dynamic since some others were asking me for it. It exposes componentRef, separates concerns of projecting state vs dynamic loading, pre-compiles the state mapping per component type, supports child components that have no knowledge of the library, doesn't require a type reference (lazy loading), etc... There's also some stuff to help map dynamic form hierarchies I use the npm packages directly in a large enterprise system, so it gets maintained. I didn't add Output yet, but would be pretty simple to add to this implementation (if there is demand for it).

@titusfx
Copy link

titusfx commented Jul 26, 2017

@DzmitryShylovich

create event will contain a component instance https://github.com/angular/angular/pull/15362/files#diff-8f1a604d8ceede1c4382f059e94239c9R110

Should be in doc

@emilio-martinez
Copy link
Contributor

@titusfx this hasn't been merged yet

@geratarra
Copy link

@DzmitryShylovich

If you need this feature asap you can copy paste the directive implementation until it will be merged.

How exactly could I do that ? I mean, copy the code of ng_component_outlet.ts from https://github.com/angular/angular/pull/15362/files and paste it on my ng_component_outlet.ts file ? I suppose that isn't enough right ?

@instantaphex
Copy link

@geratarra

How exactly could I do that ? I mean, copy the code of ng_component_outlet.ts from https://github.com/angular/angular/pull/15362/files and paste it on my ng_component_outlet.ts file ? I suppose that isn't enough right ?

You would really be better off copying and pasting the code into your own directive separate from the angular source. Call it something different and declare it in your local project. Once it has been merged , you can go change it to use the directive supplied by angular.

@elclanrs
Copy link

elclanrs commented Aug 16, 2017

We ended up using this library https://github.com/gund/ng-dynamic-component, and it worked very well for our use case.

@destus90
Copy link
Contributor

@waterplea
You can use portals package from @angular/cdk . Just use the cdkPortalOutlet directive and listen to the attached event.

@waterplea
Copy link
Contributor

waterplea commented Feb 13, 2020

@destus90 Thank you, but it's not what I need. I've made my own similar tool:
https://github.com/TinkoffCreditSystems/ng-polymorpheus
It is light weight (1KB gzip) and dependency free, on top of portals it also allows using strings and handler functions, getting context as input and returning string to be used as content. I find this solution extremely helpful as it allows me to think in terms of just abstract content "whatever" (template, component, string, number, function) and not limiting my components to particular customization API. It is so small because I just delegate all the work to the proper Angular built-in instrument and it works with Angular 4+. The thing is, I do not want to re-render components if context stays with the same "shape", kinda like ngTemplateOutlet does not re-instantiate template in that case. What I need is to be able to call outlet.componentRef.injector.get(ChangeDetectorRef).markForCheck(). Getting componentRef as public field from NgComponentOutlet would allow us to access it with @ViewChild and also work with inputs and outputs. There's no need for (created) or (destroyed) events on the outlet, as it does not create or destroy component on its own, it is always a result of user action and passing component to it, kinda like we do not have such events on ngIf.

@destus90
Copy link
Contributor

@waterplea
I don't think something will be changed with NgComponentOutlet directive because this issue was opened some years ago and nothing has been changed.
I've heard about a new function called renderComponent. It is not stable, but you might want to research it a bit more.

@waterplea
Copy link
Contributor

Well, the goal is minimal interference from my side. Of course there are tons of workarounds. Before creating the library I linked above we've had it in the form of structural directive and did all this imperative work ourselves, but that means we are responsible for the correctness of this whole code block. Passing it on to Angular to do all the magic means not only a smaller bundle but also less responsibility for us.

@lonerzzz
Copy link

lonerzzz commented Jun 20, 2020

For those who end up here while trying to find an easier way to use dynamic components, I found this library which provides the @Input/@output functionality currently missing from Angular core: https://www.npmjs.com/package/@ngxd/core

To Google staff, Angular would be well served by directly incorporating this functionality as well as providing the ability to access these dynamic nodes with @ViewChild and @ViewChildren.

@angular-robot angular-robot bot added the feature: under consideration Feature request for which voting has completed and the request is now under consideration label Jun 5, 2021
@zhuchenghuFF
Copy link

Oh, When I see these comments above,I'm in a terrible mood, Such a good function is limited by @input&@output;Worse still this issue was opened some years ago but still open! I look forward to solving this problem quickly.

@petebacondarwin petebacondarwin added this to Inbox in Feature Requests via automation Oct 15, 2021
@petebacondarwin petebacondarwin moved this from Inbox to Needs Project Proposal in Feature Requests Oct 15, 2021
@petebacondarwin petebacondarwin self-assigned this Oct 15, 2021
CSantosM added a commit to OpenVidu/openvidu that referenced this issue Feb 1, 2022
- For loading components with inputs/outputs directives, has been necessary to use the 'ng-dynamic-component' library because of unsupported on the official Angular issue  angular/angular#15360
- Allowed the dynamic components load
- Grouped panels into panel component
@netikras
Copy link

I'm settling for a custom directive that seems to be working for me. Hopefully, it'll help someone else too

import {
  AfterViewInit,
  ComponentFactoryResolver,
  ComponentRef,
  Directive,
  ElementRef,
  EventEmitter,
  Injector,
  Input,
  OnDestroy,
  Output,
  Type,
  ViewContainerRef
} from '@angular/core';

/*
  USAGE:

    <div myComponentOutlet
           [component]="inner.component"
           [injector]="inner.injector"
           (create)="componentCreated($event)"></div>
 */

@Directive({
  selector: '[myComponentOutlet]',
})
export class MyComponentOutletDirective<T> implements AfterViewInit, OnDestroy {

  @Input() component: Type<T>;
  @Input() injector: Injector;
  @Output() create = new EventEmitter<ComponentRef<T>>();
  @Output() destroy = new EventEmitter<ComponentRef<T>>();

  private componentRef: ComponentRef<T>;

  constructor(
    private viewContainerRef: ViewContainerRef,
    private resolver: ComponentFactoryResolver,
    private elRef: ElementRef,
    private globalInjector: Injector
  ) {
    this.injector = globalInjector;
  }

  ngAfterViewInit() {
    const injector = this.injector || this.globalInjector;
    const factory = this.resolver.resolveComponentFactory(this.component);
    this.componentRef = this.viewContainerRef.createComponent(factory, 0, injector);

    this.elRef.nativeElement.appendChild(this.componentRef.location.nativeElement);
    this.create?.emit(this.componentRef);
  }

  ngOnDestroy(): void {
    this.destroy?.emit(this.componentRef);
  }
}

Usage:

<div myComponentOutlet
           [component]="inner.component"
           [injector]="inner.injector"
           (create)="componentCreated($event)"></div>

Borrowed the idea from https://stackoverflow.com/questions/42598169/add-a-component-dynamically-to-a-child-element-using-a-directive

Still hoping the idea will make its way to the Angular's src

@pkozlowski-opensource
Copy link
Member

pkozlowski-opensource commented Jul 6, 2022

The ability to interact with inputs / outputs of the dynamically created component is obviously lacking in the current implementation of the ngComponentOutlet and there are reasons why we didn't add it so far:

So at this point we can consider the inputs / outputs issues to be "solved" (even if more work is needed). This leaves us with the API design question.

As I've mentioned we are not excited about the proposed <ng-container *ngComponentOutlet="component; inputs:{name: 'foo'}; outputs:{select: onSelect}"> API. The main problem is that it represents inputs and outputs as keys on an object literal. This is not what we do in other framework APIs and it pretty much bypasses existing change detection mechanisms. The other problem is that we don't support content projection.

An alternative approach would be to deprecate the ngComponentOutlet directive and have a dedicated tag in Angular (working name: ng-component), that could be used like:

<ng-component [this]="ComponentType" input1={{exp}} input2="static" (event)="doSth()">
    <div>
        projectable nodes
    </div>
</ng-component>

Advantages:

  • dynamically creating a component becomes very similar to creating a regular component;
  • interacting with inputs / outputs is identical to regular components;
  • content projection can be supported.

Introducing such mechanism would require a bit more work but would offer better ergonomics and a more complete functionality.

@devoto13
Copy link
Contributor

devoto13 commented Jul 6, 2022

@pkozlowski-opensource Is there a way to make input1, input2, and event statically analyzable based on the ComponentType assuming that it can be a pretty dynamic value? The API introduced in #46641 is not statically typed, but that would probably be an expectation with the proposed ng-component API because that's how all other components work: an error for the unexpected input/output.

@pkozlowski-opensource
Copy link
Member

@devoto13 having type-safety would require:

  • restricting component type to some "base" type;
  • making sure that we can correctly resolve types for mapped inputs / outputs.

It seems to me like we are looking at a tread-off here (dynamic / flexible vs. type safe / statically analyzable).

@amitbeck
Copy link

amitbeck commented Jul 6, 2022

@pkozlowski-opensource the <ng-component> approach sounds promising! 🚀

@sdedieu
Copy link
Contributor

sdedieu commented Jul 6, 2022

I really like the idea 🙂.
To me [this], doesn't sound quite well.
Maybe [component] remains a good name for the component type input.

@pkozlowski-opensource
Copy link
Member

I've been discussing ideas around inputs / outputs of dynamically created components with @JoostK and we see 2 main options:

Option 1: extend the existing ngComponentOutlet directive and add inputs / outputs bindings

@Component({
    template: `
       <ng-template 
         [ngComponentOutlet]="ComponentType"
         [inputs]="{input1: exp, input2: 'static'}"
         [outputs]="{event: handleEventFn}"
      ></ng-template>`
})
export class ExampleCmp {
   exp = ...;
   
   handleEventFn($event) {
       ...
   }
}

Pros:

  • we can just extend the existing directive

Cons:

  • we are inventing special syntax for binding inputs / handling events;
  • the special syntax would have to be manually diff-ed (dirty-checked) in the ngComponentOutlet directive;
  • event handling is not ergonomics (can't inline event handler in a template, the handleEventFn looses this);
  • no support for content projection.

Option 2: introduce the <ng-component> tag

<ng-component [component]="componentTypeExp" input1={{exp}} input2="static" (event)="doSth()">
    <div>
        projectable nodes
    </div>
</ng-component>

Pros:

  • standard Angular syntax for input bindings / output events handling;
  • content projection support;

Cons:

  • all the componentTypeExp component types need to support the same set of inputs / outputs;
  • introduces yet another way of dynamically creating components / would require deprecation of ngComponentOutlet

@waterplea
Copy link
Contributor

Option 2 would dynamically change tag name in DOM?

@mlc-mlapis
Copy link
Contributor

@pkozlowski-opensource] Option 2 looks really like a step for the future, and both cons are logical.

Btw, what about the relation to the planned feature of Support adding directives to host elements? Are there inner reasons why Option 2 should be preferred?

@pkozlowski-opensource
Copy link
Member

Option 2 would dynamically change tag name in DOM?

@waterplea Yes and no :-) The <ng-component> would be, most probably, represented by a comment node (similar to what happens with <ng-container> and each component would get its host element created based on a selector (same thing as with ngComponentOutlet).

Btw, what about the relation to the planned feature of Support adding directives to host elements? Are there inner reasons why Option 2 should be preferred?

I don't think those proposals are related, at least I can't immediately see any relation.

@eneajaho
Copy link
Contributor

introduces yet another way of dynamically creating components / would require deprecation of ngComponentOutlet

If ngComponentOutlet gets deprecated in favor of ng-component, are we going to get also the possibility to pass in an injector or contentNodes to the component, just like we do in ngComponentOutlet?

I ask for this, because most of the time, when using ngComponentOutlet, the way we pass data in the components, have been passing down an injector that includes injections tokens with data for the components.

@Jrubzjeknf
Copy link

Jrubzjeknf commented Aug 12, 2022

The option with <ng-component ... [intpu1]="{{ arg }}" does not help with the use case where the component that is loading the dynamic component doesn't know what kind of component it is. This issue shouldn't just solve binding input on a dynamic component, it should also solve binding dynamic inputs on a dynamic component.

This is what ng-dynamic-component solves. At our company, we define replaceable components in the following structure:

{
  component: ComponentType
  inputs: {
    myInput: myVar
  },
  outputs: {
    onOutput: () => onMyOutput()
  }
}

This is entirely configurable in Typescript, you don't need and shouldn't need a template for this. We can bind extra inputs or outputs by using the respective properties, or replace the component with a custom compatible one. The advantage of this is that any current functionality, that uses certain inputs and outputs, keeps working on the custom component. You don't need to know how the original was bound, because it will just keep working the same way after replacing the component.

This is what is solved in that library and why it is very powerful. I reckon support for this should be part of the solution of this issue.

@RezaP-huma
Copy link

As of Version 16.2.0 we have the ability to set the Inputs like this :
<ng-container *ngComponentOutlet="component; inputs:{name: 'foo'}>
My question is, is it possible to add the same support for the Outputs as well ?
<ng-container *ngComponentOutlet="component; inputs:{name: 'foo'}; outputs:{select: onSelect}">

@mattiLeBlanc
Copy link

One stop gap solution is put a rxjs Subject into your inputs and then push a value onto this subject.

<ng-container *ngComponentOutlet="drawerComponent; inputs: drawerComponentInputs"/>
const output = New Subject<any>();
this.drawerComponentInputs = {
  foo: 'hi',
  output,
}

 output.subscribe(val => {
            console.log(val);
          })

Then in the component.

@Input() output: Subject<any>;

someFunction() {
   this.output.next('some return value');
}

Make sure to unsubscribe from your subject.

@JeanRemiDelteil
Copy link

@pkozlowski-opensource
Regarding the Option 2

Cons:

all the componentTypeExp component types need to support the same set of inputs / outputs;

Indeed. However, in most cases if we are using dynamic components, it's to insert component that have the same interface (ie: input / output).

One of our use-case is to insert filters.
The user can choose which filter to apply, and the filter type depends on the data subject.

=> All filters takes the same inputs (data types, etc..), the specificities (if there are some) are taken care in the input type interface.
=> All filters must send the same event.

To address some corner cases, we could pass output listener to non-existing outputs (it will just do nothing).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area: common Issues related to APIs in the @angular/common package feature: under consideration Feature request for which voting has completed and the request is now under consideration feature Issue that requests a new feature
Projects
No open projects
Feature Requests
Needs Project Proposal
Development

Successfully merging a pull request may close this issue.