Difference between HTML component and a normal component in Aurelia? - javascript

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>

Related

How to input a component into another component in Angular?

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

Import a local module as component with sub components

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.

Why my bottom sub-component is shown on top of a page?

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.

Creating a wrapper around a compose binding and bind to the composed view model

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);
}
}

How to use JQuery UI components in Aurelia getting started app (navigation app)

I am able to run the Aurelia app by following the steps provided in getting started tutorial. They have used bootstrap nav-bar in the skeleton application. Is it possible to use JQuery UI components in the Aurelia app. If yes, please explain me how to achieve this.
Thanks in advance.
Yes, it's possible!
I've made a jQueryUI Tabs example for you:
tabs.html
<template>
<ul>
<li repeat.for="tab of tabs">
${tab.title}
</li>
</ul>
<div repeat.for="tab of tabs" id="${$parent.id + '-' + $index}">
<p>${tab.text}</p>
</div>
</template>
As you can see, I've only copied the boilerplate HTML of the jQueryUI Tabs component, and created the bindable property tabswhich is an Array of Objects like that: [{title: "", text: ""}].
tabs.js
import {bindable, inject} from 'aurelia-framework';
import $ from 'jquery';
import {tabs} from 'jquery-ui';
#inject(Element)
export class Tab {
#bindable tabs = null;
constructor(el) {
this.id = el.id;
}
attached() {
$(`#${this.id}`).tabs();
}
}
The code is pretty readable: I've imported jquery from my config.js file, and my jquery-ui from there too (only the component tabs, so it gets lighter). Then, I've injected the DOMElement to my class, so I could get it's id. I've created my bindable property tabs. In my constructor, I get the DOMElement id and populates my id property. And, finally, on the attached event (when all the binds are finished), I've got the jQuery object from my id, and called the method tabs() to turn the template into a Tabs component. Pretty simple, uh?
In my config.js file, I've added those two lines:
"jquery": "github:components/jquery#2.1.4",
"jquery-ui": "github:components/jqueryui#1.11.4",
And then you can use the Tabs component wherever you want, by calling it in any other HTML template of your project:
That's it!
You can see the working example here: http://plnkr.co/edit/ESxZA2jTlN7f6aiq1ixG?p=preview
PS: Thanks for that plnkr, Sylvian, I've used yours to fork mine.
You can import and then use jquery on your DOM elements.
Given this templatenamed test.html:
<template>
<div ref="content">test</div>
</template>
A Test custom element can manipulate the div referenced as content like this:
import {customElement, inject} from 'aurelia-framework';
import $ from 'jquery';
#customElement('test')
export class Test{
attached(){
$(this.content).css('color', 'red');
}
}
Then you can use that custom element in any view using the <test></test> tag.
This exemple uses the css() function but you can import any plug-in and apply them to your elements.
See a working example here: http://plnkr.co/edit/SEB4NK?p=preview (be patient it takes a little while to load).

Categories