I'm a bit new to React and I'm currently developing a Proyect which has a service layer. To do so, I created a function component which would have the methods as variables:
const CustomComponent = () => {
method1 = () => {...},
method2 = () => {...},
method3 = () => {...}
}
export default CustomComponent;
This component would then be imported to the component that will use it.
To make my architecture as clean as possible, I wanted to make some of the methods private. However, as you may already know, that is not possible to do in the solution I proposed. Do hoy have an idea on how to achieve this, or maybe there is a convention to make a service layer I'm not aware of?
Thank you so much in advance!
The architecture which I find particularly clean and maintainable is one where you split off logic from presentation into two files like this:
Service layer (ts):
export class Service implements ServiceInterface {
constructor(private instanceVariable: string = "foo") { }
private methodOne(): string {
return this.instanceVariable
}
public methodTwo(argumentVariable: string): string {
const importantString = this.methodOne();
return importantString + argumentVariable;
}
}
interface ServiceInterface {
methodTwo(argumentVariable: string): string;
}
export default new Service();
Service layer (js):
export class Service {
instanceVariable;
constructor(contructorArgument) {
this.instanceVariable = contructorArgument;
}
methodOne() {
return this.instanceVariable
}
methodTwo(argumentVariable) {
const importantString = this.methodOne();
return importantString + argumentVariable;
}
}
export default new Service();
Presentation layer:
import Service from "./service.ts";
const FunctionalComponent = () => {
const [localState, setLocalState] = useState(localStateInit);
return (
<>
<div>{Service.methodTwo("bar")}</div>
</>
)
}
Few things happen here (mostly regarding ts implementation).
Keep component's service and presentation layers in separate files.
Use an interface to describe the service class and its methods. This will help to work with your service layer in your component as you'll get Typescript's IntelliSense.
For this example I'm exporting an instance of the service as default export from its file. This gives you a cleaner API in your component's file, where you can call methods without having to "pollute" component file with instance creation. This has at least the following two drawbacks:
you mostly lose ability to work nicely with static class members
preconfigured instance variable (initiated as a private member in constructor) means its value cannot be replaced in testing.
If any of above are a no go, then clean up the constructor, export just the class itself and instantiate it as required in component file.
I'm also exporting the class itself. This is for testing purposes. In testing you want to be able to swap out arguments passed into class' constructor and you need to have class definition to do that.
You'll notice the shorthand notation for declaring and instantiating a private class variable in the constructor: private instanceVariable: string = "foo". This is equivalent to something like this:
class Service {
private instanceVariable: string;
constructor(constructorArgument: string) {
this.instanceVariable = constructorArgument;
}
Such notation is particularly nice when used with dependency injection.
Overall, this setup will help you with unit testing logic in your service layer, as you can test it like any other class. This comes particularly handy when there's a lot of conditional rendering logic.
Let me know if this is what you've been looking for. Maybe we could tailor it better for your use case.
Related
Say i have a file like below on the server eg Github. I want to download the file as string with fetch and then be able to convert it to an object that i could use.
OBS: all import and the interface exist on locally so there wont be any thing missing.
import parser from '../interface/parsers';
import '../interface/extension';
export default class test implements parser {
public name: string;
constructor() {
}
}
Here's an example that at least works in a Snack (ignored the fetch part and just used a local string):
export default function App() {
const [evaledComponent, setEvaledComponent] = useState(null)
useEffect(() => {
this.React = React;
this.Text = Text;
setEvaledComponent(eval('this.React.createElement(this.Text, {}, "test component")'));
},[])
return <Text>{evaledComponent}</Text>
}
Note: I took a trick from from Why eval() is not working in React Native? assigning React to this.React in order to get it usable -- it's not a pretty hack and I'd recommend a more stable solution if you went ahead with this plan.
But:
This is an extremely uphill battle to take on and using eval is a terrible security risk since it executes arbitrary code. If there's another route you can take (like rendering things based on a JSON description), I would take that route instead.
Update:
Example of returning a custom object (non-React)
eval("var MyParser = { getString: () => {return 'hi there'} }");
return <Text>{MyParser.getString()}</Text>
I'm trying to send the result of HttpClient post requests multiple components in my Angular app. I'm using a Subject and calling its next() method whenever a new post request is successfully executed. Each component subscribes to the service's Subject.
The faulty services is defined as
#Injectable()
export class BuildingDataService {
public response: Subject<object> = new Subject<object>();
constructor (private http: HttpClient) { }
fetchBuildingData(location) {
...
this.http.post(url, location, httpOptions).subscribe(resp => {
this.response.next(resp);
});
}
The components subscribe to BuildingService.response as follows
#Component({
template: "<h1>{{buildingName}}</h1>"
...
})
export class SidepanelComponent implements OnInit {
buildingName: string;
constructor(private buildingDataService: BuildingDataService) { }
ngOnInit() {
this.buildingDataService.response.subscribe(resp => {
this.buildingName = resp['buildingName'];
});
}
updateBuildingInfo(location) {
this.buildingDataService.fetchBuildingData(location);
}
}
updateBuildingInfo is triggered by users clicking on a map.
Retrieving the data from the server and passing it to the components works: I can output the payloads to the console in each component. However, the components' templates fail to update.
After Googling and fiddling for most of today I found that this implementation does not trigger Angular's change detection. The fix is to either
wrap my call to next() in the service in NgZone.run(() => { this.response.next(resp); }
call ApplicationRef.tick() after this.title = resp['title'] in the component.
Both solutions feel like dirty hacks for such a trivial use case. There must be a better way to achieve this.
My question therefore is: what is the proper way to fetch data once and send it off to several components?
I'd furthermore like to understand why my implementation escapes Angular's change detection system.
EDIT it turns out I was initiating my call to HttpClient outside of Angular's zone hence it could not detect my changes, see my answer for more details.
One way is to get an Observable of the Subject and use it in your template using async pipe:
(building | async)?.buildingName
Also, if different components are subscribing to the service at different times, you may have to use BehaviorSubject instead of a Subject.
#Injectable()
export class BuildingDataService {
private responseSource = new Subject<object>();
public response = this.responseSource.asObservable()
constructor (private http: HttpClient) { }
fetchBuildingData(location) {
this.http.post(url, location, httpOptions).subscribe(resp => {
this.responseSource.next(resp);
});
}
}
#Component({
template: "<h1>{{(building | async)?.buildingName}}</h1>"
...
})
export class SidepanelComponent implements OnInit {
building: Observable<any>;
constructor(private buildingDataService: DataService) {
this.building = this.buildingDataService.response;
}
ngOnInit() {
}
updateBuildingInfo(location) {
this.buildingDataService.fetchBuildingData(location);
}
}
The standard way to do this in an angular app is a getter in the service class.
get data()
{
return data;
}
Why are you trying to complicate matters? What are the benefits, and if there are benefits they will overcome the drawbacks?
I think I found the issue. I briefly mention that fetchBuildingData is triggered by users clicking on a map; that map is Leaflet as provided by the ngx-leaflet module. I bind to its click event as follows
map.on('click', (event: LeafletMouseEvent) => {
this.buildingDataService.fetchBuildingData(event.latlng);
});
The callback is, I now realise, fired outside Angular's zone. Angular therefore fails to detect the change to this.building. The solution is to bring the callback in Angular's zone through NgZone as
map.on('click', (event: LeafletMouseEvent) => {
ngZone.run(() => {
this.buildingDataService.fetchBuildingData(event.latlng);
});
});
This solves the problem and every proposed solution works like a charm.
A big thank you for the quick and useful responses. They were instrumental in helping me narrow down the problem!
To be fair: this issue is mentioned in ngx-leaflet's documentation [1]. I failed to understand the implications right away as I'm still learning Angular and there is a lot to take in.
[1] https://github.com/Asymmetrik/ngx-leaflet#a-note-about-change-detection
EDIT:
As your change detection seems to struggle with whatsoever detail in your object, I have another suggestion. And as I also prefer the BehaviorSubject over a sipmple Subject I adjusted even this part of code.
1st define a wrapper object. The interesting part is, that we add a second value which definitely has to be unique compared to any other wrapper of the same kind you produce.
I usually put those classes in a models.ts which I then import wherever I use this model.
export class Wrapper {
constructor(
public object: Object,
public uniqueToken: number
){}
}
2nd in your service use this wrapper as follows.
import { Observable } from 'rxjs/Observable';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { Wrapper } from './models.ts';
#Injectable()
export class BuildingDataService {
private response: BehaviorSubject<Wrapper> = new BehaviorSubject<Wrapper>(new Wrapper(null, 0));
constructor (private http: HttpClient) { }
public getResponse(): Observable<Wrapper> {
return this.response.asObservable();
}
fetchBuildingData(location) {
...
this.http.post(url, location, httpOptions).subscribe(resp => {
// generate and fill the wrapper
token: number = (new Date()).getTime();
wrapper: Wrapper = new Wrapper(resp, token);
// provide the wrapper
this.response.next(wrapper);
});
}
Import the model.ts in all subscribing classes in order to be able to handle it properly and easily. Let your Subscribers subscribe to the getResponse()-method.
This must work. Using this wrapper + unique token technique I always solved such change detection problems eventually.
The code of sabith is wrong (but the idea is the correct)
//declare a source as Subject
private responseSource = new Subject<any>(); //<--change object by any
public response = this.responseSource.asObservable() //<--use"this"
constructor (private http: HttpClient) { }
fetchBuildingData(location) {
this.http.post(url, location, httpOptions).subscribe(resp => {
this.responseSource.next(resp);
});
}
Must be work
Another idea is simply using a getter in your component
//In your service you make a variable "data"
data:any
fetchBuildingData(location) {
this.http.post(url, location, httpOptions).subscribe(resp => {
this.data=resp
});
}
//in yours components
get data()
{
return BuildingDataService.data
}
I have a general paradigm from which I'd like to create objects. Currently, I have it written in such a way that the parent class builds the paradigm from a configuration object given to it by the subclass. The configuration object defines all of the class' characteristics, and the class is also able to overload any methods that need to be slightly different from one implementation of the paradigm to the next.
This approach is convenient as it allows me to:
avoid a long list of parameters for the constructor,
prevent the subclass from needing to define the attributes in any particular order, and
prevent the subclass from needing to explicitly create instances that the parent constructor can imply from the configuration.
As much as I liked the cleanliness and flexibility of sending this configuration object to super(), I've found a glaring limitation to this approach:
I cannot make a reference to this until super has completed.
class Paradigm {
constructor(config) {
this.name = config.name;
this.relationships = config.relationships;
}
}
class Instance extends Paradigm {
constructor(name) {
super({
name: name,
relationships: {
children: {
foo: new Foo(this) // Error: `this` referenced before `super`
}
}
});
}
}
class Foo {
constructor(parent) {
this.parent = parent;
}
}
NOTE: The configuration object is typically significantly larger than this, which is why I'm using it at all.
I can imagine there are many ways to approach this, but I'm most interested in what would be the correct way.
Should I be using functions in place of statements that require a reference to this and only execute them when they are needed?
Should I keep the same approach, but simply replace super({...}) with this.build({...})?
Should I abandon the idea of the configuration object altogether and instead take a different approach?
The fact that this is needed before super often suggests that a constructor contains logic that should be moved to separate method.
In some cases some workarounds are possible, as long as they don't cause problems. E.g., a part of configuration that requires this can be instantiated later:
constructor(name) {
const config = {
name: name,
relationships: {
children: {};
}
};
super(config);
config.relationships.children.foo = new Foo(this);
}
Of course, this presumes that relationships is just stored as a reference and isn't processed on construction.
In more severe cases class hierarchy (Instance and Paradigm) may be required to be converted to ES5 classes, since ES6 classes can extend regular functions but not vice versa.
A possible way would be to change your Paradigma to this:
class Paradigm {
constructor(config) {
Object.assign(this, config(this));
}
}
So that you can do:
class Instance extends Paradigm {
constructor(name) {
super((context) => ({
name: name,
relationships: {
children: {
foo: new Foo(context)
}
}
}));
}
}
But actually this is a bad way of solving it...
Another way could be to do it the other way round:
const Paradigma = cls => class Paradigma extends cls {
constructor(...args){
super(config => setTimeout(() => Object.assign(this, config), 1), ...args);
}
};
So you can do:
const Instance = Paradigma(class {
constructor(build, name){
build({name});
}
});
new Instance("Test");
My components often start out by having multiple #Input and #Output properties. As I add properties, it seems cleaner to switch to a single config object as input.
For example, here's a component with multiple inputs and outputs:
export class UsingEventEmitter implements OnInit {
#Input() prop1: number;
#Output() prop1Change = new EventEmitter<number>();
#Input() prop2: number;
#Output() prop2Change = new EventEmitter<number>();
ngOnInit() {
// Simulate something that changes prop1
setTimeout(() => this.prop1Change.emit(this.prop1 + 1));
}
}
And its usage:
export class AppComponent {
prop1 = 1;
onProp1Changed = () => {
// prop1 has already been reassigned by using the [(prop1)]='prop1' syntax
}
prop2 = 2;
onProp2Changed = () => {
// prop2 has already been reassigned by using the [(prop2)]='prop2' syntax
}
}
Template:
<using-event-emitter
[(prop1)]='prop1'
(prop1Change)='onProp1Changed()'
[(prop2)]='prop2'
(prop2Change)='onProp2Changed()'>
</using-event-emitter>
As the number of properties grows, it seems that switching to a single configuration object might be cleaner. For example, here's a component that takes a single config object:
export class UsingConfig implements OnInit {
#Input() config;
ngOnInit() {
// Simulate something that changes prop1
setTimeout(() => this.config.onProp1Changed(this.config.prop1 + 1));
}
}
And its usage:
export class AppComponent {
config = {
prop1: 1,
onProp1Changed(val: number) {
this.prop1 = val;
},
prop2: 2,
onProp2Changed(val: number) {
this.prop2 = val;
}
};
}
Template:
<using-config [config]='config'></using-config>
Now I can just pass the config object reference through multiple layers of nested components. The component using the config would invoke callbacks like config.onProp1Changed(...), which causes the config object to do the reassignment of the new value. So it seems we still have one-way data flow. Plus adding and removing properties doesn't require changes in intermediate layers.
Are there any downsides to having a single config object as an input to a component, instead of having multiple input and outputs? Will avoiding #Output and EventEmitter like this cause any issues that might catch up to me later?
personally if I see I need more than 4 inputs+outputs, I will check my approach to create my component again , maybe it should be more than one component and I'm doing something wrong.
Anyway even if I need that much of input&outputs I won't make it in one config, for this reasons :
1- It's harder to know what should be inside inputs and outputs,like this:
(consider a component with to html inputs element and labels)
imagine if you got only 3 of this component and you should comeback to work on this project after 1 or 2 month, or someone else gonna collaborate with you or use your code!.
it's really hard to understand your code.
2- lack of performance. it's way cheaper for angular to watch a single variable rather than watching an array or object. beside consider the example i gave you at first one, why you should force to keep track of labels in which may never change alongside with values which always are changing.
3- harder to track variables and debug. angular itself comes with confusing errors which is hard to debug, why should I make it harder. tracking and fixing any wrong input or output one by one is easier for me rather than doing it in one config variable which bunch of data.
personally I prefer to break my components to as small as possible and test each one. then make bigger components out of small ones rather than having just a big component.
Update :
I use this method for once input and no change data ( like label )
#Component({
selector: 'icon-component',
templateUrl: './icon.component.html',
styleUrls: ['./icon.component.scss'],
inputs: ['name', 'color']
});
export class IconComponent implements OnInit {
name: any;
color: any;
ngOnInit() {
}
}
Html:
<icon-component name="fa fa-trash " color="white"></icon-component>
with this method angular wont track any changes inside your component or outside.
but with #input method if your variable changes in parent component, you will get change inside component too.
I would say it could be OK to use single config objects for Inputs but you should stick to Outputs at all the time. Input defines what your component requires from outside and some of those may be optional. However, Outputs are totally component's business and should be defined within. If you rely on users to pass those functions in, you either have to check for undefined functions or you just go ahead and call the functions as if they are ALWAYS passed within config which may be cumbersome to use your component if there are too many events to define even if the user does not need them. So, always have your Outputs defined within your component and emit whatever you need to emit. If users don't bind a function those event, that's fine.
Also, I think having single config for Inputs is not the best practice. It hides the real inputs and users may have to look inside of your code or the docs to find out what they should pass in. However, if your Inputs are defined separately, users can get some intellisense with tools like Language Service
Also, I think it may break change detection strategy as well.
Let's take a look at the following example
#Component({
selector: 'my-comp',
template: `
<div *ngIf="config.a">
{{config.b + config.c}}
</div>
`
})
export class MyComponent {
#Input() config;
}
Let's use it
#Component({
selector: 'your-comp',
template: `
<my-comp [config]="config"></my-comp>
`
})
export class YourComponent {
config = {
a: 1, b: 2, c: 3
};
}
And for separate inputs
#Component({
selector: 'my-comp',
template: `
<div *ngIf="a">
{{b + c}}
</div>
`
})
export class MyComponent {
#Input() a;
#Input() b;
#Input() c;
}
And let's use this one
#Component({
selector: 'your-comp',
template: `
<my-comp
[a]="1"
[b]="2"
[c]="3">
</my-comp>
`
})
export class YourComponent {}
As I stated above, you have to look at the code of YourComponent to see what values you are being passed in. Also, you have to type config everywhere to use those Inputs. On the other hand, you can clearly see what values are being passed in on the second example better. You can even get some intellisense if you are using Language Service
Another thing is, second example would be better to scale. If you need to add more Inputs, you have to edit config all the time which may break your component. However, on the second example, it is easy to add another Input and you won't need to touch the working code.
Last but not least, you cannot really provide two-way bindings with your way. You probably know that if you have in Input called data and Output called dataChange, consumers of your component can use two-way binding sugar syntax and simple type
<your-comp [(data)]="value">
This will update value on the parent component when you emit an event using
this.dataChange.emit(someValue)
Hope this clarifies my opinions about single Input
Edit
I think there is a valid case for a single Input which also has some functions defined inside. If you are developing something like a chart component which often requires complex options/configs, it is actually better to have single Input. It is because, that input is set once and never changes and it is better to have options of your chart in a single place. Also, the user may pass some functions to help you draw legends, tooltips, x-axis labels, y-axis labels etc.
Like having an input like following would be better for this case
export interface ChartConfig {
width: number;
height: number;
legend: {
position: string,
label: (x, y) => string
};
tooltip: (x, y) => string;
}
...
#Input() config: ChartConfig;
The point of having the Input besides its obvious functionality, is to make your component declarative and easy to understand.
Putting all the configs in one massive object, which will grow definitely (trust me) is a bad idea, for all the above reasons and also for testing.
It's much easier to test a component's behaviour with a simple input property, rather than supplying a giant confusing object.
You're going backwards and thinking like the way jQuery plugins used to work, where you'd call a function called init and then you provide a whole bunch of configuration which you don't even remember if you should provide or not, and then you keep copy pasting this unknown and ever-growing object across your components where they probably don't even need them
Creating defaults is extremley easy and clear with simple Inputs whereas it becomes a little bit messy with objects to created defaults.
If you have too many similar Input, Outputs, you can consider below :
1- You can create a Base class and put all your Input/Outputs that are similar and then extend all your components from it.
export class Base{
#Input() prop1: number;
#Output() prop1Change = new EventEmitter<number>();
#Input() prop2: number;
#Output() prop2Change = new EventEmitter<number>();
}
#Component({})
export class MyComponent extends from Base{
constructor(){super()}
}
2- If you don't like this, you can use composition and create a reusable mixin and apply all your Input/Outputs like that.
Below is an example of a function that can be used to apply mixins, NOTE may not necessarily be exactly what you want, and you need to adjust it to your needs.
export function applyMixins(derivedCtor: any, baseCtors: any[]) {
baseCtors.forEach(baseCtor => {
Object.getOwnPropertyNames(baseCtor.prototype).forEach(name => {
derivedCtor.prototype[name] = baseCtor.prototype[name];
});
});
}
And then create your mixins :
export class MyMixin{
#Input() prop1: number;
#Output() prop1Change = new EventEmitter<number>();
#Input() prop2: number;
#Output() prop2Change = new EventEmitter<number>();
}
applyMixins(MyComponent, [MyMixin]);
3- You can have default properties for inputs so you only override them if you need:
export class MyComponent{
#Input() prop1: number = 10; // default
}
Are there any downsides to having a single config object as an input to a component, instead of having multiple input and outputs?
Yes, when you want to switch to the onpush change detection strategy, which is often needed in bigger projects to mitigate performance issues caused by too many render-cycles, angular will not detect changes that happened inside your config object.
Will avoiding #Output and EventEmitter like this cause any issues that might catch up to me later?
Yes, if you start to move away from #Output and in your template directly operate on the config object itself, then you are causing side-effects in your view, which will be the root of hard-to-find bugs in the future. Your view should never modify the data it get's injected. It should stay "pure" in that sense and only inform the controlling component via events (or other callbacks) that something happened.
Update: After having a look at the example in your post again, it looks like you did not mean that you want to directly operate on the input model but pass event emitters directly via the config object. Passing callbacks via #input (which is what you are implicitly doing) also has it's drawbacks, such as:
your component gets harder to understand and reason about (what are its inputs vs its outputs?)
cannot use banana box syntax anymore
If you want to bundle input parameters as an object, i'd suggest to do it like this:
export class UsingConfig implements OnInit {
#Input() config: any;
#Output() configChange = new EventEmitter<any>();
ngOnInit() {
// Simulate something that changes prop1
setTimeout(() =>
this.configChange.emit({
...this.config,
prop1: this.config.prop1 + 1
});
);
}
}
You are creating a new config object when changing a property.
You are using an Output-Event to emit the changed config object.
Both points ensure that ChangeDetection will work properly (assuming you use the more efficient OnPush strategy). Plus it's easier to follow the logic in case of debugging.
Edit:
Here's the obvious part within the parent component.
Template:
<using-config [config]="config" (configChange)="onConfigChange($event)"></using-config>
Code:
export class AppComponent {
config = {prop1: 1};
onConfigChange(newConfig: any){
// if for some reason you need to handle specific changes
// you could check for those here, e.g.:
// if (this.config.prop1 !== newConfig.prop1){...
this.config = newConfig;
}
}
I'm trying to adapt code from a SO answer, with functions and variables written as below:
const getIntervals = n=> availability=> {
}
let availability = [
]
Are those normally fine to use in a react class (see below) or do they need to be rewritten?
class Calendar extends React.Component {}
The reason for asking is that I use a React implementation for Rails and do get an error including that function and variable naming pattern.
Pure Functions, which dont modify the passed value, are always fine to use anywhere.
Its also fine to use them in a React Class directly, but common functions like string modifications, array sorting algorithms, which you are using a lot across your app and classes should go in a separate module like
// my-helpers.js
export const countKeysInObject = (data) => {
if (typeof data !== "object" || Array.isArray(data)) return 0;
Object.keys(data).length;
}
some other file..
import { countKeysInObject } form 'my-helpers'
// And you can use it everywhere..
If you are using class and extending Component, you can use simple methods for most things:
class Calendar extends React.Component {
constructor(props) {
this.date = props.date;
}
render() {
return <span>{this.date.toString()}</span>;
}
}
Calendar.propTypes = {
date: React.PropTypes.date.isRequired
};
You cannot use methods for propTypes or anything that would be an initial field if you were using an object literal. Those need to be attached after the class has been declared (propTypes) or in the constructor (initial state).