Angular - bind InnerHTML with translate in TS file - javascript

I have a problem with binding into property to get html and translate it.
I have an innerHTML that I am trying to map to translate.
The problem is it is not translating and displaying the key as it is. P
Below is my code :-
let popupContainer = document.createElement('div');
popupContainer.innerHTML = require('html-loader!../html/myPopup.html').default;
popupContainer.addEventListener('click', clickEventHandler);
document.body.appendChild(popupContainer);
It does not translate and displays as below :- {{'fileverse-name-label' | translate}}
HTML :-
<div class="book__filters">
<hr />
<form class="my____form flex-row" id="filterForm">
<div class="me-3 col-md-3 checker__filters--field">
<label for="fileName" class="band__form--label">{{'fileverse-name-label' | translate}}</label>
<input
type="text"
class="drese__form--input colrs__filters--input"
placeholder="Search for a file verse"
name="fileverse"
id="fileverse"
/>
</div>
<div class="me-3 col-md-3 runner__filters--field">
<label for="chapterLabel" class="chapter__form--label">{{'chapter-label' | translate}}</label>
<select
class="chapter__form--input geeze__filters--input"
name="chapterLabel"
id="chapterLabel"
></select>
</div>
</form>
<hr />
</div>

So, adding html inside a div like you're doing will "just" add your html file into it, without any further logic.
But the pipe needs to be compiled, so you would need to do the following to make this work.
I'll just write step here, please let me know if I should provide more information
With *ngIf
Create a component out of the html you gave (module, component.ts, component.html)
Importing this component whoever you needs it.
Show/Hide it with an *ngIf.
With ViewContainerRef
Create a component out of the html you gave (module, component.ts, component.html)
// component.ts
#Component({
selector: 'foo-bar',
templateUrl: '../foo-bar.component.html', // File where you've added the html
})
export class fooBarComponent {
// ...
}
// module.ts
#NgModule({
declarations: [fooBarComponent],
imports: [
CommonModule,
TranslateModule.forChild()
],
exports: [fooBarComponent],
})
export class fooBarModule {}
Add id to where you need it
<ng-template #loaderContainer></ng-template>
get this element through the where-you-use-it.component.ts file.
#ViewChild('loaderContainer', { read: ViewContainerRef, static: true })
loaderContainer: ViewContainerRef
Add the wished element to it with the ViewContainerRef createComponent() method
this.loaderContainer.createComponent(fooBarComponent)
// This will be at the same place where you initially intended to add the div
Do not forget to add the fooBarModule inside the where-you-use-it.module.ts
Additional
If you're trying to create an "orverlay"
Then I would go with the *ngIf solution and add it into your app.component.html file, just after the <router-outlet> tag

Related

How to work with variables from a loop in HTML and with a component property?

The data I work with (bosses[]) has a boss object with contains the key-value email which is an string. I want to create the anchor with that string in the HTML. Also note that there's a loop in HTML that allows to access to each boss in bosses[].
So how can I access to create an anchor with boss.email which it only exists in the HTML loop?
I've tried <a [href]=`"mailto: + boss.email"></a> but doesn't work.
the html:
<div class="boss" *ngFor="let boss of bosses" >
<div class="boss-text">
<div class="boss-text-name">{{boss.name}} </div>
<div>{{boss.email}}</div>
<a [href]="mailto: + boss.email"></a>
</div>
</div>
The component:
import { Component, Input, OnInit } from '#angular/core';
import { boss} from 'interfaces'
#Component({
templateUrl: 'boss-cell.component.html',
selector: 'boss-cell',
})
export class BossCellComponent implements OnInit {
constructor() {}
bosses: any[] = [{
email: 'kennedy#gmail.com',
name: 'kennedy',
}]
}
You're close! I think this is what you're looking for:
<div class="boss" *ngFor="let boss of bosses" >
<div class="boss-text">
<div class="boss-text-name">{{boss.name}} </div>
<a [href]="'mailto:' + boss.email">{{ boss.email }}</a>
</div>
</div>
You can use interpolation as Suraj already mentionned in the comments, or bind to a function creating the string. Depending on weather you are going to link other elements to the mail, you should pick the cleanest option for you.
Template
<div class="boss" *ngFor="let boss of bosses; let i = index">
<div class="boss-text">
<div class="boss-text-name">{{ boss.name }}</div>
<a [href]="getMail(i)">{{ boss.email }}</a>
</div>
</div>
Script
bosses: any[] = [{
email: 'kennedy#gmail.com',
name: 'kennedy',
}]
getMail(index: number) {
return 'mailto:' + this.bosses[index].email
}
You need to update this line
<a [href]="'mailto:' + boss.email">{{ boss.email }}</a>

To insert dynamic elements with specific style attributes in *ngfor(Angular)

I have implemented drag and drop feature referring to a youtube tutorial
I have *ngFor which will generate divs from existing roomsFloorZone array, now my objective is to generate divs using style attribute and value received from backend response provided to the same *ngFor, as all the divs from existing roomsFloorZone or from backend response needs to be under same parent div
I have tried with already existing elements present in roomsFloorZone
import {Component, OnInit, AfterViewInit, Input, SimpleChange,
SimpleChanges} from '#angular/core';
#Component({
selector: 'floor-zone',
templateUrl: './floorzone.component.html',
styleUrls: ['./floorzone.component.scss']
})
export class FloorZoneComponent{
urlFloorZoneIn: any;
roomsFloorZoneIn: any;
#Input() urlFloorZone;
#Input() roomsFloorZone;
#Input() currentBoxFloorZone;
ngOnChanges(changes: SimpleChanges) {
if (changes.urlFloorZone && changes.urlFloorZone.currentValue) {
this.urlFloorZoneIn = changes.urlFloorZone.currentValue;
}
if (changes.roomsFloorZone && changes.roomsFloorZone.currentValue) {
this.roomsFloorZoneIn = changes.roomsFloorZone.currentValue
}
}
dropzone1 = [];
currentBox?: string = this.currentBoxFloorZone;
move(box: string, toList: string[]): void {
box = this.currentBoxFloorZone;
this.removeBox(box, this.roomsFloorZoneIn);
this.removeBox(box, this.dropzone1);
toList.push(box);
}
removeBox(item: string, list) {
if (list.indexOf(item) !== -1) {
list.splice(list.indexOf(item), 1);
}
}
}
I have an array existing roomsFloorZoneIn , from which I drag and drop the elements in the below html and the *ngfor works for that
html
<div id="toget" class="dropzone" [ngStyle]="{'width':'100%','background-
image': 'url('+urlFloorZoneIn+')','background-repeat': 'no-repeat',
'background-position': 'center', 'background-size': '100% 100%',
'border':'1px solid black', 'height':'340px'}" appMovableArea
appDropzone (drop)="move(currentBox, dropzone1)">
<div class="box" *ngFor="let box of dropzone1" appDroppable
(dragStart)="currentBox = box" appMovable>
{{ box.dis }}
</div>
</div>
My objective is to provide style(transform attribute) and (abc or def) for {{box.dis}} taken from below snippet
[I have style and node values handy]
to *ngFor for placing the divs
`<div xmlns="http://www.w3.org/1999/xhtml">
<!--bindings={
"ng-reflect-ng-for-of": ""
}-->
<div _ngcontent-c5="" appdroppable="" appmovable=""
class="box draggable movable ng-star-inserted" touch-action="none"
style="transform: translateX(183.2%) translateY(56%);"> abc
<span _ngcontent-c5="">X</span>
</div>
<div _ngcontent-c5="" appdroppable="" appmovable=""
class="box draggable movable ng-star-inserted" touch-action="none"
style="transform: translateX(183.2%) translateY(56%);"> def
<span _ngcontent-c5="">X</span>
</div>
</div>`
So this *ngFor for should work for existing elements(roomsFloorZoneIn) as well as I should be able to insert some elements received from backend(using their style attribute and node value)
Note: I already have the style and node value handy
If the two lists can come one after the other, you can use two separate <div>'s, both with *ngFor's using the two Arrays:
<div class="myEnclosingDiv">
<div class="firstList" *ngFor="let element of firstArray">
{{ element.stuff }}
</div>
<div class="secondList" *ngFor="let element of secondArray">
{{ element.otherStuff }}
</div>
</div>
On the other hand, if the two lists need to be mixed somehow, you simply create a new Array in your TypeScript code and use a single *ngFor.

Angular 8: detect if a ng-content has content in it (or exists)

