I have a typical Glimmer "base" component:
import Component from '#glimmer/component';
export default class BaseComponent extends Component { ... }
It has a template like normally, but the actual implementations of that component are child componenents, that override some of the template getters and parameters so that it works with various different data types.
export default class TypeAComponent extends BaseComponent { ... }
export default class TypeBComponent extends BaseComponent { ... }
etc.
My question is: How do I specify that all the child components should use the parent class template, so I don't have to duplicate the same fairly complex HTML for all child components? Visually the components are supposed to look identical so any changes would have to be replicated across all child component types. Therefore multiple duplicated templates isn't ideal.
In Ember Classic components there was layout and layoutName properties so I could just do:
layoutName: 'components/component-name'
in the base component and all child components did automatically use the defined template.
Now that I'm migrating to Glimmer components I can't seem to figure out how to do this. I have tried:
layout property
layoutName property
template property
Using the child components without a template in hope that they would automatically fall back to the parent class template.
Only thing that seems to work is creating Application Initializer like this:
app.register('template:components/child1-component', app.lookup('template:components/base-component'));
app.register('template:components/child2-component', app.lookup('template:components/base-component'));
But that feels so hacky that I decided to ask here first if there is a proper way to do this that I have missed?
How to specify template for Glimmer Component?
tl;dr: you should avoid this.
There are two answers to two, more specific, questions:
What is the recommended way to manage complex components with shared behaviors?
Typically, you'll want to re-work your code to use either composition or a service.
Composition
<BaseBehaviors as |myAPI|>
<TypeAComponent #foo={{myAPI.foo}} #bar={{myAPI.bar}} />
<BaseBehaviors>
Where BaseBehaviors' template is:
{{yield (hash
foo=whateverThisDoes
bar=whateverThisBarDoes
)}}
Service
export default class TypeAComponent extends Component {
#service base;
}
and the service can be created with
ember g service base
then, instead of accessing everything on this, you'd access everything on this.base
Ignoring all advice, how do I technically do the thing?
Co-located components (js + hbs as separate files), are combined into one file at build time, which works like this:
// app/components/my-component.js
import Component from '#glimmer/component';
export default class MyComponent extends Cmoponent {
// ..
}
{{! app/components/my-component.hbs }}
<div>{{yield}}</div>
The above js and hbs file becomes the following single file:
// app/components/my-component.js
import Component from '#glimmer/component';
import { hbs } from 'ember-cli-htmlbars';
import { setComponentTemplate } from '#ember/component';
export default class MyComponent extends Cmoponent {
// ..
}
setComponentTemplate(hbs`{{! app/components/my-component.hbs }}
<div>{{yield}}</div>
`, MyComponent);
So this means you can use setComponentTemplate anywhere at the module level, to assign a template to a backing class.
Why is this not recommended over the other approaches?
All of this is a main reason the layout and related properties did not make it in to Octane.
Formally supported Component inheritance results in people getting "clever"
this in of itself, isn't so much of a problem, as it is what people can do with the tool. Bad inheritance is the main reason folks don't like classes at all -- and why functional programming has been on the rise -- which is warranted! Definitely a bit of an over-correction, as the best code uses both FP and OP, when appropriate, and doesn't get dogmatic about this stuff.
Component Inheritance is harder to debug
Things that are a "Foo" but are a subclass of "Foo" may not actually work like "Foo", because in JS, there aren't strict rules around inheritance, so you can override getters, methods, etc, and have them provide entirely different behavior.
This confuses someone who is looking to debug your code.
Additionally, as someone is trying to do that debugging, they'll need to have more files open to try to under stand the bigger picture, which increases cognitive load.
Component inheritance allows folks to ignore boundaries
This makes unit testing harder -- components are only tested as "black boxes" / something you can't see in to -- you test the inputs and outputs, and nothing in between.
If you do want to test the in-between, you need to extract either regular functions or a service (or more rendering tests on the specific things).
I would say this is the classic case for a composition, where TypeAComponent and TypeBComponent use the BaseComponent.
So you have your BaseComponent with all the HTML, that basically is your template. I think its important here to think a bit more of Components also as possible Templates, not only full Components. So lets call this the TemplateComponent.
So you have your TemplateComponent which could also be a template-only component. Then you have as template for TypeAComponent and TypeBComponent:
<TemplateComponent
#type={{#title}}
#title={{#title}}
#onchange={{#onchange}}
#propertyThatIsChanged={{this.propertyThatIsChanged}}
...
/>
this allows you to have a getter propertyThatIsChanged to overwrite pieces. Common behaviour can also be placed on the TemplateComponent, or, if its common code, maybe on a BaseCodeComponent, that only contains shared code, while I would rather not do this.
For areas you want to replace this also opens the possibility to use Blocks.
The TemplateComponent, for example, could use has-block to check if a :title exists, use this block then ({{yield to="default"}}), and if not just use {{#title}}.
So, to the only obvious downside of this: you have to proxy all params. This seems ugly at first, but generally I think its better for components not to have too many arguments. At some point an options or data argument could be better, also because it can be built with js when necessary. It should also be mentioned that there is an open RFC that would address this issue. With the upcoming SFCs, I think this is the much more future-proof solution overall.
Related
I am "attempting" to use Sveltejs as a front-end web framework for a project built on Django (relevant as it defines the structure of my application). I am attempting to include Svelte components onto the various templates I have built for my Django application. Now, using the customElement API, I was able to compile and use my Svelte components as custom elements directly in my HTML templates which was great except for one major problem: my global styles weren't propagating to the components. This is, as I found out, because all custom elements are actually compiled as web components, meaning they wrap their internal HTML to prevent styles from affecting them. This is not the behavior I want. However, it seems to be the only real way to use my Svelte components in a way that works cohesively with my HTML templates. How do I solve this problem?
I am using webpack as my module bundler. I can comment a gist link to my webpack.config.js if it would be helpful, but as you can imagine, I am compiling for multiple Django "apps" and so my config is a bit messy. For reference, I have gotten everything including local styling with Sass and custom elements to work as intended. This is simply a question of "how to do something" which despite me spending literal hours Googling, I have been unable to find a clear answer to.
I know you can use the client API directly to access components, but not only is this tedious and messy (especially if I need to compose components on different HTML templates together), but I can't seem to get webpack to expose my Svelte components as Javascript classes. Here is how I am doing this approach (which is not working):
<!-- in the head -->
<script src="bundle.js"></script>
<!-- in the body -->
<script>new bundle.App{target: ...}</script>
It is telling me that App is not defined. Here is what my individual output configs in my webpack.config.js look like:
output: {
library: 'bundle',
// I do not understand at all what these two lines do -- I just found
// them somewhere on the interwebs as a suggestion to solve this problem
libraryTarget: 'umd',
umdNamedDefine: true,
// path, filename, etc.
}
To boil this down, I really have three, intertwined questions:
Can I use the customElement API (which I much prefer) and still apply global styles?
If I can't use the customElement API, is there a better approach to this problem that would allow for global styles?
If there is no other option, how do I properly use the client API with webpack?
TL;DR: There is no clean/perfect answer for this.
For once, there is no way to inject global styles into the Shadow Dom. Having said that there are few things you can try.
First, if you are not using slots, then you can write your own custom element registration function and use that to register elements. You will have to write your own adapter for web components that extends from HTMLElement class. In this approach, each Svelte component would be independent app that you are simply initializing from your web component. This is the best alternative you can explore
Additionally, you can use Constructable Stylesheets. It allows you to programmatically construct a stylesheet object and attach it to a Shadow DOM. Of course, this work only when you have flat components. When your web components are nested within one-another, each would have its Shadow DOM. You would have to create a common global style as a constructable stylesheet and attach to each component. Look here for the example:
const sheet = new CSSStyleSheet();
// Replace all styles synchronously for this style sheet
sheet.replaceSync('p { color: green; }');
class FancyComponent1 extends HTMLElement {
constructor() {
super();
const shadowRoot = this.attachShadow({ mode: 'open' });
// Attaching the style sheet to the Shadow DOM of this component
shadowRoot.adoptedStyleSheets = [sheet];
shadowRoot.innerHTML = `
<div>
<p>Hello World</p>
</div>
`;
}
}
class FancyComponent2 extends HTMLElement {
constructor() {
super();
const shadowRoot = this.attachShadow({ mode: 'open' });
// Same style sheet can also be used by another web component
shadowRoot.adoptedStyleSheets = [sheet];
// You can even manipulate the style sheet with plain JS manipulations
setTimeout(() => shadowRoot.adoptedStyleSheets = [], 2000);
shadowRoot.innerHTML = `
<div>
<p>Hello World</p>
</div>
`;
}
}
In above example, the sheet is a common stylesheet which is being used in two separate web components. But again, you will have to write you own web component wrapper to achieve this.
Is there any performance (or otherwise ) downside to using the #import directive inside custom elements?
This is how I pass global styles to svelte custom elements:
/assets/theme.css
---
:root,:host{
--some-var:1rem;
}
Then inside the component:
CustomElement.svelte
---
<svelte:options tag="custom-element"/>
...
<style>
#import "/assets/theme.css";
:host{
padding:var(--some-var)
}
</style>
Say I have a JS file contains 2 React Component class definition and a bunch of other things:
// a file called OuterComponent.react.js
import xxx from xxx;
import yyy from yyy;
// When does these run?
let a = 0;
a = 1;
export default class OuterComponent extends React.PureComponent<> {
render() {
return (
<View>
<InnerComponent />
</View>
);
}
}
class InnerComponent extends React.PureComponent<> {
//... something
}
Questions
When will the declaration and value setting code for 'a' run? Does it run when this file is imported/required by other files?
Can I have some flag in this file and changes from time to time, and then read the value of this flag from many other files? (like a singleton manager?) If I can, how do I export it and import it?
What does creating multiple files actually mean? (except that it breaks huge chunk of code into small chunks for better readability) Does it make any other difference?
Question 1: When will the declaration and value setting code for 'a' run? Does it run when this file is imported/required by other files?
Runs the first time the file is imported. It is not executed on subsequential imports.
Question 2: Can I have some flag in this file and changes from time to time, and then read the value of this flag from many other files? (like a singleton manager?) If I can, how do I export it and import it?
You can.
Exporting:
export let a = 0;
Importing:
import { a } from './filename.js';
Question 3: What does creating multiple files actually mean? (except that it breaks huge chunk of code into small chunks for better readability) Does it make any other difference?
Breaks code into small chunks;
Allows reuse; and
Enables encapsulation of non-exported variables/functions.
--
Check a demo of modules usage: http://plnkr.co/edit/0ImgQj2KzLj9O1D63Gq9?p=preview
Notice how a.js is loaded only once, even though it is imported both by b.js and c.js. Also see how it is exported and imported, and how when it changes, the change is picked up in other modules.
okay here it is
Answer 1: Yes, it runs when you import this file.
Answer 2: You can define some varible and export it to use in some other file, but that we do for constant values that does not change over time, like your action types etc, the thing you are referring to here is not what it is for, you want to use react Context or Redux store for that.
Answer 3: Creating multiple files is a modular approach to code, React emphasis on Composition that is the whole point of composing components in one another and build an App
Yes. This code will run immediately when the file is imported. It has nothing to do with react but with how js works.
You can share a variable in js using export keyword like this:
export let a = 0
Changes to this variable won't rerender your components because it's not part of any state.
Readability is huge impact by itself. It also allows reuse of variables names.
When working in collaboration it makes the flow much easier and reduces conflicts to only the places where they really are.
If I have an ES6 component like so:
component OldThing extends React.Component {
constructor(props) {
super(props);
}
render() {
return <div>{this.props.someProp}</div>;
}
}
an ES6 container like so:
const OldThingContainer = connect(mapStateToProps, mapDispatchToProps)(OldThing);
and a new typescript component like so:
component NewThing extends React.Component<Props, State> {
render() {
return <OldThingContainer someProp={someValue} />
}
}
I currently get an IntrinsicAttributes error about someProp not existing on OldThing. How do I get rid of that error without adding types/typedefs to/for OldThing and/or OldThingContainer? I tried the #augments solution on the OldThingContainer where it's defined but that doesn't seem to help. Neither did adding the comment on the component itself.
The reason I want to be able to import JS without types is that we are adding typescript to a very large, multiple years old code base and it's currently difficult to get people to write typescript components at all, let alone if they also have to type out every existing component their component wants to import.
Is there an existing elegant solution to this problem or am I going to have to go through ahead of everyone and manually type every existing JS component (hundreds) in some typedef file to get rid of/prevent these errors?
I tried your example and it seemed some issue with the type connect inferred for your component.
One quick'n'dirty fix would be to annotate your JS component with:
/**
* #type {React.ComponentType<any>}
*/
const OldThingContainer = connect(mapStateToProps, mapDispatchToProps)(
OldThing
);
One more elegant fix (but maybe not possible) would be trying to dive into the react-redux typings. The connect function requires you to supply some type arguments for it to properly work. When not supplied, those arguments are given {}, not any (because any can silently eat your types).
Comment: From the few I've worked with react+redux+typescript, lib typedefs are one of the biggest pain points. React typedefs can get really complicated. (flow just will hang up your editor with "waiting for flow server", which is even worse). As TS+Redux gets more popular and more support is added, this may get a lot better in a few months...
I created a new React Native project using react-native init and in the generated template, the main component class looks like this:
export default class App extends Component<{}> {
...
}
I don't really understand what the <{}> part means. I've never seen this before and all the examples seem to omit it. Just curious as to what its purpose is and if it's necessary.
When you are using typescript, you have to specify the type of values to be expected. This allows detecting mismatching properties during compile time and reduces the amount of errors.
So when you do Component<{}>, {} is the type for Props, your component will receive.
This is how React's Component class looks like:
If you notice, the type is <P, S>, which stands for <Props, State>.
There is another interface called ComponentClass that has a signature <P>,
which initializes a new component internally with state as any. This interface is used in ReactElement's type:
So all in all, you are defining a Component which accepts no props and but can have state of any type. This is usually done when you are not sure about you component's interactions.
Ideally a component should look like this:
interface IComponentState {
...
}
interface IComponentProps {
...
}
export class MyComponent<IComponentProps, IComponentState> extends React.Component {
...
}
This enforces consumer to pass any necessary properties and enforces you to have proper value of state.
This is either Typescript of Flow. You usually don't describe props as propTypes, but rather as interface or type. Then the type is passed to React.Component as a generic.
Type of props would be the passed type plus { children?: ReactNode }
Actually there are two generic arguments, second for State
Very useful and convenient stuff.
https://www.typescriptlang.org/docs/handbook/generics.html
https://www.typescriptlang.org/docs/handbook/react-&-webpack.html
These are flow type annotations. See https://flow.org/
You'll notice that there's a #flow comment at the top of the file, and a .flowconfig file in the root of the project.
Using flow here is optional, but it can help you catch bugs.
I am using Angular 1.6 with ES6. Until now I implemented inheritence with the extend keyword on ES6 classes
export default class myChildService extends myBaseService {
constructor(someDependency){
'ngInject'
super();
someDependency.doStuff();
}
}
and registered the child-class as angular-service
import myChildService from ./myChildService
angular.module('myApp', [])
.service('myChildService', myChildService)
This works well so far!
However I would like to split my application into smaller modules. This means I will have several different angular-modules which will need access to myBaseService in order to inherit the functionality.
I could just import the file containing myBaseService, but this seems not very angulary. It basically means I disregard angular-modules and DI everytime I use inheritance.
Question:
Is there any way to export an ES6-class from an angular-module base so that I can reuse it on an angular-module child which depends on base?
Maybe I am just looking at this the wrong way - if you have other suggestions to implement inheritance of angular-services/ factories using ES6-classes, please go ahead.
Why importing is not angulary? If you would like to use base service functionality, the module should know it's definition.