Insert component into html dynamically - html

I'm trying to insert a angular-material component inside a piece of html dynamically. The way i think, i won't be able to use ViewContainerRef.
Here's how it needs to work:
I'll retrieve a string from the database (it can be any material component, such as inputs, buttons, etc... Something like this:
let stringFromDB = "<md-spinner></md-spinner>"
I would need to pass this string to my html's div (which is my "container"). So i tryied:
#Component({
selector: 'report',
template: `<div [innerHtml]="stringFromDB"></div>`
})
export class ReportComponent{
stringFromDB : string = null;
constructor(){
this.stringFromDB =this.someService.getTemplateStringFromDatabase();
}
}
I can pass a simple <p></p>.
But not a component like md-spinner. Any thoughts on how to accomplish this?

In angular 4 you can use ngComponentOutlet.
Template:
<ng-container *ngComponentOutlet="dynamicComponent; ngModuleFactory: dynamicModule;"></ng-container>
Build dynamic module and component with your template string:
import { Compiler } from '#angular/core';
build() {
this.dynamicComponent = this.createDynamicComponent(this.stringFromDB);
this.dynamicModule = this._compiler.compileModuleSync(this.createDynamicModule(this.dynamicComponent));
}
createDynamicModule(componentType: any) {
#NgModule({
imports: [ ],
declarations: [
componentType
],
entryComponents: [componentType]
})
class RuntimeModule { }
return RuntimeModule;
}
createDynamicComponent(template: string) {
#Component({
selector: 'dynamic-component',
template: template ? template : '<div></div>'
})
class DynamicComponent {
constructor() {
}
}
return DynamicComponent;
}

Related

How to create HTML in angular?

I have been working on React for a year. Now, I am writing angular. How can I create a piece of html code in ts.file?
In react, I do it that way:
const example = (item: string): React.ReactNode => {
return <p> something.... {item} </p>
}
I want to do same thing in Angular8+
I know some way to do it. For example:
const example2= (name: string): string => {
return `
<div>
<p>heyyy ${name}</p>
</div>
`;
};
Are there any other ways to do it?
In Angular, there are a couple of ways to do this. If you need to generate HTML in the typescript and then interpolate it into the template, you can use a combination of the DomSanitizer and the innerHTML attribute into other elements (for example a span).
Below would be an example of what I suggested above:
hello-world.component.ts:
#Component({
selector: "hello-world",
templateUrl: "./hello-world.component.html",
styleUrls: ["./hello-world.component.scss"]
})
export class HelloWorld {
innerHTML: string = `<p>Hello, world!</p>`;
}
sanitze.pipe.ts:
#Pipe({
name='sanitize'
})
export class SanitizePipe {
constructor(private sanitizer: DomSanitizer) { }
transform(value: string): SafeHtml {
return this.sanitizer.bypassSecurityTrustHtml(value);
}
}
hello-world.component.html:
<div [innerHTML]="innerHTML | sanitize"</div>

Angular 5 generate HTML without rendering

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

Highlight the search text in angular 2

I am pretty new to Angular 2. I am trying to achieve the same task as Highlight the search text - angular 2 what is mentioned in above post.
I have created the pipe filter my question is where should i keep the pipe filter and where should i place the inner html div.
Copying the problem:
A messenger displays the search results based on the input given by the user. Need to highlight the word that is been searched, while displaying the result. These are the html and component that is been used.
component.html
<div *ngFor = "let result of resultArray">
<div>Id : result.id </div>
<div>Summary : result.summary </div>
<div> Link : result.link </div>
</div>
Component.ts
resultArray : any = [{"id":"1","summary":"These are the results for the searched text","link":"http://www.example.com"}]
This resultArray is fetched from hitting the backend service by sending the search text as input. Based on the search text, the result is fetched. Need to highlight the searched text, similar to google search.
How should i apply the search filter and where should i keep the inner html?
There are some tweaks to be made to the regex replacement regarding case, but here's a starting point:
//our root app component
import {Component, NgModule, VERSION, Pipe, PipeTransform} from '#angular/core'
import {BrowserModule, DomSanitizer} from '#angular/platform-browser'
#Pipe({
name: 'highlight'
})
export class HighlightSearch implements PipeTransform {
constructor(private sanitizer: DomSanitizer){}
transform(value: any, args: any): any {
if (!args) {
return value;
}
// Match in a case insensitive maneer
const re = new RegExp(args, 'gi');
const match = value.match(re);
// If there's no match, just return the original value.
if (!match) {
return value;
}
const result = value.replace(re, "<mark>" + match[0] + "</mark>");
return this.sanitizer.bypassSecurityTrustHtml(result);
}
}
#Component({
selector: 'my-app',
template: `
<input (input)="updateSearch($event)">
<div *ngFor="let result of resultArray" [innerHTML]="result.summary | highlight: searchTerm"></div>
`,
})
export class App {
results: string[]
searchTerm: string;
constructor() {
this.resultArray = [
{
"id": "1",
"summary": "These are the results for the searched text",
"link": "http://www.example.com"
},
{
"id": "2",
"summary": "Here are some more results you searched for",
"link": "http://www.example.com"
}
]
}
updateSearch(e) {
this.searchTerm = e.target.value
}
}
#NgModule({
imports: [ BrowserModule ],
declarations: [ App, HighlightSearch ],
bootstrap: [ App ]
})
export class AppModule {}
Plnkr
Edit: Plnkr seems unhappy. StackBlitz
you can easily use this directive
usage:
<label [jsonFilter]="search">{{text}}</label>
directive
import {
AfterViewInit,
Directive,
ElementRef,
Input,
OnChanges,
SimpleChanges
} from '#angular/core';
#Directive({
selector: '[jsonFilter]'
})
export class FilterDirective implements OnChanges, AfterViewInit {
#Input() jsonFilter = '';
constructor(
private el: ElementRef,
) {
}
ngOnChanges(changes: SimpleChanges) {
this.ngAfterViewInit();
}
ngAfterViewInit() {
const value = this.el.nativeElement?.innerText.split('\n').join('');
if (!value) return;
const re = new RegExp(this.jsonFilter, 'gi');
const match = value.match(re);
if (!match || match.some(x => !x)) {
this.el.nativeElement.innerText = value;
} else {
this.el.nativeElement.innerHTML = value.replace(re, "<mark>" + match[0] + "</mark>")
}
}
}

