13

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.

15
  • 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 Commented Jul 5, 2019 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. Commented Jul 5, 2019 at 10:10
  • 1
    I had a similar problem, I did not want to destroy my map when I was changing route. Have a look here stackoverflow.com/a/49759253/1160794 Commented Jul 5, 2019 at 10:20
  • 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. Commented Jul 9, 2019 at 10:25
  • 1
    What do you mean when you say that the stackblitz exemple does not work anymore? I just tried it and it still works Commented Jul 10, 2019 at 8:14

2 Answers 2

9
+25

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)
    );
  }
}

Sign up to request clarification or add additional context in comments.

Comments

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 )

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.