Angular template not rendering - javascript

Right now I am creating a library (my-custom-library) and a project in which we'll use that library (called my-Project)
The requirement is, that within my-project I have to use my-custom-library, extended with templates, like this (my-project's app.component.html):
<my-custom-library>
<ng-template #myTemplate>
<div>Some static content for now</div>
</ng-template>
</my-custom-library>
The reason for this is, that in my-custom-library they want to have template-able components, where the template is given from the outside (in this case from my-project).
Within my-custom-library I'm supposed to access the given template(s) and pass them to the corresponding components. This I'm trying to achieve (my-custom-project's app.component.ts)
#ContentChild("myTemplate") myTemplateRef?: TemplateRef<any>;
(my-custom-project's app.component.html)
<ng-container [ngTemplateOutlet]="myTemplateRef"></ng-container>
My problem is, that the contentChild is always empty, the template never renders. The structure itself I think is working, since when I'm moving this same structure within just one project and use it there everything works fine, the contentChild gets its value and "my template" is rendered.
One more information, I don't know if its useful but my-custom-library is created like this (my-custom-library's app.module.ts):
export class AppModule {
constructor(private injector: Injector) {
const customElement = createCustomElement(AppComponent, { injector: this.injector });
customElements.define('my-custom-library', customElement);
}
}
What could cause this issue? Is it even possible to achieve this?

I had the same issue, Apparently ngTemplateOutlet does not work with angular elements. but you can try content projection without ngTemplateOutlet and it works fine.
e.g
<my-custom-library>
<div placeholder1></div>
</my-custom-library>
and you can define placeholder1 within your angular element (my-custom-library)
e.g
<div>
/*your angular my-custom-library code here */
/* the content you want to inject from outside of your angular elements*/
<ng-content select="[placeholder1]"></ng-content>
</div>
Note: you can also do nesting of your angular elements as well using this, but with this approach you have to make sure that ng-content is not affect by any ngif condition, because your angular element can project the content from outside but it can not regenerate projection based on your conditions. those conditions should be added from where you are projecting content.

Related

Vue 2 - Styling HTML returned in a switch statement

I'm building a generic component supposed to receive various backend data. I wrote a switch. One condition is :
case 'Bases':
let bases = variant[tableName][colValue].map((base) => {
return `<div class="base b${base.strongestIndication}">${base.name}</div>`
})
return bases.join('')
I use v-html in my template, therefore in this case, we have 3 divs created, each with a class of "base" and then "b1", "b2", "b3". I can see on the webpage that these classes are properly set when I inspect the elements.
I've described in my style some rules for theses classes (mostly background-color, border-radius and so on), but they do not apply.
I'm guessing this might have something to do with the CSS being applied before these divs are created but I'm not sure of this.
What should I do to get these tags created by JS to be styled properly ?
(Also, I know using v-html can be dangerous, therefore if you have a better idea for this whole thing, I'm all ears)
There is no need to build the markup in component methods. Define it in template and bind the classes dynamically. Since you didn't post the api data structure, neither how you are using this snippet of code, I can only refactor the specific case.
<div
v-for="base in colValues"
:class="['base', `b${base.strongestIndication}`]">
{{base.name}}
</div>
Figured it out. For those wondering, switch statements returning html code aren't styled if the tag in your file is scoped. Unsure why, but removing scoped work. If you decide to do so, but unique class names, as these styles will spread all throughout the application !

Angular component creation best practices

I'm often in doubt when I want a new behaviour of a component.
Let's make a simple example, I have <app-title> component:
<div>
<h1>{{title}}</h1>
</div>
Some time after, inside another page I need to put a button alongside the title. The problem is, should I create a new title component or should I parametrize the existing one?
I can edit <app-title> to look like this:
export class AppTitleComponent implements OnInit {
#Input() showButton: boolean;
title = 'App title';
constructor() {}
ngOnInit() {}
}
<div>
<h1>{{title}}</h1>
<button *ngIf="showButton">{{buttonTitle}}</button>
</div>
This is a simple example and may be obvious but using Angular I always have this problem, think about complex components: the #Input() would become many using this method, but creating a new component would increase files and complexity.
From this example you could say to create two components, one for title and another for button but that's only because this is a very simple case. Think about changing a component from "compact" mode to "expanded" and viceversa. On the one hand you may need to have the large component and on the other hand have it smaller in size and showing less information
Is there some guideline about this?
Thanks
I think it's important thinking about behavior within the context of your component. Is the button core to behavior of the title component? Does it make sense to not only display the button, but also handle its events within the context of the title component? If the answer is no, then at some granular level I'd split the components.
Here are some other things you can consider:
Anticipating that your title component may need some content wrapped with the title, you can use transclusion:
<div>
<h1>{{title}}</h1>
<ng-content></ng-content>
</div>
Then, in the parent, you'd do something like this:
<div>
<app-title-component title='title'>
<button>Some Button Text</button>
</app-title-component>
</div>
You could write a wrapper component that packages the title and the button together... ie:
<div>
<app-title-component></app-title-component>
<button>Some Button Text</button>
</div>
You could, as suggested, parameterize the configuration. I recommend thinking about if the behavior that you are parameterizing is part of the core behavior of the component. For example, if you want to paramaterize whether or not a legend shows on a chart, that makes sense because the legend is a core feature of the chart. But I probably wouldn't parameterize whether or not the chart should be followed by a raw data sheet. Instead I would create a new component for that and render them in sequence, because the data sheet is not part of the core behavior of the chart, even though sometimes I'd want to put them next to each other.
At the end of the day, you have to think about the decision in terms of your app, your app's future usability, and developer ease (e.g.- will it make sense to a future developer that this button is packaged with title).
I hope you find this helpful.

Angular > How to use a content query to get an element inside an template outlet

In an Angular app, I've developed a reusable component whose responsibility to take care of rendering a wrapper around content provided by the parent component. The wrapper provides some layout and functionality that is required by multiple components (the parents). To accomplish this, I've adopted the "Component Composition" technique outlined in the following article which relies on the use of NgTemplateOutlet to enable the parent component to provide the content rendered inside the wrapper.
https://blog.bitsrc.io/component-reusability-techniques-with-angular-727a6c603ad2
This approach has been working well in a variety of situations but now I've come across a new situation where one of the parent components needs to use a content query to get an element inside the template outlet. I've been unsuccessful in using either the ViewChild or ContentChild decorators to get a handle on the element.
The following pseudo code outlines the basic approach I've attempted to take to date.
Reusable Element:
<div class="card">
<ng-container *ngTemplateOutlet="chart"></ng-container>
</div>
...
#ContentChild('chart', {static: false})
chart: TemplateRef<any>;
Parent Component:
<app-shared-component>
<ng-template #chart>
<div #top10Graph></div>
</ng-template>
</app-shared-component>
...
#ViewChild('top10Graph', { static: false }) private top10GraphContainer: ElementRef;
ngAfterViewInit(): void {
console.log(this.top10GraphContainer); // undefined
}
Is there any solution for using a content query for obtaining an element inside a template outlet such as I'm using here, or is another approach required?
The end goal (not demonstrated here) is to enable the parent component to render a data driven graph inside the template outlet.
I'm using Angular 10.
I think you get undefined because using ng-template is not yet rendered.
This code can work for you
<app-shared-component>
<ng-template #chart>
<div #top10Graph></div>
</ng-template>
<ng-container *ngTemplateOutlet="chart"></ng-container>
</app-shared-component>
Hope useful

injecting dynamic html inside the components from index.html in angular 8

I am working on a web app. which has magnolia as CMS. i am still new here but i will explain the scenario.
magnolia is providing the index.html which has reference of the components.But for some of those components it also provides some additional html content. this additional content has to be shown inside the particular component's template. refer following for better understanding.(remember this is inside the index.html)
<my-component>
<div> this division has to be shown inside the my component template.</div>
</my-component>
I have tried following approaches till now.
trying to use - this apparently does not work as i have learnt recently that the angular is not the master of root template(index.html). so ng-content never works. correct me if i am wrong.
using shadow dom view encapsulation. I am not expert in this, but setting viewencapsulation = shadowdom and defining slot inside the component template fulfills my purpose. The only issue with this approach is the scope of this shadow element. it will be inside the shadow root which has its own scope so no global styles are applied inside it. i had to import all the global css for each such component, which makes the main.js go crazy in size.
can someone please suggest me if there is any better or other solution to this problem?
Have you tried using Input() values for that component?
index.html
<my-comp [myInputs]="'My Input HTML <b>Content</b>'">
your receiving component…
<div [innerHTML]="myInputs"></div>
On my side, I use <ng-content></ng-content> inside the component where I want to put my dynamic content.
Ex:
<app-help-tool>
<span i18n="##pictoTable_helpPictoList">Click on a picto to see its description.</span>
</app-help-tool>
And in HelpToolComponent.ts:
<div
class="help_content"
*ngIf="this.isVisible && this.helpService.isHelpOn()"
[#showHideHelp]
(click)="showHideHelp()"
>
<ng-content></ng-content>
</div>
The result is to put the span content into the div of the component.

Assigning Angular structural directives during runtime

I am trying to assign *ngIf directive from angular code to the template during runtime. Not been able to figure out a way to do it. Is view/templateref an option to do it or is there a different way and an easier one. Is it possible in the first place?
Update:
The code is a little messy and jumbled, so avoided it. But here is the DOM code how it approximately looks and why I need to add inbuilt structural directives dynamically.
<div>
<input type="text" [value]="userProvidedValue">
<textarea [value]="someDynamicDOMCodefromWYSIWYG">
<!-- user provided provided code or dynamic code -->
</textarea>
</div>
<div>
<select *ngIf="fetchArraywithHttpFromuserProvidedValue">
<option *ngFor="let val of fetchArraywithHttpFrom-userProvidedValue" value=""></option>
</select>
</div>
<div>
<ng-template>
<!-- Some User provided code or dynamic code which might need to use *ngIf OR *ngFor -->
<!-- The *ngIf OR *ngFor will be added dynamically based on a manipulator function which is decided from the value of fetchArraywithHttpFromuserProvidedValue -->
</ng-template>
</div>
Update
I am doing a fetch request based on userProvidedValue value and the result of the fetch request decides the fetchArraywithHttpFromuserProvidedValue array. Second, based on the value of fetchArraywithHttpFromuserProvidedValue derived from fetch request the decision is made whether to show the user provided template or a predecided set of templates in switch option. (only part of user provided template needs the *ngIf directive. The user template is parsed in JS to get the needed part). The use case is similar to a tool that creates a HTML design/page which fetches structure from a user. This is exactly for a similar tool, just that I am not making a tool that creates a user defined HTML page.
Please help me out with this. If this is not possible, then please recommend an alternative that will allow me to assign functionality similarly or get me a workaround in this situation.
Update 2
Like pointed out in one of the answers below, all of the following templates failed to be added with proper parsing/compilation with elementref or using ViewContainerRef + TemplateRef:
<input [value]="someVariableFromClass"/>
<input value="{{someVariableFromClass}}"/>
<div *ngFor="let item of items">{{item}}</div>
The following works though, if the template is in the DOM before the application is being built and loaded (not dynamic addition):
<ng-template #tpl>
<!-- Add the template to #vcr using ViewContainerRef from the class -->
<div *ngFor="let item of items">{{item}}</div>
</ng-template>
<div #vcr>
<!-- Add the template from #tpl using ViewContainerRef from the class -->
</div>
Currently, I am trying out the compiler API in Angular and checking if compileModuleAndAllComponentsAsync<T>(moduleType: Type<T>): Promise<ModuleWithComponentFactories<T>> can help me in this use case. The issue seems like I will have a create a completely new component by assigning the html as a template to the component, then create a dynamic module, and then compile the whole before inserting into the view (Logic I am trying out currently - not working yet). After this (if I succeed), I will try adding the component template with a directive and see if that compiles right. Any help is welcome. It seems like I might end up by adding static directives to manual placeholders and adding [innerHTML]= / safeHTML / Sanitize API, if I dont succeed. Not ideal though. Please help with alternatives if you can.
I am using this example, though it's plunkr currently not working.
How can I use/create dynamic template to compile dynamic Component with Angular 2.0?
http://plnkr.co/edit/wh4VJG?p=preview
You don't call a fetch method inside an *ngIf. The ... inside *ngIf="..." gets executed every time angular decides to do change-detection and that might be dozens of times per second. You don't want to deploy a DDOS for your own backend.
This is why you should put a field like isUserProvidedValueValid there and update that field in the subscription of your HttpClient-call:
userProvidedValue: any;
isUserProvidedValueValid: boolean = false;
constructor(private httpClient: HttpClient) {}
doFetch() { // called by a button-click for example
this.isUserProvidedValueValid = false;
this.httpClient
.get<any>(SOME_URL)
.subscribe(res => {
if (res) { // you might have a complex check here, not just not-undefined
this.isUserProvidedValueValid = true;
}
// you might consider putting this in the if-clause and in the *ngIf only check for userProvidedValue being not null
this.userProvidedValue = res;
});
}
Now for the code that your user provides: first of all, you need to sanitize it. You can do it with a pipe inside a directive (you don't need to use ng-template, you use innerHtml of a normal tag for it) like in this example: https://stackoverflow.com/a/39858064/4125622
template: `<div [innerHtml]="html | safeHtml"></div>`,
Or before the .subscribe() in the code above, you can do
// domSanitizer needs to be injected in constructor as well
.map(res => this.domSanitizer.bypassSecurityTrustHtml(res));
If you need to transform this code, you can add another .map()-RXJS-mapper or another custom pipe - it's up to you which kind of transformer you prefer. In the transformer you can use VanillaJS for manipulation of the user-code. Perhaps an HTML-parser-plugin or a JSON-toHTML-parser-plugin or something similar might be useful to you - depends on the data type your user provides.
No you can't add structure directives dynamically, you need to approach it by thinking what is your app expecting later on.
For example, if you plan on looping through an array which I'm guessing is what you intend to do having looked at your code, you could do the following:
hotels: any;
<div *ngIf="hotels.length > 0">
<div *ngFor="let item of hotels">{{item.name}}</div>
</div>

Categories