简体   繁体   中英

Angular2, generic overlay component for all other components inside the app

I have the following two components:

overlay

@Component({
    selector: 'overlay',
    template: '<div class="check"><ng-content></ng-content></div>'
})
export class Overlay {
    save(params) {
        //bunch of stuff that are commonly used
    }
}

myComponent (there are many more like this)

@Component({
    selector: 'myComponent',
    directives: [Overlay],
    template: '<overlay><form (ngSubmit)="save({ name: 'sam', lastName: 'jones' })">I'm the component. Click <input type="submit">Here</input> to save me!</form></overlay>'
})
export class MyComponent {

}

This solutions doesn't work with no errors thrown, Angular2 simply skips both components initialization. Anyways, i think you get the idea here. There's a common component which will need to wrap a lot of other components hence it needs to be generic. It has a template so services don't do the job. I haven't worked much with custom annotations, maybe they could do something like this? Any thoughts on how to achieve such a functionality?

note: the overlay contains logic and template, both of which are needed for any other component that needs to use it. The template is a complex modal dialog with animations, messages, fades etc... that is common across the solution.

Update

Found this: Angular2: progress/loading overlay directive

Ok maybe this is not a direct answer to your question, but from your comment I guess you want to implement modals inside your app. I haven't done it yet but in near future I will (this is part of my app's requirement).

First I do not use bootstrap javascript because I'm using angular already. The only thing that you need is bootstrap css .

consider below root template:

 <my-app></my-app>

 <my-modal>
    <div class="modal-container">
       <div class="modal-content">{{modalContent}}</div>
    </div>
 </my-modal>

show your modal: Global Event

You can show your modal from any component . The trick is to inject a modal-service to send a global event . see this answer to see how it works.

You can send any object as event . This event object could contains your modal's contents or a key so modal component can fetch it from some where else.

ModalComponent

Your ModalComponent should subscribe to that event. On event you can show and hide it (the modal component) in several ways that i'm sure your are aware of. Using structural directives is one way.

Style

A modal is a container ( .modal-container ) with width and height of viewport with transparent background. Modal's content ( .modal-content ) is another container with fixed width and height and absolute position. If you want bootstrap styles you can add it and for animations you can use angular animations

I'm currently using a similar approach for menubar and it works very well!!

I created one for this specific task and thought about embedding bootstrap modal as template but came across a couple of issues such as the fact that the only useful piece out of bootstrap modal would be the class names. Not surprisingly, didn't want that for this specific task since I needed to distinguish between window modals and progress modals on components.

Overlay

@Component({
    selector: 'overlay',
    template:
    `<div [ngClass]="isOpen ? 'opened' : 'closed'">
         <div class="modal" role="dialog">
            <div class="modalBody">
                <div *ngIf="isSaving">
                    <span class="text-success text-bold">
                        Saving...
                    </span>
                </div>
                <div *ngIf="isSaved">
                    <span class="text-success text-bold">
                        Saved.
                    </span>
                </div>
            </div>
        </div>
    </div>`,
    styles: [
        '.modal { position:absolute; width: 100%; height: 100%; margin: -30px; background-color: rgba(255, 255, 255, 0.7); z-index: 1000; text-align: center; }',
        '.closed { visibility: hidden; }',
        '.opened { visibility: visible; }',
        '.modalBody { top: 45%; left: 25%; width: 50%; position: absolute; }',
        '.text-bold { font-weight: 800; font-size: 1.5em; }'
    ]
})
export class Overlay implements OnChanges, AfterContentInit {
    @Input() isSaving: boolean = false;
    @Input() isSaved: boolean = false;
    @Input() containerElement: HTMLElement;

    isOpen = false;

    private modalElement;

    constructor(private element: ElementRef, private animationBuilder: AnimationBuilder) { }

    ngOnChanges() {
        if (this.modalElement) {
            if (this.isSaving == true || this.isSaved == true) {
                this.toggleAnimation(true);
            }
            else if (this.isSaving == false && this.isSaved == false) {
                this.toggleAnimation(false);
            }
        }
    }

    ngAfterContentInit() {
        this.containerElement.style.position = 'relative';
        this.modalElement = this.element.nativeElement.querySelector('.modal');
    }

    private toggleAnimation(isOpen) {
        var startCss = { backgroundColor: 'rgba(255, 255, 255, 0)' };
        var endCss = { backgroundColor: 'rgba(255, 255, 255, 0.7)' };

        if (isOpen) {
            this.isOpen = true

            this.animation(
                true,
                this.modalElement,
                400,
                startCss,
                endCss,
                null
            );
        }
        else {
            this.animation(
                isOpen,
                this.modalElement,
                400,
                startCss,
                endCss,
                () => {
                    this.isOpen = false;
                }
            );
        }
    }

    private animation(isStart, element, duration, startCss, endCss, finishedCallback) {
        var animation = this.animationBuilder.css();

        animation.setDuration(duration);

        if (isStart) {
            animation.setFromStyles(startCss).setToStyles(endCss);
        } else {
            animation.setFromStyles(endCss).setToStyles(startCss)
        }

        animation.start(element);

        if (finishedCallback) {
            setTimeout(finishedCallback, duration);
        }
    }
}

Usage

As is, you'll need a relative container in order for overlay to fill its parent. The css definitely needs modifications to accommodate for a few scenarios such as mobile devices and non-positioned containers. Here's how it's currently used:

HTML

<form action="/edit" method="post" #myForm="ngForm" (ngSubmit)="save ajax method that will update the isSaving and isSaved accordingly" novalidate>
    <div style="position: relative;" #overlayContainer>
        <overlay [isSaving]="isSaving" [isSaved]="isSaved" [containerElement]="overlayContainer"></overlay>
    </div>
</form>

After saving the form , an overlay is shown within the containerElement for 400ms and is faded out and goes hidden afterwards until the next save attempt. The isSaving and isSaved binding values are the responsibility of the any component that wishes to use the overlay .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM