Facing problem with opening and displaying a modal in my Angular4 .NET Application. I would click a link and consecutively a modal would show. In my case the date link for invoice number [pl see the image].
I followed the approach shown here -- http://jasonwatmore.com/post/2017/01/24/angular-2-custom-modal-window-dialog-box
Now what I have currently is, my opaque screen blocking the background but the modal is not displaying as I was hoping for. Like this
I don't know why the modal didn't appear. I am guessing z-index problem maybe? Cause I do not see any console errors. So probably not angular code related matter. Most likely CSS is what I feel. My main app screen is divided into 2 segments as you can see, col-sm-3 and col-sm-9 body content.
Basically this is what I wrote to test my code.
my main app window layout -
<div class='container-fluid'>
<div class='row'>
<div class='col-sm-3'>
<nav-menu></nav-menu>
</div>
<div class='col-sm-9 body-content'>
<alert-component></alert-component>
<router-outlet></router-outlet>
</div>
</div>
</div>
my modal related html --
<div class="col-md-4" style="border-radius:8px; background:linear-gradient(50deg, #e1ecfa, #f2fbde); text-align:right; margin-left:12px; padding:10px;">
<b style="color:darkblue">Invoices issued to this customer</b>
<ul style="list-style:none" *ngFor="let i of iObj">
<li (click)="openInvoiceModal('custom-modal-1')" class="glyphicon glyphicon-arrow-right">
<a>
<b>{{i.inv_id}}, on {{i.inv_date}}</b>
</a>
</li>
</ul>
</div>
*** my test modal ***
<modal id="custom-modal-1">
<div class="modal">
<div class="modal-body">
<h1>Invoice Modal!</h1>
<p>
Home page text: Hello There!
</p>
<button (click)="closeModal('custom-modal-1');">
Close
</button>
</div>
</div>
<div class="modal-background"></div>
</modal>
typescript with this page --
openInvoiceModal(id: string) {
this.modalService.open(id);
}
closeInvoiceModal(id: string) {
this.modalService.close(id);
}
All the other files and code are the same as has been written in that link/tutorial. I tried experimenting at one place with z-index also. But it didn't serve the purpose. So I am baffled.
A few alterations in the modalcomponent file also according to my layout css etc, so I am posting it here.
export class ModalComponent implements OnInit, OnDestroy {
#Input() id: string;
private element: JQuery;
constructor(private modalService: ModalService, private el: ElementRef) {
this.element = $(el.nativeElement);
}
ngOnInit(): void {
let modal = this;
// ensure id attribute exists
if (!this.id) {
console.error('modal must have an id');
return;
}
this.element.appendTo('.container-fluid');
this.element.on('click', function (e: any) {
var target = $(e.target);
if (!target.closest('.modal-body').length) {
modal.close();
}
});
this.modalService.add(this);
}
// remove self from modal service when directive is destroyed
ngOnDestroy(): void {
this.modalService.remove(this.id);
this.element.remove();
}
open(): void {
this.element.show();
$('.container-fluid').addClass('modal-open');
}
// close modal
close(): void {
this.element.hide();
$('.container-fluid').removeClass('modal-open');
}
}
I am not sure why the modal itself is not showing. Although the opaque background is being called means - I am going the right way. ALMOST!
What am I missing? Where is the glitch? Surely it has to be some small tricky part that I am failing to grab! Kindly guide me.
Let me know if you need more code stubs from me to understand my scenario. I will be happy to share.
In anticipation,
Related
I am learning Angular, and I try to create a small project. I use the following code:
TS:
export class Basic implements OnInit {
products:any[];
hide: boolean = true;
constructor() {
this.products=[
{
name:"Mac Book Pro",
price:"1000",
color:"grey"
},
{
name:"iPhone",
price:"700",
color:"black"
}
]
}
ngOnInit(): void {
}
toggle(){
this.hide=!this.hide;
}
}
HTML:
<div *ngFor="let x of products">
<div (click)="toggle()">{{x.name}}</div>
<div [hidden]="hide">
{{x.price}}
{{x.color}}
</div>
</div>
So, the code produces a list of products. My aim is once I click on one product to get its additional information below it. But I can't figure out how to make it only for one element. Currently, when I press on the first element, it also shows the information for the second.
The problem is that you have one varibale hide for all of items. So you can do that by a extra property say isOpen in you model:
<div *ngFor="let x of products;">
<div (click)="toggle(x)">{{x.name}}</div>
<div [hidden]="!x.isOpen">
{{x.price}}
{{x.color}}
</div>
</div>
toggle(x){
x.isOpen = !x.isOpen
}
Here is working sample I created for you: Stackblitz
I'm working on an Angular 9 project and I should activate a toggle when going back to a specific page and only in that case.
So actually when I go back from page localhost:4200/y to page localhost:4200/x, I turn on the toggle, otherwise if I go through router.navigate to page localhost:4200/x, the toggle should stay turned off.
The go back button typescript is like this:
export class BackToPreviousComponent() {
#Input() labelPrevious: string;
back(): void {
history.back();
}
}
this is its HTML:
<a (click)="back()" class="btn btn-nav">
<i class="icon-back pr-1"></i>
{{ labelPrevious }}
</a>
and this is how is fitted in the application:
<div class="component">
<app-back-to-previous [labelPrevious]="'random text'"></app-back-to-previous>
...
</div>
So how can I tell the application to turn on the toggle only in this case?
I am trying to add a basic MatDialog to my project. In the project I have 2 components, a header for the page and another called "CardBox", which basically just holds cardboxes of links to different websites.
When you click on the "i" icon, I would like to open a dialog box with more information.
See image below.
Initially, my understanding was that I just add a MatDialog field in the constructor of Cardbox component. Like so:
cardboxes.component.html
<mat-card id="CARDBOX">
<img class="info" src="path/image.jpg" alt="image" height=25px (click)="openDialog()"/>
</mat-card>
cardboxes.component.ts
#Component({
selector: 'app-cardbox',
templateUrl: './cardbox.component.html',
styleUrls: ['./cardbox.component.scss']
})
export class CardboxComponent implements OnInit {
constructor(private dialog: MatDialog) { }
ngOnInit(): void {}
openDialog() {
this.dialog.open(CardBoxComponent);
}
}
(I'm aware that this is calling its own component, and would just open the same thing again. I am just trying to get it to work first.)
app.component.html
<div id="bg">
<app-header></app-header>
<br>
<app-cardbox></app-cardbox>
</div>
However, in doing so, it removes EVERYTHING from the page except the background, including the header component. This is what it looks like when the program is run when there is SOMETHING in the constructor of Cardbox.
As you can see, having something in the constructor gets rid of everything on the page, which does not make sense to me as it removes the header, which is a completely separate component from the cardbox. I have tried everything to make it work but still it is not working.
Why is touching the constructor makes the entire project blank? Is there something I forgot to add to another file? And how can I add a MatDialog popup feature to the project in a way that works?
TLDR: When I put anything in the constructor of one of my components, the entire page disappears. How do I resolve this?
Still seeking answer to this :(
You are using it wrong.
I am surprised your app compiles when doing this.dialog.open(CardBoxComponent)
What you need to do is, first create your dialog component.
To make things simple you can create it in the same file as you CardBox component, but make sure you put it outside CardBox class:
cardboxes.component.ts
#Component({
selector: 'dialog-overview-example-dialog',
templateUrl: 'dialog-overview-example-dialog.html',
})
export class DialogOverviewExampleDialog {
constructor(
public dialogRef: MatDialogRef<DialogOverviewExampleDialog>,
// data is gonna be the data you pass to dialog when you open it from CardBox
#Inject(MAT_DIALOG_DATA) public data: DialogData) {}
onNoClick(): void {
this.dialogRef.close();
}
}
then you create a template for the dialog component:
dialog-overview-example-dialog.html
<h1 mat-dialog-title>more info</h1>
<div mat-dialog-content>
<p>{{data.info}}</p>
</div>
finally you add openDialog(myInfo) function to your ts file, inside CardBox component:
cardboxes.component.ts
openDialog(myInfo): void {
const dialogRef = this.dialog.open(DialogOverviewExampleDialog, {
width: '250px',
// data you pass to your dialog
data: {info: myInfo}
});
dialogRef.afterClosed().subscribe(result => {
console.log('The dialog was closed');
this.animal = result;
});
}
and add it to your template too:
cardboxes.component.ts
<mat-card id="CARDBOX">
<img class="info" src="path/image.jpg" alt="image" height=25px (click)="openDialog('info about first site')"/>
</mat-card>
in this example I pass the info as a text, but it can be an object too.
Here is a demo to make things easier for you: link
I have a modal and I want a progress bar to be shown while the data for modal display is being fetched by the service call. But in this case, progress bar is being fetched first and then the modal which makes the progress bar to be displayed under the modal. How to fix this ?
this.service.searchMembers(memSearchJson).subscribe((response: any) => {
// some function
}
<modal id="custom-modal-2">
<div class="modal">
</div>
<div id="memberSearchBar" class="class-hide">
<mat-spinner></mat-spinner>
Finding Member IDs..
</div>
</modal>
If document.getElementById("memberSearchBar").className = 'loading-div'; is called before the service call, it throws error as className null. Where should I call this to display progress bar on modal?
I would recommend using *ngIf, as mentioned above. For a better understanding, I add a link to the perfect tutorial.
Loading spinner
For example: in ts:
showSpinner: boolean = true;
ngOnInit() {
this.spinnerShow();
}
if you need show spinner before you get data like from service.
spinnerShow(){
this.workflowService.getData().subscribe(()=>
this.showSpinner = false);
}
And in HTML
<div *ngIf="showSpinner"></div>
Sorry for my terrible english, but i hope this will help you. :)
Instead of doing document.getElementById("memberSearchBar").className = 'loading-div' you can take advantage of Angular's *ngIf and [class.class-name]="expression"
Try something like this instead
displayModalAndSearchMembers(){
// using only isLoading should be sufficient, but I wanted to show how you could use [class.classname]="expression" in the html template as well.
this.isLoading = true;
this.showSpinner = true;
this.displayModal = true;
this.service.searchMembers(memSearchJson).subscribe((response: any) => {
// some function
this.isLoading = false;
this.showSpinner = false;
}
}
<modal id="custom-modal-2">
<div *ngIf="displayModal" class="modal"> <!-- added *ngIf -->
</div>
<div id="memberSearchBar" *ngIf="showSpinner" [class.loading-div]="isLoading">
<mat-spinner></mat-spinner>
Finding Member IDs..
</div>
</modal>
EDIT
You could also use *ngIf="displayModal" on the modal as well, in case you have some display: none on that one as well.
I added some ts code as well. I am not 100% sure what the desired behaviour OP wants, but I am assuming that she wants to show/hide the spinner, and set a class name on it.
This can be simplified by using only *ngIf="isLoading":
<div id="memberSearchBar" class="loading-div" *ngIf="showSpinner">
My question is how to show a loading spinner until all of my async http requests are completed. This way I wouldn't show bits and pieces of the screen until all of the data is received from the server.
My biggest issue is that I have components that are triggered specifically through the html, so I can't simply put an *ngIf statement over part of the html when I want to show it.
Here's what I have so far. FYI, the Template variable that currently triggers the visibility of the html is set when one of the http requests complete in this component. I want to wait for the child component's http requests to complete before showing the html, but I must execute the logic in the html in order to call the child components.
The *ngIf statement does NOT currently work in the way I desire, I'm just showing what I'm currently doing.
<div class="col-sm-12"
*ngIf="Template">
<div id="nav" style="height: 200px">
<div id="outer"
style="width: 100%">
<div id="inner">
<o-grid>
</o-grid>
</div>
</div>
</div>
<collapsible-panel *ngFor="let s of Template?.s; let i = index"
[title]="s.header">
<div *ngFor="let c of s.c">
<fact [eC]="c.c"
[label]="c.l">
</fact>
</div>
</collapsible-panel>
<collapsible-panel title="T">
<div>
<i-f >
</i-f>
</div>
</collapsible-panel>
</div>
<div *ngIf="!Template" class="spinner"></div>
EDIT (SOLUTION): Here's the solution I implemented, per the answer below from #danday74.
I instantiated the variable inside of my service where I make all of my http requests. I defined it as true to start, and set it to false in one of the child components when the subscribe completes.
I'll just need to make sure in the future to set cService.asyncRequestsInProgress to false wherever the last async http request takes place, if it ever changes.
Parent HTML:
<div class="col-sm-12"
[ngClass]="{hideMe:cService.asyncRequestsInProgress}">
......
</div>
<div *ngIf="cService.asyncRequestsInProgress" class="spinner"></div>
Service:
#Injectable()
export class CService {
asyncRequestsInProgress: boolean = true;
constructor(public http: HttpClient) { }
}
Child Component (Where the last async request completes):
export class FComponent implements OnInit {
....
doSomething() {
this.cService.getWhatever().subscribe(x => {
this.cService.asyncRequestsInProgress = false;
}
}
}
styles.css
.hideMe {
visibility: hidden;
}
You could use a resolver. A resolver ensures data is loaded before the component loads.
Alternatively, if you don't want to use *ngIf you could just use [ngClass]="{hideMe: allAsyncRequestsComplete}" to style the bit you don't want to show until loading is complete. CSS might be:
.hideMe {
visibility: hidden;
}
And set allAsyncRequestsComplete to true when loading is done.
You can use resolvers for the loading, then in app.component.ts, set your variable to true or false depending on the event:
navigationInterceptor(event: RouterEvent): void {
if (event instanceof NavigationStart) {
//true
}
if (event instanceof NavigationEnd) {
//false
}
// Set loading state to false in both of the below events to hide the spinner in case a request fails
if (event instanceof NavigationCancel) {
//false
}
if (event instanceof NavigationError) {
//false
}
}