Angular2: Inserire un componente dinamico come figlio di un contenitore nel DOM

C’è un modo per inserire dinamicamente un componente come un bambino (non un fratello /sorella) di un DOM tag Angolare 2?

Ci sono un sacco di esempi in giro c’è di inserire un componente dinamico come un fratello di un dato ViewContainerRef‘s tag, come (come di RC3):

@Component({
  selector: '...',
  template: '<div #placeholder></div>'
})
export class SomeComponent {
  @ViewChild('placeholder', {read: ViewContainerRef}) placeholder;

  constructor(private componentResolver: ComponentResolver) {}

  ngAfterViewInit() {
    this.componentResolver.resolveComponent(MyDynamicComponent).then((factory) => {
        this.componentRef = this.placeholder.createComponent(factory);
    });
  }
}

Ma questo genera un DOM simili a:

<div></div>
<my-dynamic-component></my-dynamic-component>

Risultato atteso:

<div>
    <my-dynamic-component></my-dynamic-component>
</div>

Utilizzando il SomeComponent‘s ViewContainerRef ha lo stesso risultato, è ancora di inserire la componente generato come un fratello, non un bambino. Mi sarebbe andato bene con una soluzione in cui il modello è vuoto e componenti dinamici sono inseriti nel modello (all’interno del componente selettore di tag).

Struttura del DOM è molto importante quando si utilizza librerie come ng2-dragula trascinare da un elenco di componenti dinamici e beneficiare il modello si aggiorna. Extra div è nell’elenco di elementi trascinabili, ma al di fuori del modello, rompendo il drag & drop logica.

Alcuni dicono che non è possibile (c.f. questo commento), ma suona come una sorprendente limitazione.

  • Vedere github.com/angular/angular/issues/9035
  • Grazie per la rapida risposta e link utili! @pkozlowski ha dato la risposta alla mia domanda su questa discussione: utilizzare <template #placeholder></template> invece di <div #placeholder></div>.
  • Avete una soluzione che funziona sulla direttiva? dal momento che questo è per il componente
InformationsquelleAutor Antoine | 2016-06-29

 

