Getting HTML attr. with ViewContainerRef? - javascript

I am adding a component to my template via *ngFor
<div class="filter-group" *ngFor="let type of entityTypes">
<filter-filter-group [type]="type" id="{{type.uri}}"></filter-filter-group>
</div>
And in my component I am getting my ViewChrildren like so
#ViewChildren(FilterGroupComponent, { read: ViewContainerRef }) filterGroup: QueryList<ViewContainerRef>;
this.filterGroup now contains my components which is exactly what I want as I then use the component Factory to dynamically insert components to each viewChild.
As you can see in the tmpl. each component has an id="{{type.uri}}"
Is there a way to get the value of id?
I have searched far and wide and so far am a little lost on how to achieve this. NativeElement could be an option, but I believe that is only available with ElementRef?
Possible Solution:
console.log(group['_data'].renderElement.id);
this gives me the id.... but i don't think this would be the way to do it? I mean it works, but feels wrong.

The ViewContainerRef has an elementRef as a child, which in turn has a nativeElement child (the HTML element per se). So you should be able to do group.elementRef.nativeElement.id to get the id
Note: Be aware that using console.log(element) that returns an HTML element will print the element as HTML, for these cases instead use console.dir

you can try like this
HTML
<div class="filter-group" *ngFor="let type of entityTypes">
<filter-filter-group [type]="type" #mycomp id="{{type.uri}}"></filter-filter-group>
</div>
TS
#ViewChildren('mycomp') mycompRef: any;
someEvent() {
console.log(this.mycompRef); // in this object you will get the id
}

#ViewChildren("vc", {read: ViewContainerRef}) containers: QueryList<ViewContainerRef>
ngAfterViewInit(): void {
this.containers.map(container => console.log(container["_lContainer"][0].id))
this.createLayout();
}
This worked for me in Angular 10

Related

Angular: How to use a component dynamically in another component?

I have an DialogService for Angular Material:
constructor(private dialog: MatDialog){}
openDialog(dialogData){
const dialogRef = this.dialog.open(DialogComponent, {
data: dialogData
}
}
and a DialogComponent to open the dialog with:
let componentToRender
constructor(#Inject(MAT_DIALOG_DATA) public dialogData){
this.componentToRender = dialogdata.componentToRender
}
and this template for it:
<div class="dialog">
<ng-container></ng-container> // Here i want to dynamically render a given component
</div>
I want to give my dialogService with the dialogData a reference to an component that i want to be rendered inside my diaologComponent <ng-container>
The result should be, that i can call my service with a reference to a component to open a dialog container that renders this given component inside the component.html's ng-container. For example like so:
let dialogData = {}
dialogData.componentToRender = COMPONENT_TO_RENDER_INSIDE_OF_DIALOG
this.dialogService.openDialog(dialogData)
The idea is to make something like a dialog-container where the body can be any component i want to render inside of the dialog-container
I hope it is enough to write only the essential code, because I ask this question from another computer and could not copy paste the stuff I already have. thank you :)
For now I kind of solved this with ViewContainerRef.
I use the createComponent() method and give it the Component I want to render.
Then I insert the create ref inside my ng-template.
#ViewChild('container', {read: ViewContainerRef}) container!: ViewContainerRef
const componentRef = this.viewContainerRef.createComponent(MY_COMPONENT_DYNAMICALLY_GIVEN)
this.container.insert(componentRef.hostView)
This works but also renders my component selector tag around my content.
<my_inserted_component> <!-- I need to get rid of this :D -->
<!-- contents of my_inserted_component -->
</my_inserted_component>
That sadly results into Layouting problems. So now I need to find out how to change my CSS or (better) how to get rid of the outer tag with the component selector name.
EDIT: Also I should mention that I am on Angular 14

How do you create web components with customizable templates using Angular Elements?

