How to render a content inside directive at various places - javascript

I want a component directive to render anything put inside it's selector to be rendered at specific sections inside its html.
header, footer, main in this case.
any.html
<any-selector>
<div>content to place</div>
</any-selector>
Expecting it to render following
any-selector.html
<header><div>content to place</div></header>
<main><div>content to place</div></main>
<footer><div>content to place</div></footer>
tried it with ng-content but it rendered only at first occurrence of <ng-content>
If there's any way to achieve this?

So, this is an expected behavior from ng-content, either you put [select] to point to certain ng-content or you can render just on the first occurrence of ng-content.
I tried a way which has worked for me. Here is the demo working code
<any-selector>
<div #myProjection>content to place</div>
</any-selector>
and then in app-selector.HTML you can do :
<div id='border'>
<h1>Hello Component</h1>
<header><div #canvas></div></header>
<footer><div #canvas></div></footer>
</div>
app-selector.component
#ViewChildren("canvas") canvas: QueryList<ElementRef>;
#ContentChild("myProjection") str : ElementRef;
ngAfterViewInit() {
this.canvas.forEach((div: ElementRef) =>
div.nativeElement.insertAdjacentHTML('beforeend', this.str.nativeElement.innerHTML));
}

Related

Angular: render numerous components on (click)

I do not want to render a new component or go to a new route, this is not my intention. I cannot use a single variable with an *ngIf to handle rendering the component as I cannot predict the amount of variables I will need to render.
Here is the situation.
<div ngFor="let stuff of lotsOfStuff">
<div (click)="generateAnotherComponent()">
<span>some basic info</span>
<my-child-component></my-child-component> //component to render on click
</div>
<div (click)="generateAnotherComponent()">
<span>some basic info</span>
<my-child-component></my-child-component> //component to render on click
</div>
</div>
You can generate the component dynamically.
There are various ways to achieve this, but one would be to use a template variable placed on an element, for example
<div #ChildInsertionPoint></div>
And then targeting this "insertion point" as where you'll place the newly generated components by using ViewContainerRef:
#ViewChild('ChildInsertionPoint', { read: ViewContainerRef })
childInsertionPoint: ViewContainerRef;
generateAnotherComponent() {
this.childInsertionPoint.createComponent(ChildComponent);
}
Dynamic component generation became a lot easier with Angular 13 (that's what I've assumed you're using). But if not
Finally, I'm assuming that you will bind your click event to a button instead of the div element itself.
Here's a small example on Stackblitz

ng component not useful

I was learning the ng-content. Why need to use it when we can easily write like this
with ng-content
menu.component.html
<div>
<app-message> <h1>Laptop</h1></app-message>
</div>
message.component.html
<div>
<ng-content></ng-content>
<p>something text to be display</p>
<button > Submit</button>
</div>
without ng-content
menu.component.html
<div>
<h1>Laptop</h1>
<app-message> </app-message>
</div>
message.component.html
<div>
<p>something text to be display</p>
<button > Submit</button>
</div>
Using <ng-content> allows you to create flexible reusable components.
For example, a custom app-modal component:
<div class="modal">
<div class="modal-title">
{{title}}
</div>
<div class="modal-body">
<ng-content></ng-content>
</div>
</div>
This is very powerful, and only the start of what you can achieve with <ng-content>
A core prinpiple of software engineering is code reuse (DRY). With this we can bundle all of the logic relating to a component into one component, and inject the content into it.
This is like asking the question
Why not just declare your CSS styles inline instead of in CSS classes?
It is possible, but unmaintainable, and we have evolved beyond that.
Your example is fairly trivial, but it is still useful if you wanted to change style or behaviour based on some injected logic.
There exist some situations when is necessary, maybe no in simple cases but when the app grows up and you have to insert a custom component inside another is very usefully
As you can see from your own example, without using ng-content, the parent component suddenly is partially responsible for the child's layout. It'll get messy real quick, the quicker the more complex the layout gets.
It's useful for displaying components inside of other components, so the components can be reusable. For example, if you want to display some text on a few pages, you call the component with the text inside of the other components that you want the text to be displayed on. It could also be useful for showing different information but styled in the same format. E.g.
if your ng-content already has the information this is an example of how it would be used:
menu.component.html
<ng-content></ng-content>
message.component.html
<ng-content></ng-content>
This is good because you can copy and paste the exact component into another one without having to rewrite the code
If your ng-content is looking for a data source for information to pass into it
<ng-content [data]='data'></ng-content>
This is good because you can recreate the component inside of another component but with different data inside of it.
If you've ever used react, you pass data into it in a similar way here as you would with react props, but instead of props in angular, it will be an #input field. Here is some example code
test.component.html
<ng-content [data]='THIS IS THE DATA'></ng-content>
This is the actual component, as you can see, it is looking for a data source
ng-content.component.html
<p>The data we are looking for is {{data}} </p>
ng-content.component.ts - this says that when the component is called, it is looking for an input called 'data' and the type has to be a string
#Input() data: string;
We would then see the test.component.html displayed like this:
The data we are looking for is THIS IS THE DATA

How to select content from inner children in ng-content

I have a component called tab which has <ng-content select="[tabItem]"></ng-content>
Sometimes tabItem is inside other child components. My problem is Angular selects the content from direct children, not inner children (app-my-tab), is there any way to do it?
app.component.html
<app-tabs>
<div tabItem>
Tab 1
</div>
<div tabItem>
Tab 2
</div>
<app-my-tab></app-my-tab>
</app-tabs>
my-tab.component.html
<div tabItem>
My Tab
</div>
<div>
Other content
</div>
See this stackblitz
There is no solution for deep selection.
I thing it is logical, because:
understand and real code easily
easy to debug.
If you want really do that use *ngIf in app-my-tab.
To use *ngIF:
All element in app-tabs must have tabItem attribute
send your condition to show/hide some other element in to app-my-tab component. and app-my-tab receive it as #Input() property
in app-my-tab html use *ngIf to show or hide some element
Example: https://stackblitz.com/edit/deep-ng-content-2gyttv?file=src/app/app.component.html

Partial accordion-style animation on Angular component with various templates

I'm making a production app that has a card-style layout. Every filter (date pickers, etc) and visualizers (table, chart) is wrapped inside a "Card" component. There are even two child components that inherit from this class.
So in the end, this is how a pair of cards look, so you can have an idea on what's going on:
As you can see, the cards have 3 parts:
A header
An inner component (which is put inside a ng-content)
A footer with some summary information
The requisite we currently have, is to be able to click in the chevron (that icon in the card header right), and hide just the inner component. This is the result I want to achieve:
But I'm having a problem with this, because of how I designed these cards. Look at how the card code parent class looks:
#Component({
selector: "card",
styleUrls: ["./card.css"],
template: `
<div class="col card" [#animate]="enabled">
<div class="row card-header">
{{title}}
<i (click)="switchVisibility()" class="fa fa-chevron-down icon-right"></i>
</div>
<div class="margin" [#animate2]="enabled">
<ng-content></ng-content>
</div>
`
Implementation:
<card [title]="'DATE SELECT'" class="col">
<date-picker-wrapper class="date-wrapper" [config]="config" [dateranges]="dateranges" [doubleDateRange]="false">
</date-picker-wrapper>
</card>
You probably have already spotted one of the problems here: all of the inner component is inserted in the ng-content, therefore, if I hide the component with an animation located at the Card (which is the idea, so I can inherit this method in the child card classes)... I will hide the footer too. And that's not the plan.
I guess I need to wrap inside the ng-content the upper side template, and the footer in another different template or something. But I don't know how to do it, and the guides regarding ng-template didn't seem to be very clear. Also, I'm not very sure of what's the best approach to achieve this result.
This is how a component's footer looks, in this case, the daterangepicker:
<div class="footer-component">
<hr>
<p class="footer-text">
<span style="color:#ff8716;">
{{ config?.daterange?.rangelabel ? config?.daterange?.rangelabel : "Custom" }}
</span>
|
{{ getFormattedDate(1, true) }} - {{ getFormattedDate(1, false) }}
</p>
<button class="btn btn-relocator" (click)="openDate(datemodal)">Edit</button>
</div>
One of the problems I see here, is that this footer actually requires some functionality of the component, some methods that are not global and therefore, not accessible from outside of the component.
Then, I've got a few questions:
Should I replicate this animation in all my components, inside their template? Wouldn't that be a little bit dirty? How would you populate the state of the chevron icon click to the inner component?
If there is a way of inserting two templates in the Card component, how do you think I can access the component's logic from the footer template?
Any comments regarding how I should "hide" the content? I've got an idea using "*" height as for "auto", but I'm not sure if that even works.
For hiding the , you need to use a boolean toggle variable.
In your component, declare variable as below:-
enabledContent = false;
Then try to modify your html like below:-
<div class="col card" [#animate]="enabled">
<div class="row card-header">
{{title}}
<i (click)="enabledContent=!enabledContent;switchVisibility();" class="fa fa-chevron-down icon-right"></i>
</div>
<div class="margin" [#animate2]="enabledContent">
<ng-content></ng-content>
</div>
Click on this for sample code
because I am not aware [#anumate2], in sample link it is [hidden] .
For enable a method global, you may achieve by doing below step:-
First create a service,
myservice.service.ts and inside write your function(sample code showing only):-
public getFormattedDate(1, true): any {
return result;
}
Now in your component , declare your service as below(sample code showing only):-
import { Component, OnInit,} from '#angular/core';
import { MyService} from './myservice.service';
#Component({ selector: ... templateUrl: ... styleUrls: ... })
export class MyComponent implements OnInit {
constructor(private myservice: Myservice) { }
ngOnInit() { }
}
Then in your html file, use your service method as below(sample code showing only):-
<div class="footer-component">
{{ myservice.getFormattedDate(1, true) }} - {{ myservice.getFormattedDate(1, false) }}
</div>
I tried to use variable from a service.ts inside html file, it works fine, but I haven't tested calling method from service.ts. Hope it will work. If it is ok, then you can call your service method from anywhere as services are global in angular and just need to declare in component to be used.

Angular 1.5 nesting components

I have a container component that has 2 other controllers nested in it. I have them set up like so
container -> component1 and container -> component2
My train of thought here was that I could transclude the HTML from my page into my container component.
<container> <component1></component1> <component2></component2></container>
and then in the container HTML <div ng-init="vm.init()" ng-transclude></div>
Problem is, my container isn't running at all, I added a console log to its init function and no code is running. While component1 and component2 run their init's.
Seems to be a large amount of documentation covering older angular versions. Which tell me that they need to be nested like so <div ng-controller="parentController"> <div ng-controller="childController"></div> </div
How do you nest components into each other in 1.5?
Looks like the issue was that I was attempting to put a ng-init and ng-transclude in the same element. My guess is that ng-transclude overrides everything in the element it is on.
so I moved the ng-init to another element and its working fine
<div ng-transclude ng-init="vm.init()"></div>
changed to
<div ng-init="vm.init()"> <section ng-transclude ></section> </div>

Categories