Angular 5 generate HTML without rendering - html

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);
}

Related

ViewChild of script tag returns undefined

I need to access a script tag with ViewChild but it returns undefined when I try to access to nativeElement. It is working with a normal <p> tag but not with <script> .
Here is the stackblitz with an example (with p and script).
I basically do:
#ViewChild('pRef', {static: false}) pRef: ElementRef;
and the in ngAfterViewInit():
this.pRef.nativeElement.innerHTML = "DOM updated succesfully!!!";
User Renderer2 and Document to inject your script dynamically.
in app.component.ts
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent implements AfterViewInit {
name = 'Angular';
#ViewChild('pRef', {static: false}) script: ElementRef;
#ViewChild('prefworking', {static: false}) prefworking: ElementRef;
constructor(
private _renderer2: Renderer2,
#Inject(DOCUMENT) private _document: Document
) { }
ngAfterViewInit() {
this.prefworking.nativeElement.innerHTML = "Working pref";
this.convertToScript();
}
convertToScript() {
let script = this._renderer2.createElement('script');
script.type = `text/javascript`;
this._renderer2.appendChild(this._document.body, script);
}
}
I have created the stackblitz. checkout this link https://stackblitz.com/edit/angular-8-viewchild-example-gaffzs

Angular7 - access parameter in Appcomponent

My application requires a brand code to determine the style and dom.
currently the on load my URL would be www.SiteName.com/HBL (HBL = brandName)
It is a simple site where it has the only header, footer, search component.
but I need to get the Brand info from service api.
So in Appcomponent.ts, I injected ActivatedRoute and in the ngOnInit method, I subscribed paramMap.
When I load the app I am getting null parameter value.
This what I have done
my app.compnent.html:
<div class="container">
<header [brand]="brand"></header>
<!-- <esw-search></esw-search> -->
<router-outlet></router-outlet> - Search will be populated thru route
<esw-footer></esw-footer>
</div>
I could have avoided router but sometimes the search page will be directly accessible.
like www.SiteName.com/HBL/search?trackingnumber=123456;language=en
my routing component:
import { NgModule } from '#angular/core';
import { RouterModule, Routes } from '#angular/router';
import { NotFoundComponent } from './notfound/notfound.component';
import { SearchComponent } from './tracking-search/search/search.component';
const routes: Routes = [
{ path: '', component: SearchComponent },
{ path: ':brandName/search', component: SearchComponent },
{ path: ':brandName/', component: SearchComponent },
{ path: '404', component: NotFoundComponent },
{ path: '**', redirectTo: '404' }
];
#NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
my appcomponent.ts code:
#Component({
selector: 'esw-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {
title = 'logistics-tracking-ui';
apiUrl: string;
brand: Brand;
constructor(
private tracking: TrackingService,
private route: ActivatedRoute) {
}
ngOnInit(): void {
this.route.paramMap.subscribe(params => {
const brandName = params.get('brandName');
this.tracking.getBrandData(brandName).subscribe((response) => this.brand = response);
});
}
}
}
SearchComponent.htm:
<div class="card-body">
<div class="card mx-auto">
<div class="card-body">
<h3 style=" text-align: center"> Track your International Package</h3>
<div>
<span class="ui-float-label">
<input [(ngModel)]="trackingNumber" id="float-input" type="text" size="30" pInputText>
<label for="float-input">Tracking Number</label>
</span>
<button pButton type="button" label="Click" (click)="searchTracking()"></button>
</div>
<esw-search-details [trackingDetails]='trackingDetails$'></esw-search-details>
</div>
</div>
</div>
searchComponent.ts:
#Component({
selector: 'esw-search',
templateUrl: './search.component.html',
styleUrls: ['./search.component.scss']
})
export class SearchComponent implements OnInit {
trackingNumber = '';
trackingDetails$: Observable<any>;
constructor(private trackingservice: TrackingService) { }
ngOnInit() {
}
searchTracking(): void {
alert('Search Clicked' + this.trackingNumber);
if (!this.trackingNumber.trim()) {
// if not search term, return empty hero array.
// Publish error message
console.log('Invalid Input');
return;
}
this.trackingDetails$ = this.trackingservice.getTrackingDetails(this.trackingNumber, 'en-GB');
}
Note: I have not added much logic to search & serachDetails component.
The issue's I have:
Access brand params value in App component.
Is this right approach to defining layout in app.coponent.html?
Is there any better approach I can use for this?
Sorry this is my first angular project, any help will be appriciated.
May be you need to add a route for the param and that has to be added as the first in the list of routes, like
const routes: Routes = [
{ path: ':brandName/:brand', component: SearchComponent },
{ path: ':brandName/search', component: SearchComponent },
{ path: ':brandName/', component: SearchComponent },
{ path: '404', component: NotFoundComponent },
{ path: '', component: SearchComponent },
{ path: '**', redirectTo: '404' }
];
and now in the app component we can access it like:-
this.route.paramMap.subscribe(params => {
const brandName = params['brand']
this.tracking.getBrandData(brandName).subscribe((response) => this.brand = response);
});
If you want to go the route you are with passing the the exports/imports, then you have to be careful of the asynchronous loading of JS. Assuming your api call, exports, and imports are set up correctly, the Api call is completed and the brand is filled after the header component is loaded, (verify by adding console log in the app component after the api call is completed. You'll see it logs after the header loads, making it inaccessible to the header component's ngOnInit method). So you can either prevent loading until you have the required element:
<header *ngIf="ReturnedObject.brand" [brand]="brand"></header>
Or you can load the element after the page is loaded with Ng life cycle hooks, such as
ngAfterContentInit(){}
(this is not a great option as your page will load with whatever default branding, then it will reload once the brand is updated)
my preferred method
You can use the "{{}}" notation to dynamically name your class of an element as needed, and instead of passing an export to load another component, set the class in the parent component, then load the child component:
(in your child css)
.texas {
background-image: texasFlag.png;
}
.newYork {
background-image: newYorkFlag.png;
}
(in your parent html)
<header class="{{ReturnedObject.brand}}"></header>
<your-child-component></your-child-component>
<footer class="{{ReturnedObject.brand}}"></footer>
That way, the class is already set by the parent before the child starts to load, taking away the "racing" your parent and child component are doing to load.

Component Interaction #Input

I would like a component to send input to another component. Below is the code .ts and .html. of the two components.
Now the problem is that the html page of the parent component also shows the html part of the child component ... I want the component to pass only one string to the child component
Parent.ts
import ...
#Component({
selector: 'app-parent',
templateUrl: './parent.html',
styleUrls: ['./parent.css']
})
export class ParentComponent implements OnInit {
sostegno : string;
constructor() { }
ngOnInit() { }
avvia1() {
this.sostegno = "xxx";
this.router.navigate(['./xxx'], { relativeTo: this.route });
}
avvia2()
this.sostegno = "yyy";
this.router.navigate(['./yyy'], { relativeTo: this.route });
}
}
Parent.html
<div>
...
</div>
<app-child [sostegno]="sostegno"></app-child>
Child.ts
import ...
#Component({
selector: 'app-child',
templateUrl: './child.html',
styleUrls: ['./child.css']
})
export class ChildComponent implements OnInit {
#Input() sostegno : string;
constructor() { }
ngOnInit() {
console.log(this.sostegno);
}
}
There are some changes which you need to make because looking at the code which your currently have it seems incomplete.
You are using this.router without injecting the Router class in your constructor.
You are using this.route without injecting the ActivatedRoute class in your constructor.
To test that your parent > child interaction is working you can remove your param and instead place a test for the html
<app-child [sostegno]="'Test'"></app-child>
This should work for your ngOnInit function which is inside of your child component. If this works all you need to do now is either initialize sostegno in your parent component else your console log inside your child component will not reflect the changes when you call avvia1 or avvia2 inside of your parent class.
Hope this helps!

lazy load module in matDialog

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.

Angular 2 component inherit with onClick

I would like to inerit from component and add onClick method, how can I do it?
I want to have only one html file.
Here is a basic example -
I have a temple file with this html code-
<h1>h1</h1>
<h2>h2</h2>
I want to inherit this component and add OnClick method on h1.
Thanks in advance.
You can just add that component with function and a variable that sets to true on click.
<button (click)="toggleComponent()"></button>
<app-example-component *ngIf="variable">
ts:
variable = false;
toggleComponent() {
this.variable = !this.variable;
}
You can extend component like I did as below:
Plunker Link
https://plnkr.co/edit/azixm9?p=preview
//our root app component
import {Component, NgModule, VERSION} from '#angular/core'
import {BrowserModule} from '#angular/platform-browser'
#Component({
selector: 'my-app',
template: `
<div>
<h2 (click)="callMe()">Hello {{name}}</h2>
<comp-one></comp-one>
</div>
`,
})
export class App {
name:string;
constructor() {
this.name = `Angular! v${VERSION.full}`
}
public callMe(compName: any): void {
alert("App Component will handle this functionality")
}
}
#Component({
selector: 'comp-one',
template: `<h2 (click)="callMe()">Click Me</h2>`,
})
export class ComponentOne extends App {
}
#NgModule({
imports: [ BrowserModule ],
declarations: [ App, ComponentOne ],
bootstrap: [ App ]
})
export class AppModule {}