5 Replies
  1. 48

    TL;DR: sostituire <div #placeholder></div> con <div><ng-template #placeholder></ng-template></div> ad inserire all’interno del div.

    Qui è un lavoro stackblitz esempio (Angolare 6), e il relativo codice:

    @Component({
      selector: 'my-app',
      template: `<div><ng-template #container></ng-template></div>`
    })
    export class AppComponent implements OnInit {
    
        @ViewChild('container', {read: ViewContainerRef}) viewContainer: ViewContainerRef;
    
        constructor(private compiler: Compiler) {}
    
        ngOnInit() {
          this.createComponentFactory(MyDynamicComponent).then(
            (factory: ComponentFactory<MyDynamicComponent>) => this.viewContainer.createComponent(factory),
            (err: any) => console.error(err));
        }
    
        private createComponentFactory(/*...*/) {/*...*/}
    
    }
    

    Sembra <ng-container #placeholder></ng-container> è anche di lavoro (sostituire ng-template da ng-container). Mi piace questo approccio, perché <ng-container> è chiaramente indirizzato a questo caso d’uso (un contenitore che non aggiungere un tag) e può essere utilizzato in altre situazioni come NgIf senza avvolgere in un vero e proprio tag.


    PS: @GünterZöchbauer mi ha indirizzato per la discussione giusta in un commento, e ho finalmente risposto alla mia domanda.


    Modifica [2018-05-30]: Aggiornato alla stackblitz link per avere un lavoro, l’up-to-date esempio.

    • Il segnaposto è noto, non uno che è dinamico. Potrebbe, è possibile creare il ViewChild in modo dinamico.?
    • scusate per il ritardo. Credo che sia possibile ottenere una completamente dinamico ViewChild, ma non so come fare. Il ViewChild ha diversi modi per identificare il componente a destinazione però. Potrebbe essere una questione a parte per chiedere 🙂
    • Ma la posizione di componente dinamica potrebbe essere <il mio-app><div><ng-modello #contenitore></ng-modello></div><!– La Componente Dinamica della Posizione–></my-app>. Potrebbe ng-modello di scomparire?
    • La ng-modello/ng-componente effettivamente scompare nel rendering dell’HTML. È una specie di tag a punto la posizione in cui si desidera inserire il vostro componente. È possibile controllare l’esempio aggiornato e controllare con il browser dev tools per confermare che.
    • In realtà, occupava uno spazio per essa, ma non di rendering.
    • Nel link nella mia risposta (stackblitz), non ottengo lo stesso risultato di quello che tu descrivi. La componente modello è <h1><ng-template #container></ng-template></h1> e rende <h1><!----><my-dynamic>I am inserted dynamically!</my-dynamic></h1>. La componente dinamica è a posto mi sarei aspettato di essere, non al di fuori del <h1> e <ng-template> scomparsa, sostituita da un commento vuoto, credo). C’è qualcosa di sbagliato?
    • Sì. L’esempio in stackblitz opere. Ho scoperto che sto usando angolare 5. Credo che questo potrebbe essere la causa principale.
    • Questo approccio funziona in quanto Angolare 2 (o almeno 4 con la stessa sintassi), quindi ci potrebbe essere un altro motivo per cui si ottiene un comportamento diverso. Forse questo potrebbe essere un buon separati domanda 🙂
    • Buona domanda e risposta eccellente. Tuttavia, c’è un modo per passare i parametri alla componente dinamico? Solo per inizializzare e identificare il componente, il resto può essere fatto tramite un servizio comune…
    • L’unico approccio che conosco è quello di ottenere l’istanza del componente alla fine e inserire manualmente i valori come myCompInstance.foo = 'Hello world!'. Btw credo Angolare sta facendo qualcosa di simile a questo (perché @Input non sono accessibili nel costruttore, solo da NgOnInit gancio). Ho aggiornato il stackblitz esempio per impostare anche una componente di proprietà.
    • Impressionante. Mi piace la sua semplicità. Vi ringrazio molto

  2. 7

    Ero alla ricerca di una soluzione per lo stesso problema e approvato risposta non ha funzionato per me.
    Ma ho trovato di meglio e molto di soluzione logica come aggiungere dinamicamente creato componente, come il bambino di corrente elemento host.

    L’idea è di utilizzare l’elemento di riferimento della componente appena creato e aggiungerla all’elemento corrente rif utilizzando il servizio di rendering.
    Siamo in grado di ottenere il componente in oggetto tramite l’iniettore di proprietà.

    Ecco il codice:

    @Directive({
        selector: '[mydirective]'
    })
    export class MyDirectiveDirective {
        constructor(
            private cfResolver: ComponentFactoryResolver,
            public vcRef: ViewContainerRef,
            private renderer: Renderer2
        ) {}
    
        public appendComponent() {
            const factory = 
            this.cfResolver.resolveComponentFactory(MyDynamicComponent);
            const componentRef = this.vcRef.createComponent(factory);
            this.renderer.appendChild(
               this.vcRef.element.nativeElement,
               componentRef.injector.get(MyDynamicComponent).elRef.nativeElement
            );
        }
    }
    
    • Per favore potete aggiungere un stackblitz? Io non riesco a farlo funzionare?
  3. 4

    Mio modo sporco, basta salvare il riferimento del creato dinamicamente componente (di pari livello) e spostare con vaniglia JS:

      constructor(
        private el: ElementRef,
        private viewContainerRef: ViewContainerRef,
        private componentFactoryResolver: ComponentFactoryResolver,
      ) {}
    
      ngOnInit() {
        const componentFactory = this.componentFactoryResolver.resolveComponentFactory(
          MyDynamicComponent,
        );
        const componentRef = this.viewContainerRef.createComponent(componentFactory);
        this.el.nativeElement.appendChild(componentRef.location.nativeElement);
      }
    
    • Questo rompe se il tuo host ha un *ngIf o un’altra direttiva che lo trasformano in un modello.
  4. 4

    La mia Soluzione sarebbe abbastanza simile a @Bohdan Khodakivskyi. Ma ho provato a Renderer2.

      constructor(
        private el: ElementRef,
        private viewContainerRef: ViewContainerRef,
        private componentFactoryResolver: ComponentFactoryResolver,
        private render: Renderer2
      ) {}
    
      ngOnInit() {
        const componentFactory = this.componentFactoryResolver.resolveComponentFactory(
          MyDynamicComponent,
        );
        const componentRef = this.viewContainerRef.createComponent(componentFactory);
        this.render.appendChild(this.el.nativeElement, componentRef.location.nativeElement)
      }
    
    • Regolazione DOM struttura con il renderer potrebbe fare si esegue in problemi con la modifica interna di rilevamento, vedere questo post. Non so se questo possa causare problemi in questo caso particolare, però…
    • Questo rompe se il tuo host ha un *ngIf o un’altra direttiva che lo trasformano in un modello.
  5. 2

    Più facile approache è ora disponibile con Portale disponibile in @angolare/cdk.

    npm i @angolare/cdk

    app.modulo.ts:

    import { PortalModule } from '@angular/cdk/portal';
    
    @NgModule({
      imports: [PortalModule],
      entryComponents: [MyDynamicComponent]
    })
    

    SomeComponent:

    @Component({
      selector: 'some-component',
      template: `<ng-template [cdkPortalOutlet]="myPortal"></ng-template>`
    })
    export class SomeComponent {
      ...
      this.myPortal = new ComponentPortal(MyDynamicComponent);
    }
    

Lascia un commento