2

I'm building a component with multiple values, but instead of copying value itself I copy the whole input component:

<ng-content>
  <!-- here will be an input component of any type -->
</ng-content>

<button (click)="add()">Add</button>

<div #Container>
</div>

I refence to a @ContentChild and to a @ViewChild('Container') - that's not a big deal. However, I couldn't find a smooth way to move input component represented by ContentChild to a container, represented by ViewChild by clicking the Add button.

Here is a workaround what I have now. I reference both of them by ElementRef and then move down to pure DOM manuipulation:

const elContainer: HTMLElement = this._container.nativeElement;
elContainer.appendChild(this._input.nativeElement);

I wonder if there is a better way.

UPDATE:

@Sreekumar suggested to use CDK Portals, which purpose looks common, but that's have an incredible overhead, look (from an example), and again it uses DOM:

constructor(
 private componentFactoryResolver: ComponentFactoryResolver,
 private injector: Injector,
 private appRef: ApplicationRef,
) { } 

ngOnInit() {

    // Create a portalHost from a DOM element
    this.portalHost = new DomPortalHost(
      document.querySelector('#pageHeader'),
      this.componentFactoryResolver,
      this.appRef,
      this.injector
    );

    // Locate the component factory for the HeaderComponent
    this.portal = new ComponentPortal(HeaderComponent);

    // Attach portal to host
    this.portalHost.attach(this.portal);
}
Vladimir
  • 361
  • 1
  • 3
  • 14
  • 1
    try CDK Porals : https://material.angular.io/cdk/portal/overview – Sreekumar P Feb 11 '18 at 11:45
  • @Sreekumar, that's look like a part of Material, is there any theme-agnostic way? – Vladimir Feb 11 '18 at 12:12
  • 1
    @Vladimir It's not a part of Material. It's a generic purpose library that is maintained by Material team. – Estus Flask Feb 11 '18 at 12:25
  • Yes, thank you after a deeper learning indeed that's a good way. However that's only relevant to my needs for a quite big UI library, If I would run a small project, adding this library would be too much I think, so I hope there is a way in @angular/core to do that. – Vladimir Feb 11 '18 at 14:01

1 Answers1

2

Here is my solution which doesn't use DOM.

The example is creating multiple dynamic components and moves them from one container to another.

Here is what we should have to do that:

  1. Container A type of ViewContainerRef;

  2. Container B type of ViewContainerRef;

  3. Component type of ComponentRef; That's very important as I find out, as ViewContainerRef methods like insert uses ViewRef which you're unable to get from any other ref. type, so you're unable to use @ViewChild or @ContentChild and then move it. So here is the rule: The component you want to move should be dynamic created, as only this way you can get ComonentRef! You can find some explanation also here https://stackoverflow.com/a/45339558/3925894

And here are the steps:

Variables.

private _componentFactory: ComponentFactory<SomeComponent>;

private _componentRef: ComponentRef<SomeComponent>;

@ViewChild('InputContainer', {read: ViewContainerRef})
private _inputContainer: ViewContainerRef;

@ViewChild('ItemsContainer', {read: ViewContainerRef})
private _itemsContainer: ViewContainerRef;

Initialization.

public ngAfterViewInit(): void {

  this._componentFactory =
    this._componentFactoryResolver.resolveComponentFactory(SomeComponent);

  this._componentRef =
    this._inputContainer.createComponent(this._componentFactory);

  this._componentRef.changeDetectorRef.detectChanges();

}

Dynamically creating and moving component

private _add() {

  const index = this._inputContainer.indexOf(this._componentRef.hostView);

  this._inputContainer.detach(index);

  this._itemsContainer.insert(this._componentRef.hostView);

  this._componentRef =
    this._inputContainer.createComponent(this._componentFactory);

  this._componentRef.changeDetectorRef.detectChanges();

}

See plunkr http://plnkr.co/edit/2eHE2S8hTKcQ8i7TidBF?p=preview

That's works for me. No DOM as you can see.

Vladimir
  • 361
  • 1
  • 3
  • 14