12

Let's say I have html like this:

      <div *ngIf="(isVisible | async)">
        <app-mycomponent></app-mycomponent>
      </div>
      <div *ngIf="!(isVisible | async)">
        <app-mycomponent></app-mycomponent>
      </div>

with a button that toggles isVisible. This creates a new component instance each time I toggle the visibility.

So my question is: Can I change the implementation to have same instance of AppMyComponent to be used when visibility is toggled. E.g. by having a wrapper component that adds the app-mycomponent dynamically or something.

EDIT: My real case is quite complex and even though this example does not make sense, I'm very much interested can this be done.

EDIT2: Here's stackbliz that solves my problem.

char m
  • 7,840
  • 14
  • 68
  • 117
  • 2
    If I get it correctly, you want to make your `AppMyComponent` a `singleton`? First, I don't think there is a way to make the `component class singleton`, and second why do you need this? Usually, we make a `service singleton` – C00kieMonsta Jul 05 '19 at 10:05
  • 1
    Maybe a more concrete example of what you want to do would be helpful, because in this code sample, you are displaying the same thing in each condition, so I question the need for the condition. – JeffryHouser Jul 05 '19 at 10:10
  • If you are looking to change something inside app-mycomponent when something changed in parent component, you can do it using @Input with setters - As shown here - https://angular.io/guide/component-interaction#intercept-input-property-changes-with-a-setter – yeswanth Jul 05 '19 at 10:15
  • thanks for interest. I'm not asking if it can be made singleton. i'm asking can I use the same instance by e.g. storing it in a service or whatever. and of course this does not make any sense. my real example is too complex for anybody to answer. my need is very simple: show a map component in different places depending on conditions. this is most basic example of the case even though it is not making any sense in real world. – char m Jul 05 '19 at 10:18
  • 1
    I had a similar problem, I did not want to destroy my map when I was changing route. Have a look here https://stackoverflow.com/a/49759253/1160794 – David Jul 05 '19 at 10:20
  • @David: Thanks! I'll look into this immediatelly. – char m Jul 05 '19 at 10:22
  • 1
    If you don't mind reading a little code, I'd say the implementation of `MatDialog` in Angular Material is a pretty good showcase of how to build a component in a service and then show it in different place holders around the application. – Thor Jacobsen Jul 09 '19 at 10:25
  • thanks for tip Thor! I'll try to look it up if this bounty does not result in good answer. – char m Jul 09 '19 at 10:32
  • You can use a wrapper class to use the same instance of a component like [this](https://stackblitz.com/edit/angular-9fk2nc). – Munim Munna Jul 09 '19 at 18:40
  • @charm I would rather say save your component's state and make it so lightweight that all instances of component can refer to same state and when you update state it will immediately reflected in all components. – Dipen Shah Jul 09 '19 at 19:26
  • 1
    What do you mean when you say that the stackblitz exemple does not work anymore? I just tried it and it still works – David Jul 10 '19 at 08:14
  • sorry @David. I sometimes use stackoverflow with Edge and naturally it didn't work in that. That is exactly what I needed so please write a short answer if you will and I accept it (and grant bounty). – char m Jul 10 '19 at 14:01
  • @DipenShah: that is naturally my 1st option. i use redux to maintain the state so it's easily done. anyway i need to know how this can be done so i can assess which way to go. – char m Jul 10 '19 at 14:03
  • Using [style.display]="isVisible?'none':'inline-block'" ? – Eliseo Jul 12 '19 at 07:46
  • Please remove the second edit from the question post and write an answer post – Vega Jul 14 '19 at 07:56

2 Answers2

9

This answer is based on the stackblitz example provided in this answer to a similar question I asked.

Step #1: Create a directive that you will use wherever you want to have your reusable component.

@Directive({
  selector: "[reusable-outlet]",
})
export class ReusableDirective implements OnInit {
  constructor(
    private viewContainerRef: ViewContainerRef,
    private reusableService: ReusableService
  ) {}

  public ngOnInit(): void {
    this.reusableService.attach(this.viewContainerRef);
  }
}

Step #2 Create the service that will be in charge of:

  • dynamically creating the component that will be reused
  • attaching and detaching that component to the view container of the directive created in step ~1.

Note: Knowing when to detach the component is based on router events, but it should be possible to base it on messages instead, if you need to change where your component is without having navigation changes.

@Injectable()
export class ReusableService {
  private componentRef: ComponentRef<ReusableComponent>;

  private currentViewContainerRef: ViewContainerRef;

  constructor(
    private componentFactoryResolver: ComponentFactoryResolver,
    private injector: Injector,
    private router: Router
  ) {
    const componentFactory =
      this.componentFactoryResolver.resolveComponentFactory(ReusableComponent);
    this.componentRef = componentFactory.create(injector);

    this.router.events.subscribe((event) => {
      if (event instanceof NavigationStart && this.currentViewContainerRef) {
        this.detach(this.currentViewContainerRef);
      }
    });
  }

  public attach(viewContainerRef: ViewContainerRef) {
    this.currentViewContainerRef = viewContainerRef;
    viewContainerRef.insert(this.componentRef.hostView);
  }

  public detach(viewContainerRef: ViewContainerRef) {
    viewContainerRef.detach(
      viewContainerRef.indexOf(this.componentRef.hostView)
    );
  }
}

Joshua
  • 109
  • 2
  • 6
David
  • 33,444
  • 11
  • 80
  • 118
0

1) Insert view in #dynamicComponentContainer.

2) We can keep track of component or all component in a variable or in a object and destroy them:-

3) Or, destroy previous component when loading new one in DOM by storing last reference and .destroy() them before inserting new one.

.html

 <ng-container #dynamicComponentContainer id="dynamicComponentContainer"></ng-container>

.ts

public loadComponent(cmptName){
     switch (cmptName) {
         case 'abcComponent':
             cmptName = abcComponent;
             break;
         case 'cdeComponent':
             cmptName = cdeComponent;
             break;
        }

    let componentRef = this.componentFactoryResolver.resolveComponentFactory(cmptName).create(this.injector);

     // check for duplicates and update with new one
      //   this.checkForDuplicateCmp(componentRef);

     //   send data to respecting component using `inputdata`       
     componentRef.instance['inputdata'] = initCmpInputdata;

     let indexView = this.dynamicComponentContainer.length;
                            this.dynamicComponentContainer.insert(componentRef.hostView, indexView);

    // keep reference of lastComponent added to DOM
     this.lastComponent = componentRef;

    }      



public remove component(){
    // destroy components as on click
        this.lastComponent.destroy();

      //or
       //for (var j = 1; j < this.dynamicComponentContainer.length; j++) {
      //  this.dynamicComponentContainer.remove(j);  //or pass j 
      //       }
      //    }

abcComponent.ts

@Input() inputdata: any;

Note:- For multiple instances of abccomoponent call

loadComponent(abccomoponent )
Mahi
  • 3,748
  • 4
  • 35
  • 70