How to prevent MatDialog from closing when clicked outside but in all the dialogs in my Angular application ? I also realized that the escape key is not closing the dialog after setting disableClose to true so i added a hostlistener to force the close but it's not really the best solution...
For a specific component I can do this.
export class MyAppComponent {
constructor(private dialog: MatDialog){}
open() {
this.dialog.open(ConfirmComponent, { disableClose: true });
}
#HostListener('window:keyup.esc') onKeyUp() {
this.dialogRef.close();
}
}
but how to do it globally for all the dialogs in my application instead of doing it in each dialog component and also apply the hostlistener?
You can define global setting in provider
import { MAT_DIALOG_DEFAULT_OPTIONS } from '#angular/material/dialog';
#NgModule({
providers: [
{ provide: MAT_DIALOG_DEFAULT_OPTIONS, useValue: { disableClose: true }}
]
})
Related
I have a component which is part of a lazy load module.
Is there a way to matDialog.open() and lazy load the module and show the component?
export class testComponent implements OnInit {
constructor(
public matDialog: MatDialog,
private moduleLoader: NgModuleFactoryLoader
) {}
ngOnInit() {}
openModal() {
this.moduleLoader
.load("./modules/test-modal/test-modal.module#TestModalModule")
.then((module: NgModuleFactory<any>) => {
this.matDialog.open(/*insert component and load the module*/);
});
}
}
I found an example to lazy load module with component in mat-dialog.
Please see refer to:
https://medium.com/ngconf/routing-to-angular-material-dialogs-c3fb7231c177
Just in case the link is no longer available, i'd included a brief step and example to do it
1. Create a lazy load module
2. Create entry component(empty component) to launch your modal component
#Component({
template: ''
})
export class DialogEntryComponent {
constructor(public dialog: MatDialog, private router: Router,
private route: ActivatedRoute) {
this.openDialog();
}
openDialog(): void {
const dialogRef = this.dialog.open(DialogOverviewExampleDialog, {
width: '250px'
});
dialogRef.afterClosed().subscribe(result => {
this.router.navigate(['../'], { relativeTo: this.route });
});
}
}
3. Create a route for the lazy load module
const routes: any = [
{
path: "",
component: modalComponent(actual component with content)
}
];
#NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
providers: [DataResolver]
})
export class DialogEntryRoutingModule {}
4. At parent router module, include path to lazy load DialogEntryModule
RouterModule.forRoot([
{
path: 'home',
component: ParentComponent,
children: [
{
path: 'dialog',
loadChildren:
"../example/entry-dialog.module#DialogEntryModule"
}
]
},
{ path: '**', redirectTo: 'home' }
])
5. in ParentComponent open the modal by directing to the DialogEntryModule
<button mat-raised-button routerLink="dialog">Pick one</button>
Another alternative is to stick the mat dialog component in another module that has a route, assuming it isn't used by any other module.
For example, if you have app.module and a projects.module, and you have a mat dialog that displays project details, you could include the project details dialog component inside of projects.module instead of creating a separate module for it. The dialog code will load when the user navigates to the projects view.
#nicker's answer runs into issues when you close the dialog. This reloads the parent component and in some cases, you don't want the parent component view to be refreshed.
Is it possible to generate a html file from a component by bypassing all the data it needs without actually rendering it in the browser viewport?
I would like to just generate some html code to send it to the backend that generates a PDF from this html.
I don't think you can, since rendering of angular's components relies heavily on it's lifecycle hooks.
I can think of one way to fake it, though, by:
instantiating an invisible component from code
add it to the DOM, so it behaves like any regular component
retrieve it's HTML
and finally destroy it
Here's a working code example.
app.module.ts
Notice that i've added PdfContentComponent to the entryComponents of the module.
This is required for any component that you want to instantiate from code.
#NgModule({
imports: [ BrowserModule, FormsModule ],
declarations: [ AppComponent, PdfContentComponent ],
bootstrap: [ AppComponent ],
entryComponents : [ PdfContentComponent ]
})
export class AppModule { }
pdf-content.component.html
<span>Hello, {{ name }}</span>
pdf-content.component.ts
Notice the host: {style: 'display: none'}, this renders the component effectivly invisible
#Component({
selector: 'my-pdf-content',
templateUrl: './pdf-content.component.html',
host: {
style: 'display: none'
}
})
export class PdfContentComponent implements OnInit {
#Input() name: string;
#Output() loaded: EventEmitter<void> = new EventEmitter<void>();
constructor(public element:ElementRef) {
}
ngOnInit() {
this.loaded.emit();
}
}
app.component.html
<button (click)='printPdf()'>Hit me!</button>
<ng-container #pdfContainer>
</ng-container>
app.component.ts
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
// the pdf content will be briefly added to this container
#ViewChild("pdfContainer", { read: ViewContainerRef }) container;
constructor(
private resolver: ComponentFactoryResolver
) {}
printPdf() {
// get the PdfContentComponent factory
const factory: ComponentFactory<any> = this.resolver.resolveComponentFactory(PdfContentComponent);
// instantiate a PdfContentComponent
const pdfContentRef = this.container.createComponent(factory);
// get the actual instance from the reference
const pdfContent = pdfContentRef.instance;
// change some input properties of the component
pdfContent.name = 'John Doe';
// wait for the component to finish initialization
// and delay the event listener briefly,
// so we don't run the clean up in the middle of angulars lifecycle pass
const sub = pdfContent.loaded
.pipe(delay(1))
.subscribe(() => {
// stop listening to the loaded event
sub.unsubscribe();
// send the html to the backend here
window.alert(pdfContent.element.nativeElement.innerHTML);
// remove the component from the DOM
this.container.remove(this.container.indexOf(pdfContentRef));
})
}
}
You can use Renderer2 class provided by angular. Try this sample code;
import { Component, Renderer2, OnInit } from '#angular/core';
....
constructor(private renderer: Renderer2){
}
ngOnInit(){
const div: HTMLDivElement = this.renderer.createElement('div');
const text = this.renderer.createText('Hello world!');
this.renderer.appendChild(div, text);
console.log(div.outerHTML);
}
My tabs.ts (simpilified) - data used to generated tabs with *ngFor is brought from php backend:
import ...
export interface Group {
id: number;
group: string;
};
#Component( {
template: `
<ion-tabs #myTabs selectedIndex="0">
<ion-tab *ngFor="let tab of userGroups" [root]="page" [rootParams]="tab.id" [tabTitle]="tab.group" tabIcon="pulse"></ion-tab>
</ion-tabs>
`
})
export class GroupsTabsPage {
userGroups: Group[];
page: any = TabStudentsPage;
constructor( public app: App, public api: Api, public navParams: NavParams ) {
this.api.getGroupsList()
.subscribe(
data => {
this.userGroups = data;
},
err => {
this.app.getRootNav().push( LoginPage )
}
);
// ionViewDidEnter() {
// }
}
}
The result is invisible tabs. But when you hover your mouse ovet them, the cursor changes into 'hand' and you can click them. When clicked, the whole tabs bar becomes visible and all works as expected.
When I used #ViewChild to refer to the tabs elements, the interesting thing is that its 'length' property is always 0 (I checked in ionViewDidLoad event). Trying to select one of the tabs programatically also fails - they are like ghosts;)
Also when you place at least one static tab next to *ngFor ones in the template, all *ngFor ones show up but the static is always selected no matter what you select programatically or in selectedIndex property on tabs element.
Any idea guys? I've wasted three days..
that's a known bug, take a look at the element css, the subview's .tabbar has opacity of 0. I've just fixed it with a an override of opacity: 1. Ugly, but works...
Creating ion-tab from observable (dynamically) has some bugs (duplicates, wrong rendering etc) I use a workaround to avoid it, it consist of removing and loading the ion-tabs runtime every time then observable changes.
Parent template:
<div #pluginTabContainer></div>
Parent component:
#ViewChild("pluginTabContainer", {read: ViewContainerRef}) pluginTabContainer:ViewContainerRef;
...
plugins$.subscribe((pluginTabs:Array<PluginTabType>) => { let componentFactory = this.componentFactoryResolver.resolveComponentFactory(PluginTabContainerComponent); this.pluginTabContainer.clear(); this.pluginTabContainertRef = this.pluginTabContainer.createComponent(componentFactory); this.pluginTabContainertRef.instance.data = pluginTabs;
...
ngOnDestroy() { this.pluginTabContainertRef.destroy(); }
Loaded ion-tabs template:
<ion-tabs> <ion-tab *ngFor="let tab of data" [root]="'PluginTabPage'" [rootParams]="tab"></ion-tab> </ion-tabs>
Loaded ion-tabs component (getting parameter):
#Input() data: PluginTabType;
Hope will be helpful for you.
I had a similar issue during development and I was able to solve this by making ngOninit async and calling a timeout to set the selected tab.
view
<ion-tabs #ctrlPanelTabs class="tabs-basic">
<ion-tab *ngFor="let appTab of appTabs" tabTitle={{appTab.name}} [root]="rootPage"></ion-tab>
</ion-tabs>
1) ngOninit is async
2) this.ctrlPanelTabs.select(0); is set inside a timeout function
import { Component, OnInit, ViewChild } from '#angular/core';
import { NavController, Tabs } from 'ionic-angular';
import { AppSettings } from '../../common/app.config';
import { AppTab } from '../../models/app-tab';
import { AppTabService } from '../../services/app-tab.service';
import { PanelTabComponent } from './panel-tab';
#Component({
selector: 'page-control-panel',
templateUrl: 'control-panel.html',
providers: [AppTabService]
})
export class ControlPanelPage implements OnInit {
#ViewChild("ctrlPanelTabs") ctrlPanelTabs: Tabs;
appTabs: AppTab[] = [];
message: string;
rootPage = PanelTabComponent;
constructor(public navCtrl: NavController,
private appTabService: AppTabService) {
console.log("Control Panel Page : Constructor called..");
}
async ngOnInit() {
console.log("Control Panel Page : Entering ngOninit..");
await this.loadAppTabs();
setTimeout(() => {
this.ctrlPanelTabs.select(0);
}, 100);
console.log("Control Panel Page : Exiting ngOninit..");
}
async loadAppTabs() {
console.log("Control Panel Page : Entering loadAppTabs..");
await this.appTabService.getAppTabsHierarchyBySlaveDeviceId(AppSettings.selSlaveDeviceId)
.then((response: any) => {
this.appTabs = JSON.parse(response.result);
console.log(this.appTabs);
console.log("Control Panel Page : Exiting loadAppTabs..");
});
}
}
How can I create simple indicators loading button with directive feature in angular2, so then can be use everywhere.also with control access in parent component?
// Something like this
<button loading [waitUntil]="listenToParent">Submit</button>
You can create a directive for the same like this:
#Directive({
selector:'[loading]',
inputs: ['waitUntil']
})
class Loading {
private dataPromise: Promise;
constructor(el: ElementRef){
}
set waitUntil(data:Promise) {
this.dataPromise = data;
this.dataPromise.then(function(message) {
alert(message);
})
}
}
Component for the implementation of the same:
#Component({
selector: 'my-app',
template: `
<h2>Hello World</h2>
<button loading [waitUntil]="thePromise">button</button>`,
providers: [],
directives: [Loading]
})
export class App implements ngAfterViewInit{
name:any;
thePromise: Promise ;
constructor() {
this.thePromise = new Promise((resolve, reject) => {
setTimeout(function() { resolve("API call complete hide loader.");}, 1000);
});
}
ngAfterViewInit(){
}
}
From above example, you can see how a promise that was declared in the parent was passed and fulfilled in the directive, in the constructor of the directive you get the elementRef which can be used to manipulate the element, so you can show a loading symbol or disable the button element till the promise is fulfilled, once is promise is fulfilled the button can be enabled etc. depending on the requirement.
Plnkr for the same: http://plnkr.co/edit/IptHfR?p=preview
So I put in app.settings like
public static get DateFormat(): string { return 'MM/DD/YYYY';}
and then in one of my html template of a component I want to do something like this.
<input [(ngModel)]="Holiday" [date-format]="AppSettings.DateFormat"/>
In component I have
import { AppSettings } from '../../../app.settings';
Right now it's not accessible like this in html template. Is there any way?
No, the scope for code in the template is the component instance. You need to assign the value to a field in the component, or add a getter or method that forwards to the global variable in order to be able to use it from the template.
import { AppSettings } from '../../../app.settings';
...
export class MyComponent {
get dateFormat() {
return AppSettings.DateFormat;
}
}
then you can use it like
<input [(ngModel)]="Holiday" [date-format]="dateFormat"/>
It seems hacky but you can use pipe. It saves you from repeating injection or variable binding for each component.
#Pipe({
name: 'settings',
})
export class GlobalVariablePipe implements PipeTransform {
transform(value: any): object {
return AppSettings;
}
}
Then, once imported in your module, you can simply use the pipe as follows:
{{(''|settings).DateFormat}}
To the best of my knowledge, no, and that's by design. Templates are hooked up in conjunction with components, that's how they derive their scope and thereby access to bindable data. It's possible it could be hacked around, but even if you figure it out, this is a path you should not follow.
I do this sort of thing with a class:
class AppConfig {}
AppConfig.Globals = {
TEST_VAL: 'hey now here is the value'
};
export { AppConfig }
And then use it like so:
import { Component } from '#angular/core';
import { AppConfig } from '../../app/app.config';
class HomeComponent {
constructor() {
this.test_val = AppConfig.Globals.TEST_VAL;
}
}
HomeComponent.annotations = [
new Component ( {
templateUrl: './views/home/home.component.html'
} )
];
export { HomeComponent }
In the template:
{{test_val}}
Works well for me.
It seems an old topic. However here is my 2 cents. I used a service instead and it's working. I come up with something like this : <input [(ngModel)]="Holiday" [date-format]="appSettingService.DateFormat"/>. The inconvenient of this solution, you have DI the service.
Here is what I did :
appSettingService.ts
import { Injectable } from '#angular/core';
#Injectable({
})
export class AppSettingService{
constructor() { }
public DateFormat(): string { return 'MM/DD/YYYY';}
...
In your component :
...
constructor(public appSettingService : AppSettingService ) { }
...
And finally your in your template:
<input [(ngModel)]="Holiday" [date-format]="appSettingService.DateFormat"/>
I'm looking for a way to use the value without the suffixe like this:
<input [(ngModel)]="Holiday" [date-format]="DateFormat"/>
Step 1. Define global json globals.json
{
"LABEL": "Your Text"
}
Step 2. Define Injectable Globals class globals.ts and add to providers in app.module.ts
import { Injectable } from '#angular/core';
import jsonGlobals from './globals.json';
#Injectable()
export class Globals {
prop: any = jsonGlobals;
}
in app.module.ts
providers: [Globals]
Step 3. Inject in component's constructor
constructor(public globals: Globals) { }
Step 4. Add following compiler properties in tsconfig.json
"resolveJsonModule": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true
Step 5. Use in HTML templates
{{globals.prop.LABEL}}