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.
@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?
@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/[email protected]/components/progressbar/bar.component.js(…)(anonymous function) @ run.plnkr.co/:16
https://npmcdn.com/[email protected]/components/progressbar/bar.component.js Failed to load resource: the server responded with a status of 404 ()
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.)
@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: https://github.com/valor-software/ng2-bootstrap/issues/29#issuecomment-223271986
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: https://github.com/shlomiassaf/angular2-modal/issues/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...
@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.
@jhiemer hey, I have invited you to ng2-team, I see you are helping a lot to guys issues
Thanks you so much!
@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?
@jhiemer I need to some test for modals before creating service >.<
Also, would be great to append modal at body by service. I have z-index issue with current modal implementation.
+1
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
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.
@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);
...
@vieillecam how can i close the modal from dynamic modal itself ?
@vieillecam okay got it, just need to inject the modalService and trigger hideModal()
@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 valor-software#579
will be starting to implementing this one
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
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
Work in progress https://github.com/valor-software/ngx-bootstrap/pull/2047
any one please help this to work in angular4
plnkr is now in angular2
@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
https://stackoverflow.com/questions/46408428/ngx-bootstrap-modal-how-to-get-a-return-value-from-a-modal/48803705#48803705
The above link helped me to implement modal service..
Most helpful comment
@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):and use an injector to pull the inputs:
I was using lazy loading for modules, so I also needed to pass through the
injector(and resolve theresolverlater within the context of the calling module):and then the following in the calling module: