I have a React component that works 'standalone'. It allows child components to be editable (dynamically) with an 'editable' state (editable = true makes children editable, editable = false does not)
import Editable from './editable'
<Editable>
<div>edit me!</div>
</Editable>
Next to this component I have some 'sub components' that could be used as children for the 'main component' (Editable). The can be used to specify other behavior for their children, when Editable's state is set to 'editable = true'. I don't want to import these sub components all separately. I know some ways to achieve this, I will specify them below the question.
But what I'm looking for is syntax like this:
import Editable from './editable'
<Editable>
<div>edit me!</div>
<Editable.Hide>
<div>don't show me when editable</div>
</Editable.Hide>
<Editable.Not>
<div>don't make me editable when editable</div>
</Editable.Not>
</Editable>
So the export needs to be structured so that the usage of the default export will result in the main component, but (somehow) the sub components can also be accessed through that same default export.
Why? Mostly my curiosity into the possibilities and I would love to use a syntax like the above.
So is it possible to structure an export to be able to use a syntax like that?
These are the ways I already know how to import components with sub components:
import Editable from './editable'
<Editable.MainComponent>
<div>edit me!</div>
<Editable.Hide>
<div>don't show me when editable</div>
</Editable.Hide>
<Editable.Not>
<div>don't make me editable when editable</div>
</Editable.Not>
</Editable.MainComponent>
If I would only want the main components, I could do this with some filestructure in the editable folder and import like this:
import EditableMainComponent from './editable/mainComponent'
<EditableMainComponent>
<div>edit me!</div>
</EditableMainComponent>
Or create a named export with only the main component in the same file.
import {EditableMainComponent} from './editable'
<EditableMainComponent>
<div>edit me!</div>
</EditableMainComponent>
Another way to go is to keep only the main component as the default export and the sub components as named exports.
I am trying to avoid usage like this:
import Editable, {EditableHide, EditableNot} from './editable'
<Editable>
<div>edit me!</div>
<EditableHide>
<div>don't show me when editable</div>
</EditableHide>
<EditableNot>
<div>don't make me editable when editable</div>
</EditableNot>
</Editable>
Because I don't want the user to have to specify all the different sub components in the import. So that could also be achieved like this:
import { * as Editable } from './editable'
<Editable.default>
<div>edit me!</div>
<Editable.Hide>
<div>don't show me when editable</div>
</Editable.Hide>
<Editable.Not>
<div>Don't make me editable when editable</div>
</Editable.Not>
</Editable.default>
If components are self-sufficient and can be used separately, it's preferable to consider them of the same value and treat all of them as named exports:
import {Editable, EditableHide, EditableNot} from './editable'
If some components aren't supposed to be used apart from main component, they can be namespaced with it.
For class components:
class Not extends Component {...}
export default class Editable extends Component {
static Not = Not;
...
}
For functional components:
const Not = props => ...;
const Editable = props => ...;
Editable.Not = Not;
export default Editable;
The advantage of the last approach is that this improves testability by mocking or spying secondary components in tests, as long as they are referred as Editable.Not and not Not inside Editable main component.
The disadvantage of the last approach is that secondary components cannot be tree-shaken, this shouldn't be done in case their footprint is large and main component can be often used without them.
Related
In React, you can pass entire components into other components, and then easily display those components in the respective div. This referring not to imports, but rather function parameters so what is displayed will vary.
In Angular, you use #Input() and #Output() to do the same thing with values and functions, but what about components? How do you do this with components? I'm not talking about imports handled by the module file or at the top of the file; I'm talking about parameters that will vary based on the runtime of your program.
I.e, I want to convert the following React code into Angular, where children is another React component passed in via ReactNode:
const ReactComponent = (props) => {
return (
<div>
{props.children}
</div>
);
};
Also I apologize if any of my terminology is incorrect; I'm new to Angular and I'm coming from a (limited) React background.
I tried using #Input() with a parameter of type "any" but this doesn't seem right.
Dynamically, like React, you can use ng-content. This is the opposite of Reacts { props.children }
Use it so (Component - Code Behind)
#Component({
selector: 'btn',
template: `
<button class='btn' (click)="do()">
<ng-content></ng-content>
</button>
`
})
export class ButtonComponent{
do(){
console.log("Ok")
}
}
And the HTML
<div>
<btn>
This is a dynamic Button
</btn>
</div>
This is a dynamic Button can be a component, too. So like this:
<div>
<btn>
<div><span>Hello! </span><button>I'm a button inside a button!?</button></div>
<app-my-custom-component [data]="bindAnything"></app-my-custom-component>
</btn>
</div>
The Result can be look like this:
And here you can play with this on Stackblitz.
Read all about it in the official documentation here.
Content projection
This topic describes how to use content projection to create flexible, reusable components.
To view or download the example code used in this topic, see the live example / download example.
Content projection is a pattern in which you insert, or project, the content you want to use inside another component. For example, you could have a Card component that accepts content provided by another component.
parent.component.html
<div>
<child-component></child-component>
</div>
child.component.ts
#Component({ selector: 'child-component' })
export class ChildComponent {}
This is a very quick way to show how Angular components can be nested. In this example you have a parent component template file (parent.component.html) and inside this file you can include any other component by their selector. In the example I include 'child-component' and show child component ts file.
If you want to make child component dynamic (if I understand you correctly - to pass data from parent to child) and for example pass string value into it, you can:
parent.component.html
<div>
<child-component name="Andrei"></child-component>
</div>
child.component.ts
#Component({ selector: 'child-component' })
export class ChildComponent {
#Input() name: String;
}
More on Angular components communication: https://angular.io/guide/component-interaction
I created a sandbox environment to show an issue.
Basically the problem is when I click "Option 1" in a main menu, a new component appears in which a bottom sub-component (called BottomControls.js) is showed in the top of a page instead of a bottom of a page.
Also the CardContent is white instead of backgroundColor: 'rgb(225, 0, 80)' as defined in styles.js.
It seems like styles are applied incorrectly in BottomControls.js. I passed styles as a parameter to BottomControls.js from a parent component Main.js.
Does anybody know what am I doing wrong?
There were two main issues with how you were trying to use your styles:
You weren't exporting anything from ./layout/single/styles.js
You weren't using withStyles to convert the JS object into CSS classes that you can use
Here's a CodeSandbox that fixes those main issues:
Changes to Main.js:
// added
import { withStyles } from "#material-ui/core/styles";
Changed export default class extends Component
to class Main extends Component
// added to end of Main.js
const StyledMain = withStyles(styles)(Main);
export default StyledMain;
Changed cases of mystyles={styles} to mystyles={this.props.classes} (the classes prop is injected by withStyles).
Then in styles.js I added export default styles; to the bottom.
I have a very specific issue with Preact/React:
I have a .md file with some text, which uses react-router's <Link> tags inside for navigation. Like this:
## Heading
<Link to="/test">Let's go here</Link>
In my Component file, I render the Markdown and import the Link Component and pass the Link-components down, using the preact-markup component:
...
import {Link} from 'react-router-dom';
import text from './text.md';
import Markup from 'preact-markup';
export default class Comp extends Component {
render() {
return <Markup markup={text} components={{Link, HashLink}} />;
}
}
For importing the markdown, I use the #nuxtjs/markdown-it-loader, which works fine. It all works as expected, but doesn't feel clean.
I would like to be able to either import the Link components inside the markdown file, which would save me some boilerplate code for every view.
Or, even better, I would like to be able to write my markdown inside the Component itself, with the appropriate imports, and compile it all to HTML at build time.
I don't like runtime components since they need downloading and parse- and render time.
I'm starting with the standard TypeScript skeleton for my Aurelia development.
I wanted to add some code to the "nav-bar" component, so I decided to convert it from a simple HTML-only component to a regular component. To this end, I modified the require tag in my app.html from:
<require from="nav-bar.html"></require>
to
<require from="nav-bar"></require>
Next, I created a nav-bar.ts file, which contained the following code:
import {autoinject} from 'aurelia-framework';
import {customElement, bindable} from 'aurelia-framework';
// import {HttpClient} from 'aurelia-fetch-client';
#autoinject
#customElement('nav-bar')
export class NavBarClass {
public attached() {
console.log("TEST");
}
}
}
I left the nav-bar.html exactly as is. Now the program runs and the console contains the TEST value from the instantiated NavBarClass, BUT the menu that used to be displayed when nav-bar was HTML-only is now missing.
How do I get the menu back? What am I doing wrong? How does a regular component differ from an HTML-only component?
Thanks for your help,
-Greg
In a standard custom element the bindable properties are defined in the view-model:
nav-bar.js:
export class NavBar {
#bindable router;
...
...
}
In an html-only custom element, there is no view-model so the bindable properties are listed on the <template> element's bindable attribute instead:
nav-bar.html:
<template bindable="router">
...
...
</template>
Either way the nav-bar element usage is the same:
<nav-bar router.bind="router"></nav-bar>
I'm looking to create a dashboard using aurelia which features a plugin-type framework to add new widgets to this dashboard. I'd like to wrap these widgets so I can show title and information which is outside the widget's control, so I'd like something like:
<widget-wrapper widget-name="sales"></widget-wrapper>
and widget-wrapper would look like
<template>
<h3>${widget.title}</h3>
<compose view-model.bind="widgetName"></compose>
<span>${someOtherInfo}</span>
</template>
Then the author of the sales widget just needs a view which shows the main data and a viewmodel which defines a title property and then does whatever it needs to, to display the sales data.
I don't think the compose binding can be used here - as I won't be able to bind to the title.
I know I can specify a view for the compose binding but this doesn't help either, as I do want to use the default.
I read about template parts but it seems this is in the wrong direction for my needs - the widget-wrapper here would define what should be replaced, and the replaceable part would need to be in the sales widget. And I'd also still be stuck when trying to get the widget's title.
Is there some aurelia templating feature I've missed that would support such a scenario?
You can bind to the compose element's current view model.
Here's an example: https://gist.run?id=c28ed9f1e893cc3efd5a
app.html
<template>
<require from="./widget-wrapper"></require>
<widget-wrapper module-name="foo"></widget-wrapper>
</template>
app.js
export class App {
}
foo.html
<template>
${message}
</template>
foo.js
export class Foo {
name = 'my name is foo';
message = 'hello world!';
}
widget-wrapper.html
<template>
<h1>${composeViewModel.currentViewModel.name}</h1>
<compose view-model.ref="composeViewModel" view-model.bind="moduleName"></compose>
</template>
widget-wrapper.js
import {bindable} from 'aurelia-framework';
export class WidgetWrapper {
#bindable moduleName;
bind() {
console.log(this.composeController);
}
}