I have an Angular website that has several components. Many of these components include displaying text that links to other components. I route to these other components with an html element like this:
<a (click)='routeToTeamSeason(team_season_id)' [routerLink]>{{team}}</a>
and then the function looks like:
routeToTeamSeason(team_season_id: string): void{
console.log("Routing to: " + team_season_id)
this.router.navigate(['/teams/season', team_season_id])
}
Where router is an instance of the #angular/router class.
Several of my components all can use this same routing logic, and right now I have the same exact function copy and pasted into each component's .ts file. How can I create a shared function that will be recognized by each component's html template?
Not sure this is the best method, but here is the solution I found:
create a separate file route-to-team-season.ts:
export function routeToTeamSeason(team_season_id: string): void{
console.log("Routing to: " + team_season_id)
this.router.navigate(['/teams/season', team_season_id])
}
Then import that function into any component and declare it locally:
myRouteToTeamSeason = routeToTeamSeason
Finally, update the template to call myRouteToTeamSeason(...)
This seems a little ugly to me, so if someone has a nicer solution, let me know!
Related
What I'm trying to achieve is:
Building simple react app - the template is create react app
Copying output file (main.*.js)
Pasting it in another react app
Importing render function to render the first app into the second one
Simple react app code:
interface Props {
greeting: string;
}
export module AppModule {
export const sendGreetings = ({ greeting }: Props) => {
return `Hello ${greeting}`;
};
}
Builder file code:
!function(){"use strict";var n;(n||(n={})).sendGreetings=function(n){var e=n.greeting;return"Hello ".concat(e)}}();
Trying to import this file into another app I get this error:
File 'c:/vscode/test-react-app/test-sc-react/src/main.783e0281.js' is not a module.ts(2306)
Which is obvious. I changed the output file manually to:
export function initApp(){"use strict";var n;(n||(n={})).sendGreetings=function(n){var e=n.greeting;return"Hello ".concat(e)}};
It works but the only function that I'm able to access is initApp but not sendGreetings
I've been struggling with this for a while now and I would really appreciate any helpful suggestions
I used Bit.dev for my components that are used across multiple applications & there is an article regarding your issue
https://blog.bitsrc.io/sharing-react-components-across-multiple-applications-a407b5a15186
I think it would help.
🎯 Solution #1
You can use an iframe to inject your react app:
<iframe src='path-to-your-app.html'/>
🎯 Solution #2
Go with micro-frontend architecture approach. Where a front-end app is decomposed into individual, semi-independent "microapps" working loosely together.
As a starting point, you can try npx create-mf-app instead of the CRA.
You can include your js code directly on run time. You can use window.addEventListener to load js/css incoming from an outside source. You just have to append that js to your document on the load event.
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.
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.
Hello i m using storybook/react library to create stories of my components.
So far so good, I followed the tutorial on https://www.learnstorybook.com/react/en/get-started and i have add stories on the left side of the screen with the add command, like so:
add('archived', () => <Task task={{ ...task, state: 'TASK_ARCHIVED' }} {...actions} /> );
The Task component is a functional component.
I 'm also using the storybook-addon-react-live-edit storybook addon, from https://github.com/vertexbz/storybook-addon-react-live-edit for having live edit on the stories, like so:
The code for the above example is this:
``
stories.addDecorator(withLiveEditScope({ React, Test }));
stories.add('simple JSX', withLiveEdit(return <div>hello there!!</div>, {color: 'red'}))`
This code is simple because i just show jsx code.
Problem
I want to live edit a functional or class component, from another file but the functions withLiveEdit(source[, scope]) and addLiveSource(name, source[, scope]), accept only string as the source.
So if i add the story like so: stories.addLiveSource('demo', return ${Test});
Test is a separate Test.js file:
const Test = class Welcome extends React.Component {
render() {
return <h1>Hello, world!!</h1>;
}
}
export default Test;
Results is, it shows the code on 'live tab', but its not actually rendered on the top window.
So my question is, how can i import class or functional components on addLiveSource()/withLiveEdit()
Thanks.
I hit the same issue, you can do this:
storiesOf(group, module)
.addDecorator(withLiveEditScope({ React, SomeFunctionComponent, StyledHeader }))
.addLiveSource("title", `<SomeFunctionComponent><StyledHeader>hello</StyledHeader></SomeFunctionComponent>`);
Basically anything you use inside the template literal in addLiveSource which would need an import you need to add to withLiveEditScope.
The only drawback is you have to write it all inside the template literal (P.S. if you solve that limitation please let me know!)
I'm currently working on a framework for a web app that a number of developers will be working on. The web app will be in a dashboard style, with individual modules or "views" written in React.
One of the requirements of the framework is that it must ship with a tool that allows developers to automatically generate a new module or "view", so that it creates the files and folders needed and they can get straight to work on the code logic.
An extremely simple flow would be as follows:
Developer enters the name of their new module as an argument to a npm script
A script runs which creates [moduleName.js] and [moduleName.less], links them together, places them in a directory, and writes the generic react code.
I'm now up to the point where I am generating the common react code. Here is what I wrote:
function writeBoilerplate(moduleName) {
var jsFileStream = fs.createWriteStream("./src/m-" + moduleName + "/" + moduleName + ".js");
jsFileStream.once('open', (fd) => {
jsFileStream.write("import React from \"react\"\;\n");
jsFileStream.write("import Style from \"\.\/" + moduleName + "\.less\"\;");
jsFileStream.write("\n");
jsFileStream.write("\n");
jsFileStream.write("export default class " + moduleName + " extends React\.Component \{\n");
jsFileStream.write("\n");
jsFileStream.write(" constructor\(\) \{\n");
jsFileStream.write(" super\(\)\;\n");
jsFileStream.write(" \}\n");
jsFileStream.write("\n");
jsFileStream.write(" componentDidMount\(\) \{");
jsFileStream.write(" \}");
jsFileStream.write("\}");
jsFileStream.end();
});
You can immediately see the problem here, and I stopped before going too far. If I continue on this path, the code will become unreadable and unmanageable.
I want to refactor this to use javascript templates. However, I have never used templating before and I am unsure of how to create a template and use it, or if there are any tools to help.
How can I refactor this code to use a template?
You need to use a template library for that. You can try lodash template one, for example: https://lodash.com/docs#template.
Put your boilerplate in a template file, read it and use something like:
var compiled = _.template(templateFileContent);
compiled({ 'moduleName': 'mymodule' });