I have a component whose template allows for 2 content areas: Text and "read more" text. If the consumer of the component adds the area for the "read more" text, I want to show the "read more" link the end-user would click to show the text. If they don't include/need any "read more" text I don't want to show the link.
How do I detect the presence of the template area, and act accordingly with an ngIf?
For example, the html might be:
<app-promohero-message-unit title="Title for messaging module">
<div description>
Include a short, informative description here.
</div>
<div readmoretext>
If you need to add more detail, include another sentence or two it in this section.
</div>
</app-promohero-message-unit>
Obviously, they might not need readmoretext, so if they've omitted it I should not show the readmore link.
The component code is, so far:
import { Component, Input } from '#angular/core';
#Component({
selector: 'app-promohero-message-unit',
template: `
<div>
<h3 class="text-white">{{ title }}</h3>
<p class="text-white">
<ng-content select="[description]"></ng-content>
</p>
<p class="text-white" *ngIf="readMore">
<ng-content select="[readmoretext]"></ng-content>
</p>
</div>
<p>
<a class="text-white" (click)="showReadMore()" *ngIf="something"><u>Read more</u></a>
</p>
`
})
export class PromoheroMessageUnitComponent {
#Input()
title: string;
readMore = false;
showReadMore() {
this.readMore = true;
}
}
In Angular 8 you dont have to use the ngAfterViewInit life cycle hook. You can use the ngOnInit as long as you set the "static" value of the viewchild to true.
import { Component, OnInit, ViewChild, TemplateRef, ElementRef } from '#angular/core';
#Component({
selector: 'app-test',
templateUrl: './test.component.html',
styleUrls: ['./test.component.scss']
})
export class TestComponent implements OnInit {
#ViewChild('content', { read: ElementRef, static: true }) content: ElementRef;
constructor() { }
ngOnInit() {
console.log(!!this.content.nativeElement.innerHTML); // return true if there is a content
}
}
Note that you must wrap the ng-content directive with html tag (such as div, span etc) and to set the templateRef on this outer tag.
<div #content>
<ng-content></ng-content>
</div>
I putted it on stackblitz: https://stackblitz.com/edit/angular-8-communicating-between-components-mzneaa?file=app/app.component.html
You can get a reference to the ng-content (Template Variable) and then access that variable in your component to check the length on the content of that ng-content using ViewChild
Then you can use the ngAfterViewInit life cycle hook to check for ng-content length
Your code will be like this:
import { Component, Input, ViewChild, ElementRef } from '#angular/core';
#Component({
selector: 'app-promohero-message-unit',
template: `
<div>
<h3 class="text-white">{{ title }}</h3>
<p class="text-white">
<ng-content select="[description]"></ng-content>
</p>
<p class="text-white" *ngIf="readMore">
<ng-content #readMoreContent select="[readmoretext]"></ng-content>
</p>
</div>
<p>
<a class="text-white" (click)="showReadMore()" *ngIf="something"><u>Read more</u></a>
</p>
`
})
export class PromoheroMessageUnitComponent {
#Input()
title: string;
#ViewChild('readMoreContent') readMoreContent: ElementRef;
readMore = false;
ngAfterViewInit() {
if (this.readMoreContent.nativeElement.childNodes.length.value == 0){
this.readMore = false
}
}
showReadMore() {
this.readMore = true;
}
}
You can use the ContentChild decorator for this, but will need to use an ng-template with a defined id as your content:
<app-promohero-message-unit title="Title for messaging module">
<div description>
Include a short, informative description here.
</div>
<ng-template #readmoretext>
If you need to add more detail, include another sentence or two it in this section.
</ng-template>
</app-promohero-message-unit>
Then in your component, you can use the ContentChild annotation like this:
export class PromoheroMessageUnitComponent {
#ContentChild('readmoretext')
readMoreContent: TemplateRef<any>;
// ...snip
}
Then finally in the HTML for your component:
<!-- snip -->
<p class="text-white" *ngIf="readMoreContent">
<ng-container *ngTemplateOutlet="readMoreContent"></ng-container>
</p>

Angular 2 passing html to ng-content with bindings

I'm writing angular components for the foundation css framework. I am working on the tabs component, and want to be able to pass some HTML to the <ng-content> of this.
The problem is, I also need to pass html which a user can put bindings on, like this:
PARENT TEMPLATE
<tabs [data]='example'>
<div> Age <br> {{item.age}} </div>`
</tabs>
TABS COMPONENT
<ul class="tabs" #tabs>
<li *ngFor="let item of data | async" (click)="tabClick($event)">
<a>{{item.name}}</a>
</li>
</ul>
<div>
<ng-content></ng-content>
</div>
TABS TYPESCRIPT
#Component({
selector: 'tabs',
templateUrl: './tabs.component.html'
})
export class TabsComponent {
#Input('data') data:any;
#ViewChild('tabs') tabs: ElementRef;
}
Where item is a reference to an object in the example array.
However, I get this error:
Cannot read property 'name' of undefined
as item is being evaluated before it is inserted into the <ng-content> directive.
Is there a way to get around this limitation, or am I going about this the wrong way?
update Angular 5
ngOutletContext was renamed to ngTemplateOutletContext
See also https://github.com/angular/angular/blob/master/CHANGELOG.md#500-beta5-2017-08-29
original
ngTemplateOutlet or ngForTemplate can be used for that use case:
<tabs [data]='example'>
<ng-template let-item>
<div> Age <br> {{item.age}} </div>`
</ng-template>
</tabs>
#Component({
...
template: `
<ul class="tabs" #tabs>
<li *ngFor="let item of data | async" (click)="tabClick($event)">
<a>{{item.name}}</a>
</li>
</ul>
<div>
<ng-template [ngTemplateOutlet]="templateRef" [ngTemplateOutletContext]="{$implicit: (data | async)}"></ng-template>
</div>
`
})
class TabsComponent {
#ContentChild(TemplateRef) templateRef:TemplateRef;
}
See also Angular 2 bind transcluded content to loop variable
You should be using this way instead,
<tabs [data]='example'>
<div> Age <br> {{item.age}} </div>`
</tabs>
Component typescript
#Component({
selector: 'tabs',
templateUrl: './tabs.component.html'
})
export class TabsComponent {
#Input() data:any;
item:any{};
}
In your content projection define a selector as
<div class="tabs-body">
<ng-content select=".tabs-body"> </ng-content>
</div>
As your passing with bindings
<tabs [data]='example'>
<div> Age <br> {{item.age}} </div>`
</tabs>
DEMO
You need to pass the item object to the ng-content component.
<ng-content [item]="selectedTab></ng-content>
I am not certain on what lies behind the tab click event but you can assign that item object to selectedTab which will be passed to the component.
The component that will control the tab view can have the following:
#Input() item: Item;
And this will pass that object when you click. I might be attacking this from the wrong angle but maybe it will help you in some way.

How to manage html logic in a separate file

i have a piece of html code that in charge of presenting a list based on certain conditions:
<!-- Show list only if there are more than 5 results -->
<div list.numberOfResults > 10">
<b>Name: </b>{{list.name}} <b>ID: </b>{{list.id}}
<b>Country: </b>{{list.country}}
</div>
<!-- Show list only if there are less than 10 results -->
<div list.numberOfResults < 10">
<b>Name: </b>{{list.name}} <b>ID: </b>{{list.id}}
<b>Country: </b>{{list.country}}
</div>
Now, I also have some optional parameter (list.country) so I need to check if its not empty before as well.
I believe there is a way to take this logic outside of this html file and make a file that is responsable of the logic and the html will present the data accordingly, can someone please share a simple example of how this can be done based on my code?
thanks!!
Since There are two component you can keep them in a separate file like name
component.html
Then you can import in index.html like
<link rel="import" href="component.html" >
Or you can just grab specific portion from the file like
...
<script>
var link = document.querySelector('link[rel="import"]');
var content = link.import;
// Grab DOM from component.html's document.
var el = content.querySelector('.component');
document.body.appendChild(el.cloneNode(true));
</script>
</body>
DEMO :https://plnkr.co/edit/6atoS1WOK5RzKSmNeR8n?p=preview
You can use ngSwitch when you want to show HTML pieces conditionally,
#Component({
selector: 'my-app',
template: `
<div [ngSwitch]="list.numberOfResults>10"> // here
<div *ngSwitchCase ="true"> // here
<b> Template1 </b><br>
<b>Name: </b>{{list.name}} <br>
<b>ID: </b>{{list.id}} <br>
<b>Country: </b>{{list.country}}
</div>
<div *ngSwitchCase ="false"> // here
<b> Template2 </b><br>
<b>Name: </b>{{list.name}} <br>
<b>ID: </b>{{list.id}} <br>
<b>Country: </b>{{list.country}}
</div>
<div>
`,
})
export class App {
list={numberOfResults:2,name:'myList',id:1,country:'USA'};
}

Categories

Resources