Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Opening modal in dom #33

Closed
Tahiche opened this issue Mar 7, 2018 · 5 comments
Closed

Opening modal in dom #33

Tahiche opened this issue Mar 7, 2018 · 5 comments

Comments

@Tahiche
Copy link

Tahiche commented Mar 7, 2018

Apologies in advance, this is not an issue but a support request...
I love this lib, it´s great for most use cases. I´m just missing the option for a more "specific" use case when you don´t want to create a component (extending SimpleModalComponent) but rather just include it in your parent component. This is the way other libs (for ex Angular Bootstrap 3 Modal Component work. I´d really like to have the best of both worlds, have components for alerts, confirms, etc as you do but also to pull the modal from DOM in a more Bootstrapish approach.

My approach to do this is to create a "DomComponent" with "" selector that in turn calls SimpleModalService to open it´s content (through a GenericModalComponent). I´ll post the code i´m using, but it might be a totally wrong approach, so I can point out the problems i´m encountering....

In parent component ....

 <button type="button" class="btn btn-default" (click)="domModal.open()">Open
      <b>&lt;modal&gt;</b> in dom.</button>
    <pre>inputText: {{inputText}}</pre>

    <modal (onClose)="event_here?()" title="Modal title" #domModal>
      <modal-header>Hi this is head of modal.
        <b>Bold</b>
      </modal-header>
      Modal body here.... this is the first modal.
      <input type="text" evInput evIcon="fa-android" [(ngModel)]="inputText" />
      <modal-footer>Hi this is modal footer.
        <button class="btn btn-success" (click)="modalInnerSubmit(domModal)">InnerSubmit</button>
      </modal-footer>
    </modal>

And DomModal component:
(BaseModalComponent extends SimpleModalComponent).

@Component({
  selector: 'modal',
  template: `<ng-template #domModalContent>
  <ng-content ></ng-content>
  </ng-template>
  <ng-template #footer >
  <ng-content select="modal-footer"></ng-content>
  </ng-template>
  <ng-template #header >
  <ng-content select="modal-header"></ng-content>
  </ng-template>`
})
export class DomModalComponent extends BaseModalComponent  implements GenericModalModel, OnInit, OnDestroy {
  @ViewChild('domModalContent') content: TemplateRef<any>;
  @ViewChild('footer') footer: TemplateRef<any>;
  @ViewChild('header') header: TemplateRef<any>;
  openM: Observable<any>;
  modalIndex: number;
  openModal: SimpleModalService;
  constructor(private modalService: SimpleModalService,
    private _componentFactoryResolver: ComponentFactoryResolver) {
    super();
  }
   private closerCallback(modal){
    // does not work... modal is opener (this) not the one service opened.
    // this.modalService.removeModal(modal);
    const openModals = this.modalService.modalHolderComponent.modals;
    const index = _.indexOf(openModals, this.openModal);
    const activeComponent = this.modalService.modalHolderComponent.modals[index];
    if (activeComponent) {
      activeComponent.close();
    }
  }

  open() {
    // how many of this.properties are attributes to pass along to creation?
    const attrObject = this.attributesToObject() || {};

    this.openM = this.modalService.addModal(GenericModalComponent,
      {...attrObject},
      {closeOnClickOutside: true}
     );
     // this never fires...
     this.openM.subscribe((isConfirmed) => {
      if (isConfirmed) {
        this.close();
      }
    });
     // Might fail because modalHolderComponent  is private in service
     const openModals = this.modalService.modalHolderComponent.modals;
     this.openModal = _.last(openModals);
     this.modalIndex = openModals.length ? openModals.length - 1 : null;
  }
  attributesToObject() {
    const availableAttrs = Object.keys(this);
    // attributes we want to parse...
    const attrObject = new GenericModalModelClass();
    for (const attr of Object.keys(attrObject) {
       if (this[attr]) {
        attrObject[attr] = this[attr];
      }
    }
    return attrObject;
  }
}

I have major issues here.
I can open the modal just fine using Service. But i have no reference to the opened Modal, the only way i can seem to access it is by " this.modalService.modalHolderComponent.modals;" which seems wrong and even more since it´s a private var.
I implemented "closerCallback" to be able to extend, since DomModal is not opened through service... here is where i close the opened modal... But "this.modalService.removeModal();" needs the opened modal which i can´t seem to target, so I hackishly target modalHolderComponent to get the last opened modal.
There must be a better way (for sure ;) and even if my approach is halfway alright (doubt it) there must be a way of interacting (closing, etc) with the opened modal through Promises, Observables... Triggering the opened modal close() and resolving without direct ref to modalHolderComponent....

I´d really love to implement this to cover all cases. Thanks in advance!!.

@kevcjones-archived
Copy link
Collaborator

ok so i'll be honest i'm still digesting your implementation but if i was to take what i think i understand as your requirement you want the ability to do something like this

import { Component, ViewChild } from '@angular/core';
import { BsModalComponent } from 'ng2-bs3-modal';

@Component({
    selector: 'parent-component',
    template: `
        <bs-modal #myModal>
            ...
        </bs-modal>
    `
})
export class ParentComponent {
    @ViewChild('myModal')
    modal: BsModalComponent;

    close() {
        this.modal.close();
    }
    
    open() {
        this.modal.open();
    }
}

@Tahiche
Copy link
Author

Tahiche commented Mar 9, 2018

Hi, thanks for the response.
Yes, your example from 'ng2-bs3-modal' is the kind of implementation I´m after. I´m sorry if i wasn´t clear enough.
Since ngx-simple-modal does it´s magic through ComponentFactoryResolver my approach was to sort of mimic modalHolderComponent/wrapper... read the content of the child modal ('bs-modal' in your example) and pass it to a "holder" component via the service... ng-bootstrap modal has this option of opening a component modal via service or a modal in the template/dom.

This might be totally worng or overhead... I tried getting this "component markup" (templateRef) into "message" but I can´´t get message to render the html... Bassically, it´s just getting a modal to open markup on the page/template...

As for my approach, it´s actually working except this.modalService.modalHolderComponent is private , sometimes it works, sometimes it gives me an error.

Thanks for your patience...

@Tahiche
Copy link
Author

Tahiche commented Mar 9, 2018

I managed to close the opened modal via suscriber which looks like an OK approach... (I´m a noop at suscribers and promises)...

Code in parent template modal.component.html:

<button type="button" class="btn btn-default" (click)="domModal.open()">Open
      <b>modal</b> in dom.</button>

      <div *ngIf="modalAction != null">
          <span>Result: </span>
          <b [ngClass]="{'text-warning': modalAction=='DISMISSED'?true:false, 'text-success': modalAction==('CLOSED' || 'OPEN')?true:false}">{{modalAction}}</b>
        </div>

    <modal
    (onOpen)="modalAction='OPEN'"
    (onClose)="modalAction='CLOSED'"
    (onDismiss)="modalAction='DISMISSED'"
    title="Modal title"
    #domModal>
      <modal-header>Hi this is head of modal.
        <b>Bold</b>
      </modal-header>
      Modal body here.... this is the first modal.
      <input type="text" evInput evIcon="fa-android" [(ngModel)]="inputText" />
      <modal-footer>Hi this is modal footer.
        <button class="btn btn-success" (click)="modalInnerSubmit(domModal)">InnerSubmit</button>
      </modal-footer>
    </modal>

modal.component.ts

...
modalAction = null;
modalInnerSubmit(modal: SimpleModalComponent<any, any>) {
    alert('modalInnerSubmit');
    modal.close();
  }

dom-modal.component.ts

@Component({
  selector: 'modal',
  template: `<ng-template #domModalContent>
  <ng-content ></ng-content>
  </ng-template>
  <ng-template #footer >
  <ng-content select="modal-footer"></ng-content>
  </ng-template>
  <ng-template #header >
  <ng-content select="modal-header"></ng-content>
  </ng-template>`
})
export class DomModalComponent extends BaseModalComponent  implements GenericModalModel, OnInit, OnDestroy {
  @ViewChild('domModalContent') content: TemplateRef<any>;
  @ViewChild('footer') footer: TemplateRef<any>;
  @ViewChild('header') header: TemplateRef<any>;
  @Output() onClose: EventEmitter<any> = new EventEmitter(false);
  @Output() onDismiss: EventEmitter<any> = new EventEmitter(false);
  @Output() onOpen: EventEmitter<any> = new EventEmitter(false);

  openM: Observable<any>;
  modalIndex: number;
  openModal: SimpleModalService;
  public openedModalResult: any = null;
  modalAction: EventEmitter<any> = new EventEmitter();
  suscriber;
  constructor(private modalService: SimpleModalService,
    private _componentFactoryResolver: ComponentFactoryResolver) {
    super();
  }
  close(): Promise<any> {
    // this.suscriber.next(true);
    this.suscriber.unsubscribe();
    this.onClose.emit(true);
    return  new Promise((resolve, reject) => resolve(true) );
  }
  dismiss() {
    this.suscriber.unsubscribe();
    this.onClose.emit(false);
    return  new Promise((resolve, reject) => resolve(false) );
  }

  open() {
    // how many of this.properties are attributes to pass along to creation?
    const attrObject = this.attributesToObject() || {};
    this.suscriber = this.modalService.addModal(GenericModalComponent,
      {...attrObject},
      {closeOnClickOutside: true}
     ).subscribe((modalResult) => {
      this.openedModalResult = modalResult || null;
      this.modalAction.emit(this.openedModalResult);
      if (modalResult) {
        // this.close();
        this.onClose.emit(true);
      } else if (modalResult === false) {
        this.onDismiss.emit(null);
      } else {
        this.onClose.emit(modalResult || null);
      }
    });
    this.onOpen.emit(true);
  }
  attributesToObject() {
    const availableAttrs = Object.keys(this);
    // attributes we want to parse...
    const attrObject = new GenericModalModelClass();
    for (const attr of Object.keys(attrObject)) {
       if (this[attr]) {
        attrObject[attr] = this[attr];
      }
    }
    return attrObject;
  }
}

@kevcjones-archived
Copy link
Collaborator

Yeah, i like this idea, I think I can make this simpler for you too long term. The only issue I have right this second is that I'm only 3 weeks into my new role so I'm being conservative with my time. I'll try to carve out some time to write something to help support you more. Meanwhle looks like you've found a route through.

@kevcjones-archived
Copy link
Collaborator

Going to close this for now - its a bit of a different direction tbh from where the component is. I'll revisit it further on.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants