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

feat(modal): add modals service to launch modal components #579

Closed
kokokenada opened this issue Jun 3, 2016 · 23 comments · Fixed by #2047
Closed

feat(modal): add modals service to launch modal components #579

kokokenada opened this issue Jun 3, 2016 · 23 comments · Fixed by #2047

Comments

@kokokenada
Copy link

I'd like to launch a modal via a service like you could in angular 1. I've create a proof of concept in plunker. https://plnkr.co/edit/Cua6HJYrN3i2SkgC0iLj?p=preview

It's running on my machine, but plunker can't resolve rxjs and I wasn't able to figure it our quickly, so help appreciated.

@jhiemer
Copy link

jhiemer commented Jun 4, 2016

@kokokenada I fixed your plnkr as far as I could, but the plnkr does not open up: https://plnkr.co/edit/DpxOudhAzuPwEZz0xdcR?p=preview

Any idea why?

@kokokenada
Copy link
Author

@jhiemer , thanks for looking at it. I took your config and got further in my plunker. It's still crashes with the dreaded "TypeError: Cannot read property 'query' of null". (I groan every time I get this because it's so hard to find the problem...) Anyways, I'll keep at it.

I've also push my hobby app to github which has the dialog service running. https://github.com/kokokenada/for-real-cards

Re your instance, when I looked at the console log, I got:
The console error log complains:

Failed to load resource: the server responded with a status of 404 ()
run.plnkr.co/:16 Error: Error: XHR error (404) loading https://npmcdn.com/ng2-bootstrap@1.0.17/components/progressbar/bar.component.js(…)(anonymous function) @ run.plnkr.co/:16
https://npmcdn.com/ng2-bootstrap@1.0.17/components/progressbar/bar.component.js Failed to load resource: the server responded with a status of 404 ()

@kokokenada
Copy link
Author

OK, the app comes up now. (https://plnkr.co/edit/Cua6HJYrN3i2SkgC0iLj?p=preview). I had to comment out a critical piece, so the "Open" button doesn't work. Have a look at the constructor of the modal-outlet.component.ts. Putting the injected services back in causes the app not to run. Any idea why this doesn't work in plunker? (Works fine in my app.)

@jhiemer
Copy link

jhiemer commented Jun 5, 2016

@kokokenada take a look here, I think I managed to get it running:

https://plnkr.co/edit/SC3m12oquQ3mNRtTi1IH?p=preview

What you have struggled with in the constructor (happened to me as well) is the missing @Inject for injectable components from Angular itself.

constructor(@Inject(ViewContainerRef) private vcRef: ViewContainerRef, 
    @Inject(ComponentResolver) private resolver: ComponentResolver) { 
  }

As question left: how would that work, if I would like to provide a complete form into the modal?
Is clear now, I just create a component. :-)

@valorkin do you think this is something one could add to ng2-bootstrap? This is nearly that what I have asked for in: #29 (comment)

@kokokenada
Copy link
Author

Thanks for the fixes @jhiemer !

@valorkin , It think something like this is necessary in order to follow the style guide's (https://angular.io/docs/ts/latest/guide/style-guide.html) requirement of "single responsibility". Otherwise, how do you isolate the modal's implementation from the caller's implementation? How do you call the same modal from multiple locations?

Also, just to connect the dots: shlomiassaf/ngx-modialog#104 (was the twbs modal a fork from that? - I thought I noticed some similarities....)

If there is interest in this approach, one aspect I didn't like about the proof of concept implementation is cramming all of the parameters in one single @input named componentParameters and forcing users to break it up with ngOnChanges. My first implementation tried to translate the top level keys into individual@input's but I hit a snag. (I was generating the HTML by stringifying the values and angular complained when an array was >10. (!)) (http://stackoverflow.com/questions/37113385/unsupported-number-of-argument-for-pure-functions) And that was just the first problem. Not sure how to dynamically define @inputs...

@jhiemer
Copy link

jhiemer commented Jun 6, 2016

@kokokenada regarding the componentParameters: if I remember correctly it was nearly the same as in the Angular 1 implementation. You had the ability to use the config parameter, which provided all options, including data to be transferred to the modal. Although I don't know if there is any better option in Angular 2, this seems a valid way to go on from.

@valorkin valorkin changed the title request for modal to be launched with a service that is given a component feat(modal): add modals service to launch modal components Jun 6, 2016
@valorkin
Copy link
Member

valorkin commented Jun 6, 2016

@jhiemer hey, I have invited you to ng2-team, I see you are helping a lot to guys issues
Thanks you so much!

@jhiemer
Copy link

jhiemer commented Jun 8, 2016

@valorkin thanks, saw it. What do you think, how should we proceed with the service based modal implementation?

@kokokenada could you create a pull-request, that we could work on the implementation?

@valorkin
Copy link
Member

valorkin commented Jun 8, 2016

@jhiemer I need to some test for modals before creating service >.<

@lanocturne
Copy link
Contributor

Also, would be great to append modal at body by service. I have z-index issue with current modal implementation.

@Caballerog
Copy link

+1

@purvad
Copy link

purvad commented Sep 14, 2016

I have used this modal window service but it is not working in my rc.5 project. it gives warning for PromiseResolver saying that it is deprecated in rc.5. so what should i use instead of PromiseResolver to run in rc5

@vieillecam
Copy link

vieillecam commented Dec 30, 2016

Hi everyone,

in my team, we manage to get something working like that :

We create a generic component that take care of the dynamic component creation :

import { Subscription } from 'rxjs/Rx';
import { ModalDirective } from 'ng2-bootstrap/ng2-bootstrap';
import { Component, OnInit, ViewChild, ViewContainerRef, ComponentFactoryResolver, ReflectiveInjector, EventEmitter, Output } from '@angular/core';

export class ComponentData {
    public component: any;
    public inputs?: any;
    public settings?: any;
}

@Component({
    selector: 'dynamic-modal',
    entryComponents: [],
    template: `<div bsModal #dynamicModal="bs-modal" class="modal fade" role="dialog" aria-hidden="true"><div #dynamicComponentContainer></div></div>`,
})
export class DynamicModalComponent implements OnInit {
    @Output() dynamicModalComponent = new EventEmitter<DynamicModalComponent>();
    @ViewChild('dynamicModal') dynamicModal: ModalDirective;
    @ViewChild('dynamicComponentContainer', { read: ViewContainerRef }) dynamicComponentContainer: ViewContainerRef;

    private modalSubscriptions: Subscription[] = [];
    private modalSettings: any;

    constructor(private resolver: ComponentFactoryResolver) { }

    ngOnInit() {
        this.dynamicModalComponent.emit(this);
    }

    public showModal(modal: ComponentData) {
        // Inputs need to be in the following format to be resolved properly
        modal.inputs = modal.inputs || {};
        this.modalSettings = modal.settings;
        let inputProviders = Object.keys(modal.inputs).map((inputName) => { return { provide: inputName, useValue: modal.inputs[inputName] }; });
        let resolvedInputs = ReflectiveInjector.resolve(inputProviders);

        let injector = ReflectiveInjector.fromResolvedProviders(resolvedInputs, this.dynamicComponentContainer.parentInjector);
        let factory = this.resolver.resolveComponentFactory(modal.component);
        let componentCreated = factory.create(injector);
        this.dynamicComponentContainer.clear();
        this.dynamicComponentContainer.insert(componentCreated.hostView);

        this.dynamicModal.show();
    }

    public hideModal() {
        this.dynamicModal.hide();
    }

    public onShown(action: () => void) {
        this.modalSubscriptions.push(this.dynamicModal.onShown.subscribe(action));
    }

    public onHidden(action: () => void) {
        this.modalSubscriptions.push(this.dynamicModal.onHidden.subscribe(() => {
            action();
            this.clearSubscriptions();
        }));
    }

    public getModalSettings() {
        return this.modalSettings;
    }

    private clearSubscriptions() {
        this.modalSubscriptions.map(sub => { sub.unsubscribe(); });
        this.modalSubscriptions = [];
    }

}

Then we use a shared service to expose methods to manage a modal and take care of the current opened modal :

import { ModalOptions } from 'ng2-bootstrap/ng2-bootstrap';
import { ComponentData, DynamicModalComponent } from './../components/dynamicModal/dynamicModal.component';
import { Injectable } from '@angular/core';

@Injectable()
export class ModalService {
  private dynamicModalComponent: DynamicModalComponent;

  public initializeDynamicModal(dynModal: DynamicModalComponent) {
    this.dynamicModalComponent = dynModal;
  }

  public configureModal(config: ModalOptions) {
    this.dynamicModalComponent.dynamicModal.config = config;
  }

  public getModalSettings() {
    return this.dynamicModalComponent.getModalSettings();
  }

  public showModal(modal: ComponentData) {
    this.dynamicModalComponent.showModal(modal);
  }

  public hideModal() {
    this.dynamicModalComponent.hideModal();
  }

  public onShown(action: () => void) {
    this.dynamicModalComponent.onShown(action);
  }

  public onHidden(action: () => void) {
    let hidden = this.dynamicModalComponent.onHidden(action);
  }
}

And we have to bootstrap that dynamic component in the App root component like this :
App.component.html :

<dynamic-modal (dynamicModalComponent)="onDynamicModalLoaded($event)"></dynamic-modal>
<div>
	<main>
		<router-outlet></router-outlet>
	</main>
</div>

App.component.ts:

...
    private onDynamicModalLoaded(event) {
        this.modalService.initializeDynamicModal(event);
    }
...

Then we just have to use this by creating a basic component that contain a modal as template and use it like that :

this.modalService.showModal({ component: **yourBodyModalComponent**, settings: { **some useful parameters for your component** } });

Hope thats could help !
I will do a Plunkr when I will have some time.

@zoubarevm
Copy link

zoubarevm commented Jan 5, 2017

@vieillecam not sure if there's a better way, but I had to also add the following to the module declaration to avoid an error (No component factory found for yourBodyModalComponent):

...
entryComponents: [
    *** yourBodyModalComponent***
  ],
...

and use an injector to pull the inputs:

...
   @Input() model: MyModel;
   ngOnInit() {
    let tempModel = this.injector.get('model', null);
    if (tempModel) {
      this.model = tempModel;
    }
  }
... 

I was using lazy loading for modules, so I also needed to pass through the injector (and resolve the resolver later within the context of the calling module):

public showModal(modal: ComponentData, parentInjector?: Injector) {
    parentInjector = parentInjector || this.dynamicComponentContainer.parentInjector;
    let resolver = parentInjector.get(ComponentFactoryResolver) || this.resolver;
    // Inputs need to be in the following format to be resolved properly
    modal.inputs = modal.inputs || {};
    this.modalSettings = modal.settings;
    let inputProviders = Object.keys(modal.inputs).map((inputName) => {
      return {provide: inputName, useValue: modal.inputs[inputName]};
    });
    let resolvedInputs = ReflectiveInjector.resolve(inputProviders);

    let injector = ReflectiveInjector.fromResolvedProviders(resolvedInputs, parentInjector);
    let factory = resolver.resolveComponentFactory(modal.component);
    let componentCreated = factory.create(injector);
    this.dynamicComponentContainer.clear();
    this.dynamicComponentContainer.insert(componentCreated.hostView);

    this.dynamicModal.show();
  }

and then the following in the calling module:

...

  constructor(private modalService: ModalService, private injector: Injector) {
  }

...
    this.modalService.showModal({
      component: TestComponent,
      inputs: {
           model: ... 
      }
    }, this.injector);
...

@touqeershafi
Copy link

@vieillecam how can i close the modal from dynamic modal itself ?

@touqeershafi
Copy link

@vieillecam okay got it, just need to inject the modalService and trigger hideModal()

@mihai-gritcan
Copy link

@valorkin , According to answers from other related items to the ModalService , at the end of January it was planned to start implementing the current feature.
I am just curious: How soon will this feature be available ? :)

valorkin commented on Jan 31
closing as a duplicate of #579
will be starting to implementing this one

@mihai-gritcan
Copy link

Just update: (ngx-home.slack.com discussion fragment )
Q: @valorkin , any updates concerning that ? May, 13th, 2017

A: Dmitriy Shekhovtsov [May, 13th, 2017 4:57 PM]
I have added dedicated dev to ngx-bootstrap so now things should go faster

@mihai-gritcan
Copy link

mihai-gritcan commented Jun 9, 2017

Another STATUS Update:
June, 3rd, 2017
Jimmy Aumard [3:48 PM]
Do you know if there any ModalService available to show modal from a deep component ?

Dmitriy Shekhovtsov [7:29 PM]
I have decided to publish all possible features without breaking changes in v1
Modal services is the next one

@valorkin
Copy link
Member

valorkin commented Jun 9, 2017

Work in progress #2047

valorkin pushed a commit that referenced this issue Jul 18, 2017
fixes #1998
fixes #1995
fixes #1830
fixes #1181
fixes #579 

* feat(modal): modal service wip

* feat(modal): wip, add close buttons attribute support, add ability to open modaol with component

* feat(modal): add config, move creating of loader to constructor, add demo

* fix(modal): fix service path

* fix(modal): fix api-docs

* fix(modal): fix scroll on modals created by service, add docs

* feat(modal): wip, add BsModalService.show output obj

* refactor(modal): change inner component getter

* feat(modal): add BsModalRef and docs

* feat(modal): remove data-attributes, return BsModalRef, update docs

* feat(modal): add docs for BsModalService, BsModalRef, update demo

* feat(modal): add bs4 support

* feat(modal): keep focus inside a modal

* chore(modals): small refactoring (#2128)

* chore(modals): simplify service (#2130)

* chore(modal): view container ref made optional for component loader (#2133)

* fix(modal): fix backdrop flickering

* fix(modal): fix backdrop click on the left/right sides, add class option

* feat(modals): nested modals wip

* fix(modal): fix multiple hide() call

* fix(modal): fix multiple backdrop clicks, fix padding

* fix(modal): fixed padding

* fix(modal): fix page flickering

* feat(modal): add isAnimated support, add service section to demo

* fix(test): fix popover and tyepahead unit tests
@arunalexp
Copy link

any one please help this to work in angular4

plnkr is now in angular2

https://plnkr.co/edit/SC3m12oquQ3mNRtTi1IH?p=preview

@derrickh1980
Copy link

derrickh1980 commented Jan 13, 2018

@arunalexp
This thread helped me to build my first modal module with systemjs and angular 2 so I hope this helps.

Take a look at this modal module proof of concept project I created for my team using angular 5.
https://github.com/derrickh1980/AngularCLISampleWithCustomModalModule

@ghost
Copy link

ghost commented Feb 15, 2018

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

Successfully merging a pull request may close this issue.