Angular innerHtml - html

I am relative new in Angular and I have the following question.
I have a component: my.component.html
<app-loading *ngIf="loading" message="Inhalt wird geladen..." delay="0"></app-loading>
<div [innerHtml]="content | safeHtml"></div>
And the following ts file: my.component.ts
export class MyComponent implements OnInit {
loading: boolean;
#Input() serviceUrl: string;
content: string;
constructor(public service: MyService) { }
ngOnInit() {
this.loading = true;
this.content = '';
this.service.getMyContent(this.serviceUrl).then(myContent => this.onMyContentRead(myContent));
}
onMyContentRead(dto: SimpleDto) {
this.loading = false;
this.content = dto.output;
}
}
It calls a REST service and gets the dto.output, which is a string contains the following html content from MyClassToShow.html
<li id="myBox_panels">
%s
<app-tooltip [myText]="test"></app-tooltip>
</li>
The tooltip components exists, and looks like this:
#Component({
selector: 'app-tooltip',
template: `
<a class="help" [pTooltip]="myText" [tooltipPosition]="tooltipPosition" </a>
})
export class TooltipComponent implements OnInit {
tooltipPosition: string;
#Input() myText;
constructor(private el: ElementRef) { }
ngOnInit() {
this.setTooltipPosition();
}
setTooltipPosition() {
....
}
It seems that the app-tooltip selector is not realized, because its template content is not displayed on the webpage, altough I can see the selector on the console log.
The innerHtml can only contain plain HTML code?
How can I get my app-tooltip template as well?
Thnak you a lot in advance!

Related

How do I bold a String that I'm passing in through a message service?

My service looks as follows:
import { Injectable } from '#angular/core';
#Injectable({
providedIn: 'root',
})
export class MessageService {
messages: string[] = [];
add(message: string) {
this.messages.push(message);
}
clear() {
this.messages = [];
}
}
The HTML for the associated component looks like this:
<div>
<h4>Status Messages:</h4>
<div *ngFor='let message of messageService.messages'> {{message}} </div>
</div>
I normally call it in other components like this:
this.messageService.add('Completed all current actions.')
Say I wanted to just bold the word "Completed". Any ideas on how to do that?
You just need to changed your data model.
You can define Message, which represents single sentence with array of words, which need to be highlighted.
export interface Message {
text: string;
wordsToHighlight: string[];
}
Then during iteration over the messages array, create html string and use HTML element's innerHTML or outerHTML property to render it.
Pay attention on getHighlightedText method below.
Your component may look like this:
#Component({
selector: 'app-demo',
template: `
<div *ngFor="let message of messages" [outerHTML]="getHighlightedText(message)"></div>
`
})
export class DemoComponent implements OnInit {
messages: Message[];
constructor(private readonly messageService: MessageService) {}
ngOnInit(): void {
this.messages = this.messageService.messages;
this.messageService.add({ text: 'Completed all current actions', wordsToHighlight: ['all', 'actions'] })
}
getHighlightedText(message: Message): string {
const words = message.text.split(' ');
return words.map((word) => {
const highlight = message.wordsToHighlight.some((wordToHighlight) => word.toLowerCase() === wordToHighlight.toLowerCase());
if (highlight) {
return `<b>${word}</b>`;
} else {
return word;
}
}).join(' ');
}
}
Message service:
#Injectable({
providedIn: 'root',
})
export class MessageService {
messages: Message[] = [];
add(message: Message) {
this.messages.push(message);
}
clear() {
this.messages = [];
}
}

Angular - Dynamically load html that includes angular markups

In Angular 9+ I can successfully convert a string to a html and then load that that html using innerHtml and bypassSecurityTrustHtml().
My question is it possible to also dynamically load/render the converted html to include and recognise angular/javascript markup language eg *ngIf, handle bars and click events.
Below is the code and stackblitz at the attempt so far but as you can see it doesn't recognise the markup.
https://stackblitz.com/edit/dynamic-angular?file=app/app.component.ts
export class AppComponent implements OnInit {
text: string = "Hello world";
content: any;
constructor(private domSantizer: DomSanitizer) {}
ngOnInit() {
let body: any =
'<div>{{text}}<div><br><button (click)="test()">Test</button>';
this.content = this.domSantizer.bypassSecurityTrustHtml(body);
}
test() {
alert("It works");
}
}
Html
<div [innerHTML]="content"></div>
I have researched and tried many solutions.
My research and trial results are below.
html
<div #container></div>
typescript side as below
export class AppComponent implements OnInit {
#ViewChild("container", { read: ViewContainerRef })
container: ViewContainerRef;
constructor(private compiler: Compiler) {}
text: string = "asdasd";
ngOnInit() {
this.addComponent(
`<div>{{text}}<div><br><button (click)="test()">Test</button>
`,
{
text: "Hello word",
test: function() {
alert("It's work");
}
}
);
}
private addComponent(template: string, properties?: any = {}) {
#Component({ template })
class TemplateComponent {}
#NgModule({ declarations: [TemplateComponent] })
class TemplateModule {}
const mod = this.compiler.compileModuleAndAllComponentsSync(TemplateModule);
const factory = mod.componentFactories.find(
comp => comp.componentType === TemplateComponent
);
const component = this.container.createComponent(factory);
Object.assign(component.instance, properties);
// If properties are changed at a later stage, the change detection
// may need to be triggered manually:
// component.changeDetectorRef.detectChanges();
}
demo
some posts I have reviewed
compile dynamic Component
angular-html-binding
I think it makes the most sense :)

#viewChild and #ViewChildern gives undefined

I'm working on Angular 9 and want to access an input field after clicking on a button. right now it gives me undefined. I have tried #ViewChild and #viewChildern because I'm using ngIf.
Template.html file
<div class="search-input" #searchDiv *ngIf="serachActive">
<input
#searched
autofocus
type="text"
class="serach-term"
placeholder="Search"
[(ngModel)]="searchTerms"
(ngModelChange)="applySearch()"
/>
<button (click)="toggleSearch(!serachActive)">
<span class="material-icons"> search </span>
</button>
<ul class="search-list">
<li *ngFor="let result of results">
<a [routerLink]="['/', 'video', 'details', result._id]">{{
result.title ? result.title : ''
}}</a>
</li>
</ul>
</div>
Template.ts file
import { Component, OnInit,AfterViewInit,ElementRef,ViewChild,ViewChildren } from '#angular/core';
import { UserService } from '../../../user.service';
import { VideoService } from '../../../services/video.service';
import { Subject } from 'rxjs';
import { distinctUntilChanged, debounceTime } from 'rxjs/operators';
import { Router } from '#angular/router';
#Component({
selector: 'app-header',
templateUrl: './header.component.html',
styleUrls: ['./header.component.css'],
})
export class HeaderComponent implements OnInit,AfterViewInit{
serachActive: boolean = false;
#ViewChildren('searched') searchElement: ElementRef;
#ViewChildren("searched") input: ElementRef;
user;
subject = new Subject<string>();
results = [];
searchTerms;
loggedIn: Boolean = false;
constructor(
private userService: UserService,
private videoService: VideoService,
private router: Router
) {
this.user = this.userService.getUser();
this.loggedIn = this.userService.isAuthenticated();
}
ngOnInit() {
console.log('on init', this.input); //undefined
this.subject
.pipe(debounceTime(400), distinctUntilChanged())
.subscribe((value) => {
this.router.navigate(['search'], { queryParams: { term: value } });
});
}
ngAfterViewInit() {
console.log('on after', this.input); //undefined
}
toggleSearch(toggledata) {
this.serachActive = toggledata;
this.results = [];
this.searchTerms = '';
console.log(this.input) //undefined
console.log(this.searchElement.nativeElement) //undefined
}
applySearch() {
const searchText = this.searchTerms;
this.subject.next(searchText);
this.searchElement.nativeElement.focus(); //undefined
}
menuButtonClick(button){
if(button === "history"){
this.router.navigate(['history'])
}
}
}
Use ViewChild since you're only searching for 1 element ID.
If adding { static: true } or { static: false } in your ViewChild options doesn't work as what is stipulated on Angular Static Query Migration Documentation
Use ChangeDetectorRef instead:
#Component({...})
export class AppComponent {
#ViewChild('searchInput') input: ElementRef;
isShow: boolean = false;
constructor(private cdr: ChangeDetectorRef) {}
toggle(): void {
this.isShow = !this.isShow;
this.cdr.detectChanges(); // Detects changes which this.isShow is responsible on showing / hiding
// the element you're referencing to in ViewChild
if (this.isShow) // If element is shown, console the referenced element
console.log(this.input);
}
}
Have created a Stackblitz Demo for your reference

How can I route from a component to a section of another component in Angular?

I have two components and they are situtated in the different routes. In the Second component There is a div. I want to route from first component to the div of the second component. Yes, It is simple to route the second component. But I want to scroll be top of the div.
Thank you for answers.
This tag is declared in component1
<a [routerLink] = "['/c2']" fragment="c2id"> Link </a>
Here are the component2 changes
import { Component, OnInit } from '#angular/core';
import { ActivatedRoute } from '#angular/router';
#Component({
selector: 'app-c2',
templateUrl: './c2.component.html',
styleUrls: ['./c2.component.css']
})
export class C2Component implements OnInit {
private fragment: string;
constructor(private route: ActivatedRoute) {}
ngOnInit() {
this.route.fragment.subscribe(fragment => {
this.fragment = fragment;
});
}
ngAfterViewInit(): void {
try {
document.querySelector('#' + this.fragment).scrollIntoView();
} catch (e) {}
}
}
And your component2 html will be like this
<p style="height: 800px;">
c2 works!
</p>
<hr>
<div id="c2id" style="height: 500px;">
The div with c2id
</div>
Here is the updated and working stackblitz
https://angular-fragment-example.stackblitz.io
I think you are looking for Fragments.
Official Docs : Angular Docs- Query Params and Fragments
Examples:
Manual Navigation
in c1.html
<a [routerLink] = "['/c2']" [fragment]="c2id"> Link </a>
in c2.html
<div id="c2id">content</div>
Programatic Navigation
in c1.ts
private fragmentSetDynamically: string;
constructor(private router: Router){}
onClickButton(){
this.router.navigate(['/c2'], {fragment: fragmentSetDynamically});
}
Getting the fragment :
in c2.ts
private fragment: string;
constructor(private activatedRoute: ActivatedRoute){}
ngOnInit(){
this.fragment = this.activatedRoute.snapshot.fragment;
}

Accessing innerText inside Component

I'm having pretty simple component that looks like this and basicly does the job.
import { Component, Input } from '#angular/core';
#Component({
selector: 'app-code',
template: ' <pre><code [highlight]="code" [languages]="languages" [lineNumbers]="true"></code></pre> '
})
export class CodeComponent {
readonly languages = ['java'];
#Input()
code = '';
constructor() {
}
}
But, I'd like to make minor change to it
#Component({
selector: 'app-code',
template: '<pre><code [highlight]="code" [languages]="languages" [lineNumbers]="true"></code></pre>'
})
export class CodeComponent {
readonly languages = ['java'];
code = '';
constructor(private elem: ElementRef) {
this.code = elem.nativeElement.innerText;
}
}
So instead of writing
<app-code [code]="'some code goes here'"></app-code>
I can write
<app-code>some code goes here</app-code>
Unfortunatelly, It's not working, my code block remains empty
You cannot get innerText because CodeComponent is not drawn.
You need to use ngAfterViewInit.
#Component({
selector: 'app-code',
template: '<pre><code [highlight]="code" [languages]="languages" [lineNumbers]="true"></code></pre>'
})
export class CodeComponent {
readonly languages = ['java'];
code = '';
constructor(private elem: ElementRef) {
}
ngAfterViewInit() {
this.code = elem.nativeElement.innerText;
}
}