angularsubjectangular-dynamic-components

How to create multiple instances of a dynamic component sequentially?


I have a SystemMessageService which creates instances of SystemMessageComponent using the following method

createSystemMessage(message: string, vcr: ViewContainerRef, time: number = 4500, isErrorMessage: boolean = false, isWarningMessage: boolean = false, isSuccessMessage: boolean = false){
    let systemMessageCmpFactory = this.componentFactoryResolver.resolveComponentFactory(SystemMessageComponent);
    this.hostViewContainerRef = vcr;
    this.hostViewContainerRef.clear();
    this.systemMessageCmpRef = this.hostViewContainerRef.createComponent(systemMessageCmpFactory);
    this.systemMessageCmpRef.instance.message = message;
    this.systemMessageCmpRef.instance.timeUntilDestruction = time;
    this.systemMessageCmpRef.instance.isErrorMessage = isErrorMessage;
    this.systemMessageCmpRef.instance.isWarningMessage = isWarningMessage;
    this.systemMessageCmpRef.instance.isSuccessMessage = isSuccessMessage;
    this.closeSubMsg = this.systemMessageCmpRef.instance.closeMessage.subscribe(() => {
      this.closeSubMsg.unsubscribe();
      this.hostViewContainerRef.clear()
    })
  }

Now I have a notifier in SystemMessageService,

private systemMessageNotifier$: Subject<string> = new Subject();
public systemMessageObs$ = this.systemMessageNotifier$.asObservable();
emitSystemMessage(systemMessage: string){
    this.systemMessageNotifier$.next(systemMessage);
}

which informs app.component.ts when to display a new system message

@ViewChild('systemMessageHost', {read: ViewContainerRef}) systemMessageHost: ViewContainerRef;
  constructor(private systemMessageService: SystemMessagesService){

  }

  ngOnInit(): void {
    this.systemMessageService.systemMessageObs$.subscribe(systemMessage => {
      this.systemMessageService.createSystemMessage(systemMessage, this.systemMessageHost, 60*1000*60)
    })
  }

but the problem is that messages would overlap if sent sequentially, for example if I call the following in any component

this.systemMessageService.emitSystemMessage("Message1");
this.systemMessageService.emitSystemMessage("Message2");

I would only show "Message2" but I want to show "Message1" first and if the user closes the component, then "Message2" should be shown afterwards. How would I implement this? The SystemMessageComponentcan be closed either by timeout or click on a close button.


Solution

  • I solved it this way: I use a variable to check whether an instance exists. If it exists I push the next message in an array. When the instance is destroyed, I use a notifier to inform app.component that the instance was destroyed so that, if there is another message, the next message can be shown.

    private systemMessageNotifier$: Subject<SystemMessage> = new Subject();
    public systemMessageObs$ = this.systemMessageNotifier$.asObservable();
    private sysMsgDestroyedNotifier$: Subject<any> = new Subject();
    public systemMessageDestroyed$ = this.sysMsgDestroyedNotifier$.asObservable();
    
    createSystemMessage(message: string, vcr: ViewContainerRef, messageType: SystemMessageType, noTimeOut: boolean = false, timeUntilDestruction: number = 1000*3600){
        let systemMessageCmpFactory = this.componentFactoryResolver.resolveComponentFactory(SystemMessageComponent);
        this.hostViewContainerRef = vcr;
        this.hostViewContainerRef.clear();
        this.systemMessageCmpRef = this.hostViewContainerRef.createComponent(systemMessageCmpFactory);
        this.systemMessageCmpRef.instance.message = message;
        this.systemMessageCmpRef.instance.timeUntilDestruction = timeUntilDestruction;
        this.systemMessageCmpRef.instance.messageType = messageType;
        this.systemMessageCmpRef.instance.noTimeOut = noTimeOut;
        this.closeSubMsg = this.systemMessageCmpRef.instance.closeMessage.subscribe(() => {
          this.closeSubMsg.unsubscribe();
          this.hostViewContainerRef.clear();
          this.sysMsgDestroyedNotifier$.next();
        })
      }
    
    export class AppComponent implements OnInit {
      delayedSystemMessages = [];
      isSystemMessageComponentActive: boolean = false; 
      @ViewChild('systemMessageHost', {read: ViewContainerRef}) systemMessageHost: ViewContainerRef;
      constructor(private systemMessageService: SystemMessagesService){
    
      }
    
      ngOnInit(): void {
        
        this.systemMessageService.systemMessageDestroyed$.subscribe(() => {
          if(this.delayedSystemMessages.length > 0){
            this.isSystemMessageComponentActive = false;
            let message = this.delayedSystemMessages.shift()
            this.systemMessageService.emitSystemMessage(message);
          }
        })
    
        this.systemMessageService.systemMessageObs$.subscribe(systemMessage => {
          if(this.isSystemMessageComponentActive){
            this.delayedSystemMessages.push(systemMessage)
            return;
          }
          this.isSystemMessageComponentActive = true;
          this.systemMessageService.createSystemMessage(systemMessage.message, this.systemMessageHost, systemMessage.messageType, systemMessage.noTimeOut, systemMessage.timeUntilDestruction)
        })
      }
    }