How to open modal for multiple components from same parent component - javascript

I am using angular 9.
I have a home page which has four buttons. I want to open separate modal on each button's click at a time.
I did research a lot, here is my trial and effort.
Thanks in advance :)
parent component
#Component({
selector: 'app-link-budget-view',
templateUrl: './home.component.html',
styleUrls: ['./home.component.scss']
})
export class HomeComponent implements OnInit {
#ViewChild('childOne') callChild_OneFn: ChildOneComponent;
#ViewChild('childTwo') callChild_TwoFn: ChildTwoComponent;
...
ngOnInit(): void {
}
openModalOne() {
this.callChild_OneFn.OpenModalFunction();
}
openModalOne() {
this.callChild_TwoFn.OpenModalFunction();
...
}
}
Home Html
<button class="btn btn-primary mb-2 mr-2" (click)="openModalOne()">Modal 1</button>
<button class="btn btn-primary mb-2 mr-2" (click)="openModalTwo()">Modal 2</button>
...
<app-child-one #childOne></app-child-one>
<app-child-two #childTwo></app-child-two>
...
childOne Component
#Component({
selector: 'app-link-budget-view',
templateUrl: './child-one.component.html',
styleUrls: ['./chile-one.component.scss']
})
export class ChildOneComponent implements OnInit {
constructor( private modalService: NgbModal) { }
ngOnInit(): void {
}
OpenModalFunction() {
console.log("component One function running...")
this.modalService.open('#ModalOneId', { size: 'xl' });
}
...
}
similarly there is a function in component two
OpenModalFunction() {
console.log("component Two function running...")
this.modalService.open('#ModalTwoId', { size: 'xl' });
}
Component One Html
<ng-template #ModalOneId let-modal>
<div class="modal-header">
<h4 class="modal-title">This is modal one</h4>
<button type="button" class="close" aria-label="Close" (click)="modal.dismiss('Cross click')">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<div class="card">
<div class=" card-body">
<div id="table" class="table-editable">
...
...
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-light" (click)="modal.close('Close click')">Close</button>
</div>
</ng-template>
similarly ng-templete is used in component two html

you didn't mention if what you wrote is working or not, or what errors you are getting. it looks like it should work to me. Although if you just want to open a modal, you can handle it completely in your html:
<button (click)="childOne.OpenModalFunction()">Modal 1</button>
<button (click)="childTwo.OpenModalFunction()">Modal 2</button>
<app-child-one #childOne></app-child-one>
<app-child-two #childTwo></app-child-two>
that should be the only thing you need in the parent. no code needed in the parent .ts file. if this is not working, there's something wrong with the code in your child class.

Finally I fixed it here is the code to help others
Parent ts
export class ParentComponent implements OnInit {
constructor(private modalService: CustomModalServiceService) { }
ngOnInit(): void {
}
openModal(modalName: string) {
this.modalService.openModalFunction(modalName);
}
}
Parent html
<button class="btn btn-primary mb-2 mr-2" (click)="openModal('childOne')">Modal_1</button>
<button class="btn btn-primary mb-2 mr-2" (click)="openModal('childTwo')">Modal_2</button>
child one or two ts code accordingly for one or two
export class ChildOneComponent implements OnInit {
constructor(private modalRef: NgbActiveModal) { }
ngOnInit(): void {
}
hideModalFunction() {
this.modalRef.close();
}
}
child one or two html for modal
<div class="modal-header">
<h4 class="modal-title">This is modal one</h4>
<button type="button" class="close" aria-label="Close" (click)="hideModalFunction()">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<div class="card">
<div class=" card-body">
<div id="table" class="table-editable">
...
...
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-light" (click)="hideModalFunction()">Close</button>
</div>
and you need a custom modal service
Custom modal service
export class CustomModalService {
...
private modalRef: NgbModalRef;
constructor( private modalService: NgbModal) { }
ngOnInit(): void {
}
openModalFunction(modalName: string) {
switch(modalName) {
case 'one':
console.log("component One function running...");
// do any execution before opening child component
this.modalRef = this.modalService.open(ChildOneComponent, { size: 'xl' });
this.modalRef.componentInstance.testData = 'test';
break;
case 'two':
console.log("component Two function running...");
// do any execution before opening child component
this.modalRef = this.modalService.open(ChildTwoComponent, { size: 'xl' });
break;
case default:
// do nothing
}
}
}

You are repeating too much code here. You could make it more re-usable and less repetitive. Try to extract most of the business logic to a service. Leave the component as dumb(no business logic) as possible for better re-usability.
Parent component
#Component({
selector: 'app-link-budget-view',
templateUrl: './home.component.html',
styleUrls: ['./home.component.scss']
})
export class HomeComponent implements OnInit {
...
constructor(private myModalService: MyModalService)
ngOnInit(): void {
}
openModal(modelName: string) {
this.myModalService.openModalFunction(modalName);
}
}
Home Html
<button class="btn btn-primary mb-2 mr-2" (click)="openModal('one')">Modal 1</button>
<button class="btn btn-primary mb-2 mr-2" (click)="openModal('two')">Modal 2</button>
...
MyModal Service
#Injectable({
providedIn: 'root'
})
export class MyModalService {
...
private modalRef: NgbModalRef;
constructor( private modalService: NgbModal) { }
ngOnInit(): void {
}
openModalFunction(modalName: string) {
switch(modalName) {
case 'one':
console.log("component One function running...");
// do any execution before opening child component
this.modalRef = this.modalService.open(ChildOneComponent, { size: 'xl' });
this.modalRef.componentInstance.testData = 'test';
break;
case 'two':
console.log("component Two function running...");
// do any execution before opening child component
this.modalRef = this.modalService.open(ChildTwoComponent, { size: 'xl' });
case default:
// do nothing
}
}
hideModalFunction() {
//do something before closing the modal
this.modalRef.close();
}
}
Now no OpenModalFunction() required inside the child components as you have extracted the modal opening logic into the MyModalService. This will also adhere to the Single Functionality principle means every component has only single functionality to be fulfilled. Furthermore, you have now full control via MyModalService to handle popups. If you want to execute a common code before/after opening/closing the windows like notifying another service, you could do that.
Now this could be further optimized as all software codes do. Like extracting common code from Child Components to a single interface/abstract class. But it totally depends upon your requirements.

Related

Change login button to logout in angular 13

