Why/when would ng-content b a good practice? [closed] - javascript

Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 4 years ago.
Improve this question
Maybe a strange question regarding ng-content / Content projection. I get the idea that you can dynamically put in 'content' which can in turn be projected by using ng-content. One thing I could also do to achieve this is to make an Input property to pass in the information I want to show in the component.
I've come across this project; https://trimox.github.io/angular-mdc-web/#/home
which is the new material components for Angular. One thing I found out while browsing through the code is that he makes extreme use of ng-content to project some 'sub-components'. I cant help but wonder... why?? Because I can also try and solve this using one card component and a JSON/object like configuration which I pass on.
<mdc-card class="demo-card">
<mdc-card-primary-action mdc-ripple>
<mdc-card-media class="demo-card__media--16-9" [wide]="true"></mdc-card-media>
<div class="demo-card__primary">
<h2 class="demo-card__title" mdcHeadline6>Our Changing Planet</h2>
<h3 class="demo-card__subtitle" mdcSubtitle2>by Kurt Wagner</h3>
</div>
<div class="demo-card__secondary" mdcBody2>
Visit ten places on our planet that are undergoing the biggest changes today.
</div>
</mdc-card-primary-action>
<mdc-card-actions>
<mdc-card-action-buttons>
<button mdc-button mdcCardAction="button">Read</button>
<button mdc-button mdcCardAction="button">Bookmark</button>
</mdc-card-action-buttons>
<mdc-card-action-icons>
<button mdcIconButton mdcCardAction="icon" iconOn="favorite" iconOff="favorite_border"></button>
<mdc-icon mdcCardAction="icon" mdcRipple>share</mdc-icon>
<mdc-icon mdcCardAction="icon" mdcRipple>more_vert</mdc-icon>
</mdc-card-action-icons>
</mdc-card-actions>
Above you can see for example a 'card' component with several items in it. I've also found the source for these components; https://github.com/trimox/angular-mdc-web/blob/master/packages/card/card.ts.
My original question remains; when is it a good practice to use ng-content and when it's not. and why is it a good practice? I;ve also see DevExpress use it in their devextreme library; https://js.devexpress.com/Demos/WidgetsGallery/Demo/DataGrid/RecordGrouping/Angular/Light/
Thanks

Let me simplify your question. Let's say you want to create a card component. You could write your own child components (along with the code to perform the necessary content projection), like this:
<card>
<card-title>My Title</card-title>
<card-content>
<p>Content here...</p>
<card-action>Some button here</card-action>
</card-content>
<card-footer>My footer</card-footer>
</card>
...or you could just create one <card> component and have it take in a configuration object like this:
config = {
title: "My title",
content: {
innerHTML: "<p>Content here...</p>",
actions: [ {type: "button", text: "Some button here", onClick: "myFunction()"} ]
},
footer: "My footer"
}
So why would you want to go with the first option?
1. Semantic HTML
One reason is that, in general, it's good practice to separate structure (HTML), presentation (CSS), and behavior (JS / TS). Since a card component will probably contain other structural elements like its title, content, actions, footer, etc. it's best to express this structure in the HTML rather than do it through JavaScript/TypeScript. You should let your HTML markup convey the underlying meaning of your content. Doing so makes your code more semantic, clean, maintainable, and human readable. Perhaps if you're just developing an application yourself (rather than working in a team), then there's less of a need to make it semantic/readable to others because you understand your own code. But you should still make it readable for your future self because if you want to review your code a year later, you'll probably want your code to be semantic and easily readable.
2. Making use of Angular's template synatx
Another reason for going with the first option (using content projection + child components) is that you can make use of Angular's powerful features, such as data binding and *ngFor, in your HTML. For example, if you want to show a list of cards, with each card showing a social media post (like in Facebook), you could easily write:
<card *ngFor="let post of posts">
<card-title>{{post.title}}</card-title>
<card-content>
<p>{{post.content}}</p>
<card-action>
<card-button (click)="likePost(post)">Like</card-button>
<card-button (click)="sharePost(post)">Share</card-button>
</card-action>
</card-content>
<card-footer>{{post.footer}}</card-footer>
</card>
Whereas it would probably be more difficult to implement the same thing with a JS/TS configuration object.

Imagine If you are Passing Data from Parent to Child. Normally we will use Input property binding and we will get the data in child ts and we will bind the data. Instead of doing that ng-content we can place the content inside component tag from parent and bind your parent data and use ng-content inside child so that we can avoid input property binding
Parent Component
<parent>
<child>
<div class="head">{{heading}}</div>
</child>
</parent>
child component
<ng-content select=".head"></ng-content>
Here we are binding the parent data directly instead of using input property binding to process data

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.

React JS: data flow between grandparents/grandchildren

I intend to set up the following React Component
<tab-box>
<title>Example</title>
<butons>
<button id="actionID1"></button>
<button id="actionID2"></button>
<button id="actionID3"></button>
</butons>
<content>
<tabList>
<tab id="tab1" label="label1"></tab>
<tab id="tab2" label="label2"></tab>
</tabList>
</content>
</tab-box>
Where I would like to pull out each label attribute for tab to set up a nav bar on top of the actual content.
The question is, how do I pull out the attributes from nested children? Or how can I restructure the component so that I don't have this issue?
Thought1: use a global Store service, children like tab populate the Store and parents may retrieve them when being mounted
UPDATE, Thought2: make labels a prop on tab-box, but I still don't feel quite right..
There are ways to do it, but i doubt it is what you ultimately want. You want the parent to obtain information about the child? This doesn't make sense as you are trying to create "components" and not some code specific to one task.
Think of it this way. The tabs should be using tab component to display its own information. Under what circumstance would the tab know more than its parent? If so then is it even a component? Who is using who?
React is suited for one way data flows from parent to child, so it makes more sense to hold information in tabs component, and pass pieces of info to child tabs.
<tabs>
<button text={tab[0].text}/>
<tab>
write transcluded children here
</tab>
</tabs>
Even this begs the question - why are you using so many components to begin with? Why not use css classes to represent tabs container and individual tabs like a normal person?
If you insist on making a tabs component, at least keep it all in one component, something like
<tabs data={[{title:"tab-title", content: (<p>stuff</p>)}]} />
Also I absolutely do not agree with defining tabs in a model/store. common sense is UI and data should be separate.
As someone else mentioned you can use ref, but i'd reserve that for very limited purposes such as getting a field from a form.

In AngularJS, how can I nest variable child directive(s) inside a parent directive?

Introduction
For the project I am working on, I am trying to tackle a particular problem in the 'angular way', however I think I must be missing something because no matter what I try I continue to reach brick wall.
The crux of this issue is I am dynamically loading data from a backend that describes different components that are visible to the user. That's not the issue itself, but rather the issue of the particular & proper 'angular' way to turn a list of 'models' describing the components into actually rendered HTML.
Problem
What I am trying to create is basically the following:
Start off with a parent directive that uses ng-repeat for a scoped list called "models", which contains zero or more "components":
<parent-directive ng-repeat="model in models" model="model"></parent-directive>
The ng-repeat directive creates N copies of that original directive with different 'model' arguments (for each object in the $scope.models array).
// this is just for demonstrative purposes, it obviously looks different in source
<parent-directive model="child1"></parent-directive>
<parent-directive model="child2"></parent-directive>
<parent-directive model="child3"></parent-directive>
issue! => The parentdirective gets transformed into a specific child directive depending on data (in this case, called 'type') contained within the javascript object:
<parent-directive model="..."></parent-directive>
turns into
<child-directive-one model="..."></child-directive-one>
or
<child-directive-two model="..."></child-directive-two>
dependent on what the value 'model.type' is.
The child directive then renders into it's own custom HTML (outside the scope of this problem) using data passed to it. If we continued the example from above, that HTML should render into the following (hopefully):
<child-directive-one model="child1"></child-directive-one>
<child-directive-one model="child2"></child-directive-one>
<child-directive-two model="child3"></child-directive-two>'
Followed by (and this is outside the scope of the issue but just to see it through to the end) each directive rendering into its own HTML:
<div>in childDirectiveOne, text is: This is text contained inside child1</div>
<div>in childDirectiveOne, text is: This is text contained inside child2</div>
<div>in childDirectiveTwo, text is: This is text contained inside child3</div>
Source
I've been trying lots of different variations of things to try and get it to work (involving the link function, using $compile, etc), but this source is provided with all of those attempts stripped out. Here's the source I've developed so far:
removed source (was filled with errors). Solution that Scott helped me out with is below:
Conclusion
Thanks for any advice in advance.
Update:
Solution exists here (thanks again to Scott).
I'm not sure exactly why you can't just have a single directive, however something like the following might work. Instead of repeating the parent directive you just pass in the models and have that directive repeat and create each of the child directives.
<parent-directive the-models="models"></parent-directive>
Parent directive template:
<div ng-repeat="model in models"....>
<child-directive ng-if="YOUR CONDITION"></child-directive>
<child-directive2 ng-if="YOUR CONDITION"></child-directive>
</div>

Custom or Reusable HTML component

Can we create custom or reusable components in HTML?
My page has account search functionality which is implemented using HTML, JS, jQuery code. And my page consists of account search at different places. (Functionality is same tough). Only ID of the div changes at each time we using it.
So can we come up with kind of components in HTML?
Again, Writing the code in separate file and including at different locations wont work as ID changes at each area. ID matter as we make call to server, get data and update the fields etc.
Look into javascript templating. See mustache.js for one example.
e.g.
<script type="text/template" id="template">
{{#elements}}
<div id="{{id}}">
{{content}}
</div>
{{/elements}}
</script>
And your JavaScript:
var view = {
"elements":
[
{
id: "one",
content: "Lorem ipsum dolor"
},
{
id: "two",
content: "Sit amet consectetur"
}
]
}
var template = document.getElementById("template").innerHTML;
var output = Mustache.render(template, view);
console.log(output);
logs:
<div id="one">
Lorem ipsum dolor
</div>
<div id="two">
Sit amet consectetur
</div>
You can loop through objects, evaluate functions and insert them as text.
HTML is a markup language, not a programming language. Most likely you are looking for a template engine like haml
Following frameworks helps to solve the problem:
Mustache
HandleBars
I prefer to use HandleBar (which is written on top of Mustache).
If you use angularJS you can create a reusable html piece by making a separate html file with your component then calling it into your page as an element.
app.directive("elementName", function() {
return {
restrict: 'E',
templateUrl: "/Path/To/Html.file"
};
});
Then in every html page you want to use this component you just do the following:
<element-name></element-name>
Actually maybe you could use the separate js file, the implementation just needs to receive (maybe in the constructor, or if not a class, look up for a specific html tag) and use that required information when needed.
i love a good template engine, but you can use simple markup to decouple your logic from the html controls.
bootstrap does this using classes, and data- attributes work as well for more precise control.
you need to cleanup your code slightly; gather all the code you need, and detach all the refs to any given id. Usually, this is not difficult because you only have one or two spots that use the id, performing the rest of your operations using a variable.
wrap up all the code into one function that takes an element for it's only argument; this was formerly the one hard-coded to the id. move any IDs used in the code to classes or better yet, variables. change the css to reflect these classes and/or descendants of the widget class (more soon). at this point, you should be able to call the function and pass an element, and have it work. let's call it makeSearchFromElement().
function makeSearchFromElement(elm){
elm.onchange=function(e){ alert("searching for " + elm.value); };
}
once you can do that, all you need is an easy way to define them. let's use a class of "widget-search" as an example. add the class to any html element that needs the search functionality in your package.
<input class='search widget-search validation' id=search123 type=search />
now, all you have to do is find all the declared widgets in the HTML and send them to the widget maker somewhere in a site-wide JS file :
[].slice
.call(document.getElementsByClassName("widget-search"))
.map(makeSearchFromElement);
if you don't inject at the bottom of the body tag, you'll want to place that last bit in a ready() or onload() event.
that's all you need to do to enable any input to have the demo "search" ability simply by adding a class to the HTML, not a single line of CSS or JS needed to add each one. It lets you patch or upgrade each one on the site from one spot, and it reduces the amound of custom code to be shipped over the wire, making the page load faster than hand-binding or even template-ing them.
Refer this blog post. It elaborates "how to create reusable components" using Handlebar.js. Definitely worth reading it.
Below code is enough to add datetimepicker in your app.
{{datetime "d1" "Start Date"}}
jsFiddle for this
There is a quite easy way to do so by using the core html WebAPI features.
class ReusableHeader extends HTMLElement {
connectCallback() {
const currenctDirectory = this.attributes.currDir.value;
// Here you can access your attributes passed to the element
this.template = `<div>
<p>No brother <p>
<p> It should not be that much easy </p>
</div>`
}
}
customElements.define('my-header', ReusableHeader);
Ready to use
<my-header currDir="Home"></my-header>
The things you have to make sure before using it (keep it in header).
It should be defined first before using the element
The syntax is case sensitive so the element name should be like this () NO Capitals
ClassName should be like this ReusableHeader
Hope it works

Categories