2

I tried to find a way for having and manage an angular2 Component in a Service but with no success:

  1. I need to create:

    AlertService{
    
        alertConfirm(msg): Promise;
    }
    

alertConfirm will prompt an Confirmation window with 2 buttons (Ok, Cancel) and will return users' choise as a Promise.

  1. In General, the idea is to implement the famous JavaScript alert() method but with a designed UI window and with also a cancel button.

The method will return a Promise with a response of user's choice: "OK" or "Cancel".

  1. I tried to find a way for holding an "anonymous" component, AlertComponent, in AlertService:

    AlertComponent{
    
        showMsgConfirm(msg): Promise;
    }
    

The Promise will be set with a response when user close prompt window or click "OK" or "Cancel".

  1. The question:

How to make "AlertService" to have an inner "AlertComponent" which can be managed by it's "alertOK" method?

I mean, I didn't find a way for "alertConfirm" to call "showMsgConfirm" method and to return it's Promise as a response.

for example, calling from main app component:

this.alertService.alertConfirm("Save changes?").then(res => {
    if(res.ok){console.log("Can be saved");
}, err=> { });

Any ideas for this?

Thanks,

Update:2 different ideas for solution, but with no sucess to manage the AlertComponent:

import { Injectable, ViewContainerRef, ReflectiveInjector, ComponentFactoryResolver, ComponentRef } from '@angular/core';

import { AlertComponent } from './../components/modales/AlertComponent/AlertComponent.component';

@Injectable()
export class AlertService {

    constructor(private componentFactoryResolver: ComponentFactoryResolver) { }

    public createAlertComp(vCref: ViewContainerRef): ComponentRef<any> {


        let factory = this.componentFactoryResolver.resolveComponentFactory(AlertComponent);

        /*

        //Option 1:

        // vCref is needed cause of that injector..
        let injector = ReflectiveInjector.fromResolvedProviders([], vCref.parentInjector);

        // create component without adding it directly to the DOM
        let comp = factory.create(injector);

        // add inputs first !! otherwise component/template crashes ..
        comp.instance.model = modelInput;

        // all inputs set? add it to the DOM ..
        vCref.insert(comp.hostView);

        return comp;
        */

        //Option 2:

        var componentRef: ComponentRef<AlertComponent> = vCref.createComponent(factory);       
        return null;
    }


}
Dudi
  • 3,069
  • 1
  • 27
  • 23

1 Answers1

0

And the answer is... :

  • The Service:

    1. _counter is used for each modal to have a unique name.
    2. comp.instance.close is a property of inner component for subscribing for EventEmitter.

.

import { Injectable, ViewContainerRef, ReflectiveInjector, ComponentFactoryResolver, ComponentRef, EventEmitter } from '@angular/core';

import { CtmAlertComponent } from './ctmAlert/ctmAlert.component';


@Injectable()
export class AlertCtmService {

    private _vcr: ViewContainerRef;
    private _counter: number = 0;

    constructor(private componentFactoryResolver: ComponentFactoryResolver, public viewRef: ViewContainerRef) {
        console.log("AlertCtmService.constructor:");

        //TODO: Consider appending to this.viewRef: "#alertCtmServiceContainer" as a Dom elemnt perent container which will hold all AlertModals:
        // Maybe by: 
        // this.viewRef.element.nativeElement.insertAdjacentHTML('beforeend', '<div class="alertCtmServiceContainer"></div>');

        this._vcr = this.viewRef;
    }

    public alertOK(alertMsg: string): EventEmitter<any> {
        return this.createEventEmitterComponent("CtmAlertComponent", alertMsg, false);
    }

    public alertConfirm(alertMsg: string): EventEmitter<any> {
        return this.createEventEmitterComponent("CtmAlertComponent", alertMsg, true);
    }

    private createEventEmitterComponent(componentName: string, alertMsg: string, isConfirm: boolean): EventEmitter<any> {

        console.log("AlertCtmService.createEventEmitterComponent:");

        switch (componentName) {
            case "CtmAlertComponent":
            default:
                var _component = CtmAlertComponent;
                break;
        }

        let factory = this.componentFactoryResolver.resolveComponentFactory(_component);

        // vCref is needed cause of that injector..
        let injector = ReflectiveInjector.fromResolvedProviders([], this._vcr.parentInjector);

        // create component without adding it directly to the DOM
        let comp = factory.create(injector);

        // add inputs first !! otherwise component/template crashes ..
        comp.instance.close.subscribe(resp => {
            console.log("AlertCtmService.createEventEmitterComponent: comp.instance.close.subscribe: resp=" + resp.ok);
            comp.destroy();
        })

        comp.instance.alertBodyMsg = alertMsg;
        comp.instance.isConfirm = isConfirm;
        comp.instance.nameId = "Modal" +(++this._counter).toString();

        // all inputs set? add it to the DOM ..
        this._vcr.insert(comp.hostView);

        //return null;
        return comp.instance.close;

    }

    public init(vCref: ViewContainerRef): ViewContainerRef {
        this._vcr = vCref;
        return this._vcr;
    }

}
  • Inner Component:
    1. Using Bootstrap for handling display of Modal in UI: modal('show') \ modal('hide').

.

import { Component, AfterViewInit, Input, ViewChild, ElementRef, Renderer, NgZone, EventEmitter} from '@angular/core';

@Component({
    selector: 'ctm-alert',
    styles: [``],
    templateUrl: '/app/shared/alertCtm/ctmAlert/CtmAlert.component.html',
    styleUrls: ['./app/shared/alertCtm/ctmAlert/CtmAlert.component.css'],
    providers: []
})

export class CtmAlertComponent implements AfterViewInit {

    public ModalIsVisible: boolean;

    //private static subscriptions: Object = {};

    //enums = Enums;

    close = new EventEmitter();
    public nameId = "";
    private isOk = false;
    alertBodyMsg: string = "";
    isConfirm = false;

    constructor() {
        console.log("CtmAlertComponent.constructor:");
    }

    ngAfterViewInit() {
        this.showModal();

        var attrId = this.getIdAttr();
        $('#' + attrId).on('hidden.bs.modal', function () {
            debugger;
            console.log('CtmAlertComponent: #licenseModal_XXX.on(hidden.bs.modal)');
            this.submitStatus();
        }.bind(this) );

    }

    showModal() {        
        this.ModalIsVisible = true;
        var attrId = '#' +this.getIdAttr();
        $(attrId).modal('show');
    }

    hideModal() {
        this.ModalIsVisible = false;
        var attrId = '#' + this.getIdAttr();
        $(attrId).modal('hide');
    }



    getIdAttr(): string {
        return "ctmAlertModal_" + this.nameId;
    }

    submitStatus() {
        var resp = { ok: (this.isOk == true) };
        this.close.emit(resp);

    }
    submitOk() {
        this.isOk = true;
        this.hideModal();
    }
    submitCancel() {
        this.isOk = false;
        this.hideModal();
    }


}
  • App's Declaration:

    1. unfortunately, we must declare the anonymus component in our main-app module.
    2. We must add a declaration of entryComponents: [CtmAlertComponent],

.

import { CtmAlertComponent } from './shared/alertCtm/ctmAlert/ctmAlert.component';

@NgModule({
  imports: [
    BrowserModule,
    HttpModule,    
    AppRoutingModule,
...
  ],
  declarations: [
      CtmAlertComponent,
      AppComponent,
...
  ],
  entryComponents: [CtmAlertComponent],
  providers: [
...
  ],
  bootstrap: [AppComponent],
})
export class AppModule { }

enableProdMode();
  • Modal UI:

    1. this html template is based on bootstrap's UI:

.

<div class="ctmAlertModal modal fade in" [id]="getIdAttr()" role="dialog">
    <div class="modal-dialog modal-lg" [ngClass]="{'modal-lg-6': true }">

        <!-- Modal content-->
        <div class="modal-content">

            <div class="modal-header" style="">
                <div class="pull-right" style="position: relative;">
                    <a href="#" data-dismiss="modal" (click)="hideModal()"><span class="fa fa-times-circle" aria-hidden="true" style="color: #949494"></span></a>
                </div>
            </div>                   

            <div class="modal-body">

                <div class="modal-body-msg">
                    {{alertBodyMsg}}
                </div>


                <div class="modal-body-buttons">

                    <div style="margin: 0 auto;" [style.width]="(isConfirm)? '165px' : '70px' ">

                        <button type="button" *ngIf="isConfirm" class="btn-submit pull-left btn-cancel" [ngClass]="{'disabled': false }" [disabled]="false" (click)="submitCancel()">
                            <!--<img alt="End-Training" class="centering-me2" src="../../../contents/training_state_stop_white.svg">-->
                            Cancel
                        </button>
                        <button type="button" class="btn-submit pull-right" [ngClass]="{'disabled': false }" [disabled]="false" (click)="submitOk()">
                            <!--<img alt="Resume-Training" src="../../../contents/training_state_play_white.svg">-->
                            OK
                        </button>
                    </div>


                </div>


            </div>

        </div>

    </div>
</div>

.

  • Usage::

    1. for example:

.

    this.alertCtmService.alertOK("Save changes???").subscribe(function (resp) {
        console.log("alertCtmService.alertOK.subscribe: resp=" + resp.ok);
        this.saveData();
    }.bind(this) );

**

**

sources:

  1. building-angular-2-components-on-the-fly-a-dialog-box-example

  2. angular2-ngmodule

Dudi
  • 3,069
  • 1
  • 27
  • 23