I have a simple list with two buttons. I want to be able to show one or the other depending on whether I'm logged in.
<div>
<li class="nav-item">
<button *ngIf="token === ''" type="button" class="btn btn-dark btn-lg fs-4" (click)="login()">Inicia sesión</button>
<button *ngIf="token != ''" type="button" class="btn btn-dark btn-lg fs-4" (click)="logout()">Cerrar sesión</button>
</li>
</div>
I tried simply putting the ngIf but it doesn't make it instant, besides that since the log in is in another component I don't really know how to change that from there.
this is my component:
import { Component, ElementRef, ViewChild} from '#angular/core';
import { Router } from '#angular/router';
import { faHamburger } from '#fortawesome/free-solid-svg-icons';
import { UsersService } from './services/user.service';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'my-app';
token = this.userService.getToken();
#ViewChild('clickLogout')clickLogout:ElementRef;
faHamburger = faHamburger;
constructor(public userService: UsersService, public router: Router) { }
logout(){
this.userService.logout();
this.router.navigateByUrl('/login');
}
login(){
this.router.navigateByUrl('/login');
}
}
So as it is, I would need to reload the page every time I need one button or another and I need it to do it instantly when I log in or log out.
try to use that in html,
<div *ngIf="token === ''; else empty">
<button type="button" class="btn btn-dark btn-lg fs-4"
(click)="login()">Inicia
sesión</button>
</div>
<ng-template #empty>
<button type="button" class="btn btn-dark btn-lg fs-4"
(click)="logout()">Cerrar sesión</button>
</ng-template>
in ts call the token in the ngOnInit method and change the route (/login to /logout when you navigate) :
export class AppComponent implements OnInit {
//code...
token=''
ngOnInit(): void {
this.token = this.userService.getToken();}
You should use BehaviorSubject to store the token in the service, add:
public token$ = new BehaviorSubject<string>(null);
then use this.token$.next(valueFromRequest); when you will receive token value and
this.token$.next(null); to reset it.
In html code use ngIf in that way:
<button *ngIf="(userService.token$ | async)" type="button" [...]>Logout</button>
<button *ngIf="!(userService.token$ | async)" type="button"[...]>Login</button>

*ngFor in angular 5 bootstrap modal not working

Need some expert help to show api-answer data in a bootstrap modal.(the other thread about this in SO did not answer this question)
I have a .ts file that looks like below.
NgbdModal1Content and NgbdModalStacked are the ones I'm having trouble with. Ignore NgbdModal2Content & NgbdModal3Content and their components.
Also, I added a sleep part to make sure the api answer had come back and populated
getResultatFromApi3: string [] = [];
getResultatFromApi4: Observable<Station> [];
Before the modal is rendered. The getResultatFromApiX are console logged before modal is rendered.
import { Component, Injectable } from '#angular/core';
import { NgbActiveModal, NgbModal } from '#ng-bootstrap/ng-bootstrap';
import { GetApiService } from './get-api.service';
import { Observable } from 'rxjs/Observable';
import { Station } from './station';
import { Station2 } from './station2';
#Component({
selector: 'app-modal-stacked',
templateUrl: './modal-stacked2.html',
})
// tslint:disable-next-line:component-class-suffix
export class NgbdModal1Content {
constructor(private modalService: NgbModal, public activeModal: NgbActiveModal) {}
open() {
this.modalService.open(NgbdModal2Content, {
size: 'lg'
});
}
open2() {
this.modalService.open(NgbdModal3Content, {
size: 'lg'
});
}
}
#Component({
template: `
<div class="modal-header">
<h4 class="modal-title">Teststation 1</h4>
<button type="button" class="close" aria-label="Close" (click)="activeModal.dismiss('Cross click')">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<p>Kör test här!</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-dark" (click)="activeModal.close('Close click')">Close</button>
</div>
`
})
// tslint:disable-next-line:component-class-suffix
export class NgbdModal2Content {
constructor(public activeModal: NgbActiveModal) {}
}
#Component({
template: `
<div class="modal-header">
<h4 class="modal-title">Teststation 2</h4>
<button type="button" class="close" aria-label="Close" (click)="activeModal.dismiss('Cross click')">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<p>Kör test här!</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-dark" (click)="activeModal.close('Close click')">Close</button>
</div>
`
})
// tslint:disable-next-line:component-class-suffix
export class NgbdModal3Content {
constructor(public activeModal: NgbActiveModal) {}
}
#Component({
// tslint:disable-next-line:component-selector
selector: 'ngbd-modal-stacked',
templateUrl: './modal-stacked.html'
})
// tslint:disable-next-line:component-class-suffix
export class NgbdModalStacked {
constructor(private modalService: NgbModal, private _getApiService: GetApiService) {}
getResultatFromApi3: string [] = [];
getResultatFromApi4: Observable<Station> [];
getApiData: string [];
// Triggered when opening Modal (that contains two buttons for two other modals
open() {
this._getApiService.method3Call().subscribe(function(data) {
console.log('Test från Y-Tube-videon', data);
this.getResultatFromApi4 = data;
this.getResultatFromApi3 = data;
console.log(this.getResultatFromApi4);
console.log(this.getResultatFromApi3);
});
this.delay(5000).then(any => {
this.modalService.open(NgbdModal1Content);
console.log('du klickade på Teststationer');
});
}
async delay(ms: number) {
await new Promise(resolve => setTimeout(() => resolve(), ms)).then(() => console.log('fired'));
}
}
My api call comes back with information when I trigger Open() in NgbdModalStacked part and answer looks like this, from console log:
I moved the NgbdModal1Content hmtl part to a separate html file to make it easier. That html file looks like this:
<div class="modal-header">
<h4 class="modal-title">Teststationer</h4>
<button type="button" class="close" aria-label="Close" (click)="activeModal.dismiss('Cross click')">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<p><button class="btn btn-lg btn-outline-primary" (click)="open()">Teststation 1 {{getResultatFromApi4}}</button></p>
<p><button class="btn btn-lg btn-outline-primary" (click)="open2()">Teststation 2</button></p>
<p>hej2<p>
<ul *ngFor="let data of getResultatFromApi3">
<li>Reported: {{data.Name}} <span>Mer info</span></li>
</ul>
<table>
<tr *ngFor="let data of getResultatFromApi3">
<td>{{data.Name}}</td>
</tr>
</table>
<ul>
<li *ngFor="let data of getResultatFromApi4">
{{data.Name}}
</li>
</ul>
<ul>
<li *ngFor="let data of getResultatFromApi4">
{{data.Name}}
</li>
</ul>
<p>hejigen2<p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-dark" (click)="activeModal.close('Close click')">Close</button>
</div>
But it doesn't output the data (see pic below) and I can't understand what I'm doing wrong? Tried data.Name and data.name, have both string and object array but api answer is not displayed but the other things in p are displayed.
HOW DO I MAKE THE DATA.NAME SHOW?
Thank you
public getResultatFromApi10 = [];
this._getApiService.method3Call().subscribe(
data => data.forEach(item => {this.getResultatFromApi10.push(item); })
);
This code made it work for me. The other subscribe would output info in the console.log but not carry any data from .ts to the html-part wheras the push(item) seem to have done the trick.
Hope this can help somebody else who might run into the same problem.

basic-dialog component is not working fine with async call

I have created an basic-dialog component to display popup on screen for confirmation. This component is not working with asynch call. I want to display popup after receiving service response. I am using Angular 6 and Bootstarap 3.3
Following is my basic component ts file:-
#Component({
selector: 'basic-dialog',
styleUrls: ['./dialog.component.scss'],
templateUrl: './dialog.component.html',
animations: [
trigger('dialogState', [
transition(':enter', [
style({ transform: 'translateY(-100%)' }),
animate('900ms ease-in', style({ transform: 'translateY(0%)' }))
]),
transition(':leave', [animate('100ms ease-in', style({ transform: 'translateY(-100%)' }))])
])
]
})
export class DialogComponent implements OnChanges, OnDestroy {
#Input() visible = false;
#Input() modalId = '';
#Input() styles: any;
constructor(
#Inject(DOCUMENT) private document: Document,
private renderer: Renderer2,
private element: ElementRef,
private changeDetectorRef: ChangeDetectorRef
) {}
ngOnChanges(): void {
if (this.visible) {
this.element.nativeElement.querySelector('.modal').id = this.modalId;
document.querySelector('body').appendChild(this.element.nativeElement);
this.changeDetectorRef.detectChanges();
}
}
ngOnDestroy(): void {
if (this.element.nativeElement.parentElement.tagName.toUpperCase() === 'body'.toUpperCase()) {
document.querySelector('body').removeChild(this.element.nativeElement);
if (document.querySelector('.modal-backdrop')) {
document.querySelector('body').removeChild(document.querySelector('.modal-backdrop'));
}
}
}
}
HTML file :-
<div class="modal fade" [id]="modalId" role="dialog" aria-label="close" style="display: none;">
<div class="modal-dialog" [ngStyle]="styles">
<div class="uil-dialog-card x-overflow-hidden">
<!-- <button *ngIf="closable" (click)="close()" aria-label="Close" type="button" class="close" data-dismiss="modal">
<img class="svg" src="../assets/theme/img/cross_icon.svg">
</button>-->
<div id="modal_content">
<ng-content></ng-content>
</div>
</div>
</div>
</div>
dashboard file ts file:-
ngOnInit(): void {
this.visible = false;
this.loginService.dashboardModuleChangeMsg.subscribe(data => {
this.displayModuleChangePopup(data);
});
}
/** To display Dialog box */
displayModuleChangePopup(data): void {
this.visible = true;
this.changeDetectorRef.detectChanges();
}
/** Event handler for continue button in Dialog box */
onSubmitHandler(): void {
// code
}
/** Event handler for cancel button in Dialog box */
onCancelHandler(): void {
// code
}
dashboard html file :-
<basic-dialog *ngIf="visible" [visible]="visible" [modalId]="MODULE_SWITCH_DIALOG" [styles]="{'max-width':'434px'}">
<div class="row">
<div class="uil-card-title-area" style="margin-bottom:20px">
<div class="uil-card-title">
Module Navigation
</div>
</div>
<div id="text_inputs_body">
You are trying to navigate to another module. Any unsaved data will be lost.
</div>
<div class="alignRight">
<button type="button" data-dismiss="modal" class="uil-btn-flat uil-flip-button" (click)="onCancelHandler(user)">Cancel</button>
<button type="button" data-dismiss="modal" class="uil-btn uil-tooltip" (click)="onSubmitHandler(user);">Continue</button>
</div>
</div>
</basic-dialog>
Popup is not getting displayed on first time, but when second time subscriber triggered popup is getting display.
If i run this popup without async call, it is working fine.

Angular: passing attributes to Dynamic component template

I have a component created, and I want to pass an attribute though the component to the template.
This is the component:
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-card-generator',
templateUrl: './card-generator.component.html',
styleUrls: ['./card-generator.component.css'],
inputs: ['id', 'collapseid', 'headingid','collapsehashid']
})
export class CardGeneratorComponent implements OnInit {
bindings: {
headingid:'#?',
collapseid:'#?',
collapsehashid
}
constructor() { }
ngOnInit() {}
and this is the template:
<button class="btn btn-link collapsed" style="text-decoration:none;" type="button" data-toggle="collapse" data-target="#collapseTwo" aria-expanded="false" aria-controls="collapseTwo">
{{id}}
</button>
</h5>
</div>
<div id="collapseTwo" class="collapse" aria-labelledby="headingTwo" data-parent="#accordionExample">
<div class="card-body"></div>
</div>
created components are placed in home.component this way:
<div id="cardDivContainer" >
<app-card-generator id="Chart 1" collapsehashid="data-target='#collapseOne'" collapseid="aria-controls='collapseOne'" headingid="aria-labelledby='headingOne'"></app-card-generator>
<app-card-generator id="Chart 2" collapsehashid="data-target='#collapseTwo'" collapseid="aria-controls='collapseTwo'" headingid="aria-labelledby='headingTwo'"></app-card-generator>
</div>
I just need to set the "data-target", "aria-labelledby", and the "aria-controls" attributes for each component (depending on the component ID).
I am new to Angular, I hope the above make sense.
You can use #Input to reference the elements within the component. The elements can be passed as parameter outside. Something like:
import {
Component,
OnInit,
Input,
Output,
EventEmitter
} from '#angular/core';
#Component({
selector: 'app-card-generator',
styleUrls: ['./card-generator.component.css'],
templateUrl: './card-generator.component.html'
})
export class CardGeneratorComponent implements OnInit {
#Input() id: string;
#Input() collapseid: string;
#Input() headingid: string;
#Input() collapsehashid: string;
constructor() {
}
ngOnInit() {}
}
If the attributes do not exist in the element you are referencing, you can use attr.attribute and use the {{}} notation
<button class="btn btn-link collapsed" style="text-decoration:none;" type="button" data-toggle="collapse" attr.data-target="{{'#' + collapseid}}" aria-expanded="false" attr.aria-controls="collapseid">
</button>
<div id="{{collapseid}}" class="collapse" attr.aria-labelledby="{{headingid}}" data-parent="#accordionExample">
<div class="card-body"></div>
</div>
And finally you can access the attributes created in your component in the call from the outside
<div id="cardDivContainer" >
<app-card-generator id="Chart 1" collapsehashid="collapseOne" headingid="headingOne"></app-card-generator>
<app-card-generator id="Chart 2" collapsehashid="collapseTwo" headingid="headingTwo"></app-card-generator>
</div>
More #Input and #Output details in this link:
https://stackblitz.com/edit/card-generator-sample?file=src%2Fapp%2Fapp.component.html

Ngx Bootstrap Template Modal Destroyed / Removed From Dom When Hidden

import { Component, TemplateRef } from '#angular/core';
import { BsModalService } from 'ngx-bootstrap/modal';
import { BsModalRef } from 'ngx-bootstrap/modal/modal-options.class';
#Component({
selector: 'demo-modal-service-static',
templateUrl: './service-template.html'
})
export class DemoModalServiceStaticComponent {
public modalRef: BsModalRef;
constructor(private modalService: BsModalService) {}
public openModal(template: TemplateRef<any>) {
this.modalRef = this.modalService.show(template);
}
}
Create template modal
<template #template>
<div class="modal-header">
<h4 class="modal-title pull-left">Modal</h4>
<button type="button" class="close pull-right" aria-label="Close" (click)="modalRef.hide()">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
This is a modal.
</div>
</template>
http://valor-software.com/ngx-bootstrap/#/modals#directive-section
Hi, as seen above, creating modal with template will destro the template when, it is hidden. However; I don't want to be hidden. That is because, I use the modal for selecting some property multiple times from a list. Thus, every time, I have to load the modal. That causes a performance issue. Thus, Is there any way to stop it removing the modal from the dom.

Categories

Resources