How can I pass in generic HTML to an Angular 2 component? - javascript

I want to make a generic modal component that can hold anything, from just text to images/buttons etc. If I do something like this:
<div class="Modal">
<div class="header"></div>
<div class="body">{{content}}</div>
<div class="footer"></div>
</div>
I'm not able to actually pass HTML into content, just text. How can I create a component such that the parent component can pass in whatever HTML it wants? What if I wanted to add n number of buttons to the footer, each with it's own callback? Is there a better way I should be doing this?

What you are looking for is ng-content
<div class="Modal">
<div class="header"></div>
<div class="body">
<ng-content></ng-content>
</div>
<div class="footer"></div>
</div>
and you may pass any HTML content directly into your component.
Lets say your component name is my-modal, you may use it like below,
<my-modal>
<<HTML content : this will be replaced in the ng-content area >>
</my-modal>
Hope this helps!!

Related

How to set a dynamic "id" HTML attribute of an angular component?

My problem is related to placing the same component with different parameters on the same page. In this case it is a component containing a chart from a third party Javascript library (D3JS) which needs an HTML id attribute to locate and modify the component's HTML contents.
Now this id attribute should contain a unique string for each chart placed on the page, and if I directly set it as a string from the parent component it works just fine:
<my-chart id="gaugeChart0"></my-chart>
The reason it works is I guess, because the id attribute exists right at component creation time and whatever code is trying to access it can do that right away.
However this chart is in turn embedded into a bootstrap 4 card layout, like so:
<div class="row">
<div class="col-6">
<div class="card">
<div class="card-body">
<div class="row">
<div class="col-5">
<my-chart id="gaugeChart0"></my-chart>
</div>
<div class="col-7">
... Some other widgets ...
</div>
</div>
</div>
</div>
</div>
<div class="col-6">
<div class="card">
<div class="card-body">
<div class="row">
<div class="col-5">
<my-chart id="gaugeChart1"></my-chart>
</div>
<div class="col-7">
... Some other widgets ...
</div>
</div>
</div>
</div>
</div>
</div>
Now to make it more convenient (and to easily bind to click events to the whole block etc.) I would like to extract the whole part beginning with <div class="card"> into a new component.
Let's say I call this new component WidgetContainerComponent which contains the chart as well as the bootstrap card layout including the other widgets defined there.
The resulting code when using this wrapper component would be:
<div class="row">
<div class="col-6">
<widget-container chartId="gaugeChart0"></widget-container>
</div>
<div class="col-6">
<widget-container chartId="gaugeChart1"></widget-container>
</div>
</div>
In order for that to work the WidgetContainerComponent has an input field
#Input() chartId: string;
that can be set.
What I want to do then is to set the id attribute of the MyChartComponent to the string that has been set to chartId:
<div class="card">
<div class="card-body">
<div class="row">
<div class="col-5">
<my-chart [id]="chartId"></my-chart>
</div>
<div class="col-7">
... Some other widgets ...
</div>
</div>
</div>
</div>
But this does not work, as angular adds a prefix to the id attribute which results in something like ng-reflect-id.
I also tried setting the attribute with [attr.id] as described here and here:
<div class="card">
<div class="card-body">
<div class="row">
<div class="col-5">
<my-chart [attr.id]="chartId"></my-chart>
</div>
<div class="col-7">
... Some other widgets ...
</div>
</div>
</div>
</div>
This results in the MyChartComponent having a straight id attribute, but it seems to be only added at a later stage within the lifecycle of the component.
I also tried to initialize the chart within the MyChartComponent only in ngOnInit and ngAfterContentInit, but this does not work as well.
Any suggestions or ideas are very welcome!
So I found a solution to this problem. The problem was twofold:
The first problem was related to the requirement of having a genuine id attribute in contrast to the attribute name prefixed with ng-reflect- by angular.
What I didn't know is that from Angular 4, this prefix gets added to any attribute which is declared as an #Input variable for the component in development mode (for an in-depth explanation see this post).
The solution to this problem was to write [attr.id]=chartId instead of [id]=chartId. The reason is that in this case I needed to set an HTML Attribute, however using the square brackets notation I created a Property Binding. In order to dynamically set an HTML Attribute via the property binding syntax you have to add the prefix attr. to the attribute's name.
For a good overview regarding all valid binding syntaxes see the Angular Docs.
Specifically to review how to do attribute binding see this section.
The second problem was that I tried to access the id attribute of the component when it has not yet been created. This problem is related to the lifecycle hooks of Angular, where there are different stages. For a good overview of Angular's lifecycle hooks, see this page.
Because I didn't know it myself in detail, I will elaborate it here shortly.
Angular comes with a fixed set of lifecycle hooks which are called each time a component is set up and are also called in a predefined order. The hooks are defined as follows:
constructor
ngOnChanges
ngOnInit
ngDoCheck
ngAfterContentInit
ngAfterContentChecked
ngAfterViewInit
ngAfterViewChecked
ngOnDestroy
_
Important to note is that elements created or prepared in one step can only be accessed in any of the subsequent steps. In my case I put all the code for the initialization of the chart into the ngAfterContentInit hook, however the component is only fully loaded when ngAfterViewInit is called.
Finally the solution was to put the pre-initialization code into the constructor and to place the chart initialization code into the ngAfterViewInit method. At this stage the id attribute had been created properly and could be accessed by the chart.

Trying to access a specific child div from a container div

I am having trouble with a little site I have been working on; I want a sort of "stream" container that holds "cards" of "content," where this "content" is some "text" as well as some "stats."
This is the HTML I currently have:
<div id="stream">
<div class="card">
<div class="content">content
<div class="text">
blahblahblah
</div>
<div class="stats">
blahblahblah
</div>
</div>
</div>
<div class="card">
<div class="content">content
<div class="text">
blahblahblah
</div>
<div class="stats">
blahblahblah
</div>
</div>
</div>
<div class="card">
<div class="content">content
<div class="text">
blahblahblah
</div>
<div class="stats">
blahblahblah
</div>
</div>
</div>
</div>
Eventually, I want users to be able to prepend "cards" to this "stream" as well.
Now, however, I am trying to implement some jQuery function to hide the "stats" of a card until it is clicked on. So after setting display to none in CSS of the stats, I made this in a javascript file:
$(document).ready(function(){
$("#stream, div.card").click(function() {
$(this).find($("div.stats")).show();
});
});
It sort of works; the stats of a card are hidden until I click on a card. When I click on a card, however, all the cards' stats divs are shown.
I was hoping to somehow make it that the specific card clicked is also the (only) one that gets shown. Obviously, the current way I am doing this opens all of them as jQuery I have selects all the cards at once; how can I remedy this?
Again, I apologize if this question has been asked; I could not seem to find something similar, and I really want this to work . . .
P.S. I tried to search for this particular instance; alot of suggestions were to just give divs ids, but this feels inconvenient when I eventually want users to prepend cards?
Your selector ->
"#stream, div.card"
was basically asking for all #stream and all div.card..
But what you really meant was, find all div.card inside #stream. and this would be, (aka without the ,).
"#stream div.card"
Also you jquery find doesn't require you to convert into a jquery object, so find("div.stats") will do the trick.

changing only one part of the template with angular 2 routing

I have a template wit three sections one is the top navigation followed by left navigation and the content section which is the right div or element
<nav>top navs goes here </nav>
<div class="container>
<div class="row">
<div class="col-md-3" id="left-nav">
left navs goes here
</div>
<div class="col-md-9" id="content">
<!--Content goes here-->
<router-outlet></router-outlet>
</div>
</div>
The problem i am having right now is the <router-outlet></router-outlet> which is supposed to replace the HTM inside the div with an id of content doesn't do so. instead it grabs all the HTML of the same template and place it inside <div class="col-md-9" id="content">Content goes here</div>. Why duplicate the same template is this the normal behavior or there is something wrong with my implementation?
If you are changing just one part of your page one router-outlet is enough.
On the place that router-outlet is, component is injected. What component is injected is defined by your routing configuration.
Also change:
<div class="col-md-9"> id="content">
to
<div class="col-md-9" id="content">

How do I put a couple of elements inside template?

I need to create templates in handlebars for an html page and the whole html should go inside of templates. For e.g. I have:
<div class= "something-pull-left-something">
<div class="someclass">
<li a href= ''>Some more info and some more divs and spans and html code</li>
</div>
</div>
and I should create a big template for the first div ''something-pull-left-something'' and smaller templates inside of it for the other items and I cant quite understand how this should happen.
Divide it up into parts as it makes sense. Try to avoid having one huge template. Instead, make one template that includes a number of other templates. You may run into performance issues but worry about that later -- it is likely not an issue.
Make main template which contains header, body and footer.
Add partials to the main template.
If you wants to use Bootstrap then you can go through this http://getbootstrap.com/components
or you can use bootstrap class
<div class="container">
<div class="col-md-12">
//code
</div>
<div class="col-md-12">
<div class="col-md-6">
//code
</div>
<div class="col-md-6">
//code
</div>
</div>
</div>

just one of the ng-bind-html in page work

I write angularjs application and have this block of code but just first html-bind-html works for me
<div ng-bind-html='newsTitle'>
<div ng-bind-html='newsDetail'></div>
</div>
When i change the priority like this :
<div ng-bind-html='newsDetail'>
<div ng-bind-html='newsTitle'></div>
</div>
It shows newsDetail value.
How many ng-bind-html per page can i declare? why second value doesn't show?
I guess I understand your problem.
<div ng-bind-html='newsTitle'> <!-- This will replace the content of the div with $scope.newsTitle -->
<div ng-bind-html='newsDetail'> <!-- So this won't appear, because it has been removed by ng-bind-html='newsTitle' -->
</div>
</div>
Look my comments in the code. So if you put newsDetails in the first place, the binded HTML ($scope.newsDetail) will replace the current content aswell.
In a word, ng-bind-html replace the current content of your element with the binded HTML you provide. So you shouldn't put HTML in those elements.
You just have to do something like this :
<div class="news">
<div ng-bind-html='newsTitle'></div>
<div ng-bind-html='newsDetail'></div>
</div>
Some docs about ngBindHtml directive : https://docs.angularjs.org/api/ng/directive/ngBindHtml
If it's real copy of your html. Then I suppose that it's problem with structure. Please close your block:
<div> </div>
You can try and write it like this
<div><span ng-bind-html='newsTitle'></span></div>
<div><span ng-bind-html='newsDetail'></span></div>

Categories