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.
Related
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.
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
I keep reading that when in doubt I should turn an element into a component. So are there actually any benefits to turning form elements such as <input /> to components.
Consider the following:
const SomeComp = (props) => {
return (
<form className='someClass' onSubmit={props.handleSubmit}>
<Input
className='someClass'
type='text'
id='someId'
placeholder='Enter Something'
onChange={props.handleChange}
value={props.side}
/>
</form>
)
}
Unless I'm using options like autoCorrect, autoCapitalize, spellCheck the only things I'll save by wrapping the input into a component like <TextInput/> and importing it to various forms is not adding the type prop to each input and maybe the fact that the error for the input is not declared at the top of the form.
Is there anything else I'm missing? What's the most beneficial way to approch such form elements and why?
Usually you will not want to turn very simple elements like inputs into separate components, unless you are expecting some special functionality from them.
The simplest way to go through is to always have, for each functional need (like a login page for example), a smart component for handling the behavior and functionality, and one dumb components for displaying the ui elements.
When you start feeling there is too much code in some of your smart components, or a dumb component has gone to large you can start dividing them. Otherwise try to keep it simple.
I use styled-components and do stuff like this all the time. The basic idea with Styled Components is that you attached CSS styles to your component instead of creating CSS classes to target, for example, all input elements or using inline styles which limit what you can do (i.e. media queries, psuedo-selectors, etc). And it allows you to separate your components functionality from it's presentation (within the same file).
Here is a link to a youtube video on the topic: https://www.youtube.com/watch?v=jaqDA7Btm3c
This way, you avoid the need for a global CSS file that is hard to maintain and doesn't exactly fit well within the webpack ecosystem that views your assets as a graph of dependencies.
If you are not interested in styled-components, and are happy with the way you style your form elements, then I see no reason to create a custom Input component.
For example, in Facebook's docs on React forms, they do not create custom form components.
https://facebook.github.io/react/docs/forms.html
Unrelated:
If you don't already know, you can write your example like so:
const SomeComp = props =>
<form className='someClass' onSubmit={props.handleSubmit}>
<Input
className='someClass'
type='text'
id='someId'
placeholder='Enter Something'
onChange={props.handleChange}
value={props.side}
/>
</form>
(removed the () around the single argument, and removed the {} and return statement. Like I said, unrelated, but thought I would mention it in case you were not aware.
I am using EmberJS CLI and I am trying to wrap my head around components. However I don't quite understand what the best way is to communicate between them. I am trying to make a simple graphics editor with a side menu and a canvas. I have a "graphics-editor" component which contains the "side-menu" and "editor-canvas" components as children. If I choose an option such as "insert text" on the "side-menu" component, how do I tell the "editor-canvas" to add some text?
I have heard of actions up, data down. So should I pass the side menu model to the canvas to observe (I may also have a menu bar which can tell the canvas to do stuff later on too, so I would also have to observe that somehow)? So whenever an action on the side menu occurs, its model is updated and the canvas can observe this and act on changes to it?
Or is there a clean way to bubble the side menu action up to the parent editor component and then catch it and call an action on the canvas component?
Or should I be using views and controllers (even though EmberJS seem to be steering people away from them) instead of components. Then the controller of graphics-editor can catch an action and pass it onto editor-canvas using controllerFor?
Or should I be using Ember.Evented?
In my main application template I have:
{{graphics-editor model=model}}
My graphics editor (graphics-editor.hbs) component looks like:
<div class="ui-layout-north nopadding">
{{file-menu menuItems=model.fileMenu.menuItems}}
</div>
<div class="ui-layout-west">
{{side-menu addLabel='addLabel'}}
</div>
<div class="ui-layout-center nopadding emptyBackground">
{{editor-canvas canvasModel=model.canvas}}
</div>
<div class="ui-layout-south nopadding">
{{editor-footer}}
</div>
Keep the components. In this case of sibling components messaging each other quite a lot (side menu and canvas), I think it warrants using Ember.Evented.
You could have a event bus service (Ember.Service.extend(Ember.Evented)) injected in your components for a pub/sub mechanism. For instance in your side menu component:
eventBus: Ember.inject.service(),
actions: {
insertText: function(text) {
this.get('eventBus').trigger('insertText', text);
}
...
}
to which the canvas component would be subscribed.
I recently wrote about this subject: http://emberigniter.com/parent-to-children-component-communication/
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.