Add or Remove HTML elements into view using data model in angular 2 with type script without using innerHTML and standard structural directive

In smartGWT, there are various widget classes, which can be extended and create the required layouts. At runtime, these layouts will be added to html. Now, I want to perform similar operations using Angular 2 with TypeScript i.e. write a code in a class such that it will add html forms and other elements to the view at runtime.
Below is the code.
This code provides a select element (select1).
Requirement
When user selects an option, another select item (select2) should be added to view and the options of select 2 should be the properties of the item selected in select1. When the selection changes in select1, the options of select 2 should be changed.
main.ts
import { platformBrowserDynamic } from '#angular/platform-browser-dynamic';
import { AppModule } from './app.module';
platformBrowserDynamic().bootstrapModule(AppModule);
app.module.ts
import { NgModule } from '#angular/core';
import { BrowserModule } from '#angular/platform-browser';
import { AppComponent } from './app.component';
import { NgbModule } from '#ng-bootstrap/ng-bootstrap';
import { FormsModule } from '#angular/forms';
#NgModule({
imports: [ BrowserModule, NgbModule.forRoot(), FormsModule ],
declarations: [ AppComponent ],
bootstrap: [ AppComponent ]
})
export class AppModule { }
app.component.ts
import { Component } from '#angular/core';
import { RelatedLinkImpl } from './RelatedLinkImpl';
#Component({
selector: 'my-app',
templateUrl: 'app/relatedLink.component.html',
})
export class AppComponent {
relatedLinkList : Array<RelatedLinkImpl> = [new RelatedLinkImpl('Object0', 'Object0.object', ["a0","b0","c0"]), new RelatedLinkImpl('Object1', 'Object1.object', ["a1","b1","c1"]), new RelatedLinkImpl('Object2', 'Object2.object', ["a2","b2","c2"])];
selectedLinkItem : RelatedLinkImpl;
onConceptSelection() : void {
if (this.selectedLinkItem != null) {
console.log("Link Text : " + this.selectedLinkItem.linkText + " Link Type : " + this.selectedLinkItem.linkType + " Link Properties : " + this.selectedLinkItem.linkProperties);
}
}
}
RelatedLinkImpl.ts
export class RelatedLinkImpl {
private _linkText : string;
private _linkType : string;
private _linkProperties : Array<string> = new Array<string>();
constructor(linkText : string , linkType : string, linkProperties : string[]) {
this._linkText = linkText;
this._linkType = linkType;
for(var i=0; i<linkProperties.length; i++) {
this._linkProperties.push(linkProperties[i]);
}
}
get linkText() : string {
return this._linkText;
}
set linkText(linkText : string) {
this._linkText = linkText;
}
get linkType() : string {
return this._linkType;
}
set linkType(linkType : string) {
this._linkType = linkType;
}
get linkProperties() : string[] {
return this._linkProperties;
}
set linkProperties(linkProperties : string[]) {
for(var i=0; i<linkProperties.length; i++) {
this._linkProperties.push(linkProperties[i]);
}
}
}
relatedLink.component.html
<form #f="ngForm" novalidate>
<div class="form-group">
<select name="ruleStarting" class="form-control" [(ngModel)]="selectedLinkItem" (change)="onConceptSelection()">
<option *ngFor="let relatedLinkItems of relatedLinkList" [ngValue]="relatedLinkItems">{{relatedLinkItems.linkText}}</option>
</select>
</div>
</form>
Edit 1 :
Have refered following sources :
Different form controls in Angular 2
Build Nested model driven forms in Angular 2
Edit 2 :
The question is how to add html elements at run time using angular 2 with typescript. The way we do it with smartGWT/GWT. Just right typescript classes in Angular 2 and generate the HTML applications.

Are global variables accessible in Angular 2 html template directly?

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