I would like to create a library of web components using Angular Elements that have default templates but allow developers to override the output.
For example, consider a search-results component. I might have a default template that looks like this:
<h1>Search results for: {{query}}</h1>
But a developer might want to change the output to this (as an arbitrary example -- it needs to be flexible):
<h1>You searched for <strong>{{query}}</strong></h1>
My first thought was to simply use ng-content like this:
<search-results>
<h1>You searched for <strong>{{query}}</strong></h1>
</search-results>
However, this does not work and will literally output {{query}}.
I then discovered that I could add a TemplateRef input parameter on my component and pass in an ng-template element which would parse expressions. This works well within an Angular application, but I'm not sure how to do this outside of an Angular context using the compiled web components that Angular Elements generates.
Can anyone explain how to do this?
I see two solutions.
Solution 1: HTML template
Define a HTML template and pass its id to the Angular component. There you clone that node (see example in link) and add it to the DOM.
Placeholders ({{query}}) do not work "out of the box" in that template. You could replace them manually or just update the template and watch for changes in the Angular component. (Mutation Observer)
I'm working on this idea right now... I'll post an update here once my code is on GitHub so you can have a look at it.
Solution 2: JS templates
Another idea is to work with JS templates. (EJS for example)
You define a template string that you pass to the Angular component. There you render it with the given data object.
You can create a function that parse the .....{{variable}}... to ...value...
replaceText(content: string) {
const match = content.match(/(\{\{\w+\}\})/g)
match?.forEach(x => {
const variable = x.slice(2).slice(0, -2) || "fool"
const value = (this as any)[variable] || ""
content = content.replace(x, value)
})
return content
}
Then, you store the "ng-content" innerHTML in ngAfterviewInit. When you need, you call to this function.
Imagine some like -see that the "ng-content" is under a div "inner" with display:none
#Component({
template: `
<div class="alert alert-{{ type }} alert-dismissible" *ngIf="show">
<div [innerHTML]="newContent"></div>
<button type="button" class="close">
<span (click)="show = false; closed.emit()">×</span>
</button>
</div>
<div #inner class="hidden">
<ng-content></ng-content>
</div>
`,
styles:[`
.hidden{
display:none
}
`]
})
In ngAfterVieInit
#ViewChild('inner', { static: false }) inner!: ElementRef;
content:any;
ngAfterViewInit(): void {
this.content = this.inner.nativeElement.innerHTML;
}
And when you need
this.newContent = this.satinizer.bypassSecurityTrustHtml(
this.replaceText(this.content || '')
);
See a simple stackblitz
Use bypassSecurityTrustHtml method of DomSanitizer, provided by an angular; and bind it with html <div [innerHtml]="getSearchText()"></div>.
public getSearchText() {
return this.domSanitizer.bypassSecurityTrustHtml(`You searched for <b>${this.searchText}</b>`);
}
For more visit the angular documentation https://angular.io/api/platform-browser/DomSanitizer

Dynamic Property Binding for Angular Directive Inputs

Hi I'm making a a drag directive but because of the way it works I can't get it to work on dynamic objects as it calls the id in the input.
#Input()
set Drag(options: any) {
this.stickerElement = document.getElementById(options.id);
}
Which works fine when the element isn't dynamic:
<div id="sticker" class="issues" [Drag]="{id: 'sticker'}">
but when it's set dynamically I can't figure out how to interpolate the ID dynamically.
<div [attr.id]="'Session' + Day.id" [Drag]="{id:'Session' + Day.id}"></div>
I've tried setting this.stickerElement with the #HostListener when you use it but that allows the directive to bubble and use child elements. I guess I can work around it but it doesn't feel right.
I feel like I'm missing some knowledge because no matter what I google nothing useful comes up about how to interpolate it correctly. Can you interpolate an attribute into a directive like this?
Thanks in Advance
B
I don't see any issue in the interpolation. However, document.getElementById(options.id) in Angular looks dirty. Instead you could use a template reference variable and directly send the HTMLElement.
Try the following
Template
<div appSticker #sticker [Drag]="{ref:sticker}"></div>
Directive
#Directive({ selector: "[appSticker]" })
export class StickerDirective {
stickerElement: HTMLElement;
#Input()
set Drag(options: any) {
this.stickerElement = options.ref;
}
constructor() {}
}
Also I don't see the directive binding in the <div> tag in your code.

How can I render template with context in an Angular component?

I'm using ng-template in Angular8 to make a plural translation for displaying a notification to the user from the component but I cannot get full generated inner HTML of the template before attaching to DOM because context not bound yet.
How can I render a template with its context and get its inner HTML for this purpose?
I tried to use ViewContainerRef to render the template and attach it DOM and it works fine but I don't want to attach something to DOM and read it later.
Template:
<ng-template #activeDeactiveSuccessMessage let-card="card">
<span i18n="##card.notification">Notification</span>
<span i18n>{card.lockStatus, select,
LOCKED {Card number ending with {{card.number}} has been deactivated.}
UNLOCKED {Card number ending with {{card.number}} has been activated.}
other {Card number status changed to {{card.lockStatus}} }}</span>
</ng-template>
Component Code:
#ViewChild('activeDeactiveSuccessMessage', { static: false }) private activeDeactiveSuccessMessage: TemplateRef<any>;
Bellow is part of code to attach the rendered template to DOM and works fine:
let el = this._viewContainerRef.createEmbeddedView(this.activeDeactiveSuccessMessage, { card });
But I don't want to attach to DOM, want to get rendered template inside component before attaching.
used bellow code to get text but for second node which needed context, returns comment!:
let el = this.activeDeactiveSuccessMessage.createEmbeddedView({ card });
console.log(el.rootNodes[0].innerHTML); // --> Notification
console.log(el.rootNodes[1].innerHTML); // --> <!----><!----><!----><!---->
I expect the output of Card number ending with 6236 has been deactivated. for the second node.
Problem SOLVED!
The problem I encountered was because of my request to translate alert messages in the script at runtime, and I did not want to use ngx-translate.
Fortunately, in angular 9, with the help of #angular/localize, this problem has been solved and it is easy to translate texts into the script in this way:
#Component({
template: '{{ title }}'
})
export class HomeComponent {
title = $localize`You have 10 users`;
}
to read more visit https://blog.ninja-squad.com/2019/12/10/angular-localize/

Angular - Setting context with createEmbeddedView

I'm having a hard time trying to pass context data to my dynamicly created embeded view.
Note: I'm using Angular 2.4.7
Here is what i whant to achieve:
In my DialogPresetComponent (dialog-preset.component.html):
This component's view contain a bunch of template ready to be used within my dialog framework:
<template #presetInfo>
{{options.description}} // Note this binding ! Here is the problem
</template>
Still in this component, i get a ref to those templates like this:
#ViewChild('presetInfo', { read: TemplateRef }) presetInfo: TemplateRef<any>;
Then, i store this templateRef in a DialogService, so i can access them later, and from somewhere else.
In DialogComponent (dialog.component.html):
Here is the template of my modal:
<div class='c-header'>
</div>
<div class='c-body'>
<div #container></div>
</div>
<div class='c-footer'>
</div>
In DialogComponent (dialog.component.ts):
In my DialogComponent grab a reference to the container like this:
#ViewChild('container', {read: ViewContainerRef }) containerRef: ViewContainerRef;
I also define attributes that i want to access from my dynamicly injected template:
options: DialogOptions = { title: 'Dialog title' };
What am i trying to do:
I'm trying to put the template #presetInfo within the #container and with the context of my DialogComponent
So finaly, i inject my template in my component giving it the right context:
In my DialogComponent (dialog.component.ts):
// Where templateRef is the template previously defined in DialogPresetComponent
createEmbeddedView( templateRef: TemplateRef<any> ) {
this.containerRef.createEmbeddedView( templateRef, this );
}
The problem come from the fact that the binding {{options.description}} in my injected template DO NOT WORK, even when passing the right context ('this' in my case) via createEmbeddedView.
The framework tell me that options is undefined.
What am i missing here ?
There is not a lot of documentation about this 'context' stuff, so i guess i'm not doing it the right way ....
Any clues or hints are welcome !
Thanks !
this.containerRef.createEmbeddedView( templateRef, {$implicit: {description: 'some description'} } );
<template #presetInfo let-options>
{{options.description}} // Note this binding ! Here is the problem
</template>
If you pass an object with a property $implicit then just let-xxx is enough to make the $implicit value available as xxx within the template.
For other properties you need let-yyy="someProp" to make it available within the template as yyy.

Categories