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
Related
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));
}
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
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
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.
Case in point, I am developing a multi-select control in Angular2, similar in functionality to Select2 or a multitude of other controls.
I started by defining what I want the user interface to look like in terms of defining what's included in the dropdown, and came up with two options.
One is to use #Input()s for the options:
<my-multi-select [options]="options"></my-multi-select>
...and then within the template for my-multi-select:
<div class=option" *ngFor="let option of options">{{option.display}}</div>
Ahother is to use transclusion, which is how material2 appears to do it:
<my-multi-select>
<my-option *ngFor="let option of options" [value]="option"></my-option>
</my-multi-select>
...and then within the template for my-multi-select:
<div class=select-container">
<ng-content select="my-option"></ng-content>
</div>
I was content with the transclusion option, but then when I started to actually implement it rand in to difficulty binding the events coming from my-option to my-multi-select. I could try to figure out a way to notify my-select of things that are happening in my-option, like using an Observable, or digging deeper in to using an #Output event -- but that feels like trying to jam a square peg in to a round hole when #Input variables might just be simpler.
This led me to the question, is transclusion even appropriate here? And the bigger question, when is transclusion appropriate, and when is using transclusion jamming a square peg in to a round hole?
For your example, it's simple to compare the two aproaches as you are only including one text item as part of the transclusion. This makes using #Input() trivial and may well be the best solution.
However, imagine a scenario where you have multiple elements you want to include in your child component, each with custom HTML tags along for the ride. Using trasnclusion this is trivial, but using #Input() it requires a few "hacks" to get right and isn't very maintainable or extendable.
Explanation
Building on from this Todd Motto blog about transclusion as a reference, we can utilise transclusion to have more complex HTML for our title and content sections without issue.
Transculsion Parent
<my-component>
<my-title>
<h1>This is the Component title!</h1>
</my-title>
<my-content>
And here's some awesome content.
<ul>
<li>First</li>
<li>Second</li>
</ul>
</my-content>
</my-component>
Transclusion Child
<div class="my-component">
<div>
Title:
<ng-content select="my-title"></ng-content>
</div>
<div>
Content:
<ng-content select="my-content"></ng-content>
</div>
</div>
Now imagine this same scenario using only #Input() to declare our elements.
Our binding from the parent isn't very friendly, we definitely don't want to do this for more complex scenarios.
In our child component we have to use [innerHTML] to get around interpolation encoding in Angular. Again, this isn't very easy to extend or maintain. This is where in my opinion transclusion really excels.
Input Parent
<my-component
[my-title]="<h1>This is the Component title!</h1>"
[my-content]="And here's some awesome content.<ul><li>First</li><li>Second</li></ul>">
</my-component>
Input Child
<div class="my-component">
<div [innerhtml]="'Title:' + my-title"></div>
<div [innerhtml]="'Content:' + my-content"></div>
</div>
This led me to the question, is transclusion even appropriate here?
If want html to look like:
<my-multi-select>
<my-option *ngFor="let option of options" [value]="option"></my-option>
</my-multi-select>
Where my-multi-select and my-option are components, then transclusion is the way to do it.