How to make reusable function which affect different data in angular? - javascript

First of all, I am so sorry if the title is not represented the problem I am about tell. Here, I have a lot of component which has an object that quite do the same thing. I'll give two example:
First component, PlanningComponent:
export class PlanningComponent implements OnInit {
constructor() {}
data = {
items: null,
isEmpty: null as boolean,
getData: () => {
/* fetching data from database and store it in items properties */
},
/* another method related to data goes here */
};
pagination = {
totalData: null as number, /* this prop gets its value within getData process */
currentPage: null as number, /* this prop already has a value within OnInit process */
goNext: () => {
this.pagination.currentPage += 1;
this.data.getData();
},
goLast: () => {
this.pagination.currentPage = this.totalData;
this.data.getData();
},
/* another method related to pagination goes here */
};
filter = {
filterA: null as string,
filterB: null as number,
filterC: null as string,
isEnabled: null as boolean,
isToggled: null as boolean,
onReset: () => {
this.filter.filterA = null;
this.filter.filterB = null;
this.filter.filterC = null;
this.data.getData();
},
/* another function related to filter goes here */
};
}
Second component, HarvestComponent:
export class HarvestComponent implements OnInit {
constructor() {}
data = {
items: null,
isEmpty: null as boolean,
getData: () => {
/* fetching data from database and store it in items properties */
},
/* another method related to data goes here */
};
pagination = {
totalData: null as number, /* this prop gets its value within getData process */
currentPage: null as number, /* this prop already has a value within OnInit process */
goNext: () => {
this.pagination.currentPage += 1;
this.data.getData();
},
goLast: () => {
this.pagination.currentPage = this.totalData;
this.data.getData();
},
/* another method related to pagination goes here */
};
filter = {
filterX: null as string,
filterY: null as number,
filterZ: null as string,
isEnabled: null as boolean,
isToggled: null as boolean,
onReset: () => {
this.filter.filterX = null;
this.filter.filterY = null;
this.filter.filterZ = null;
this.data.getData();
},
/* another function related to filter goes here */
};
}
Here as you can see, the two component is looks quite the same. The difference lies on the value of data.items, the filter properties, and the affected data when calling the function (for example pagination.goNext()). In the first component is calling the getData() of planning, and the second one is calling the getData() of harvest. Yeah you got the point.
I don't want to write the same code again and again, the apps I am about to develope has a lot of pages but has a similar behaviour. Is there any good approach to make the code reusable in my case?
I have tried to think about create different component for pagination and filter, but I still don't get a clear picture how to make the component affecting the different data (data.items() in planning, data.items() in harvest)
So far, I just create a helper function. For example for filter component I created a function in helper to make props within filter become null. But still, i write the same thing in every component, it just cut a line or two. Is there any hint for me?

As people suggested, you may use pipes.
But I think you have forgotten the main benefit of OOP (which can't be used in Javascript, but is implemented in Typescript). And it's class inheritance! :)
Remember the basics:
class base {
variable1;
constructor() {
this.variable1 = 1;
}
}
class child extends base {
variable2;
constructor() {
super();
console.log(this.variable1);
}
}
And the same is for class members as methods and properties.
Thanks to Typescript, it is possible now.

Related

How to do I set the default value for a member of the type BOOLEAN_FIELD in a derived class from foundry.abstract.DocumentData in foundry vvt?

Is there a hook / overwriteable function, when a placeable foundry vvt object was succesfully added to the scene and is ready to be used?
calling
const myown_data = { give_me_icecream: true }
canvas.scene.updateEmbeddedDocuments('ContainerName',[{ _id: this.data._id, ...myown_data }])
in
_onCreate
complains that the data could not be written / saved when I create the object. But when I reload the scene the data is written to all objects of the type without a problem. I would like to just to set some default values for a BOOLEAN_FIELD or any other DocumentField in a foundry.abstract.DocumentData Class, which should be used only when the object is created for the first time.
The solution is to overwrite the _initialize () function of DocumentData (in the module folder it is name something like ModuleNameData.js).
export class MyOwnClassData extends foundry.abstract.DocumentData {
static defineSchema ()
{
_id: fields.DOCUMENT_ID,
scene: {
...fields.DOCUMENT_ID,
required: false,
nullable: true
},
my_own_member: fields.BOOLEAN_FIELD
}
_initialize () {
super._initialize()
this.my_own_member = true
}
}

React setState for nested object while preserving class type

Extending from this question React Set State For Nested Object
The way to update nested state is to decompose the object and re-construct it like the following:
this.setState({ someProperty: { ...this.state.someProperty, flag: false} });
However, this is a problem for me, as it will not preserve the component class (More detail below). I am aiming for my state to be structured like the following:
this.state = {
addressComponent: {
street: '',
number: '',
country: ''
},
weather: {
/* some other state*/
}
}
To make my life easier, I created a simple address class that builds the object for me and have some other utility function such as validation.
class AddressComponent {
constructor(street, number, country) {
this.street = street;
this.number = number;
this.country = country;
}
validate() {
if (this.street && this.number, this.country) {
return true;
}
return false;
}
}
This allows me to do is change the initialization of state to be:
this.state = {
addressComponent : new AddressComponent(),
weather: new WeatherComponent();
}
this will allow my view component to perform nice things like
if (this.state.addressComponent.validate()) {
// send this state to parent container
}
The problem with this approach is that if I want to mutate one single piece of information like country and use the above Stack Overflow approach such as:
this.setState({addressComponent: {...addressComponent, country: 'bluh'}})
Doing this will mean that the resolting addressComponent is no longer part of AddressComponent class hence no validate() function
To get around it I cound recreate a new AddressComponent class every time like:
this.setState({addressComponent: new AddressComponent(this.state.street, this.state.number, 'bluh');
But this seems weird.
Am I doing this wrong? Is there a better approach? Is it acceptable to use classes like this with react?
It's undesirable to use anything but plain objects for React state to avoid situations like this one. Using class instances will also make serialization and deserialization of the state much more complicated.
The existence of AddressComponent isn't justified, it doesn't benefit from being a class.
The same code could be rewritten as functional with plain objects:
const validateAddress = address => !!(street && address.number && address.country);
...
if (validateAddress(this.state.address)) {
// send this state to parent container
}
I think what you said about reinstantiating your class each time you update it in your state is the cleanest way to ensure your validate() method is called at that time. If it were me, I would probably write it as:
const { addressComponent } = this.state;
this.setState({ addressComponent: new AddressComponent(addressComponent.street, addressComponent.number, 'bluh') });
You can create a reusable function to update a complex state like this.
updateState = (option, value) => {
this.setState(prevState => ({
addressComponent: {
...prevState.addressComponent,
[option]: value
}
})
}

ngrx/store throws run time error when using createFeatureSelector with a class

My application is built on latest versions of Angular, Immutable.js, NgRx;
I want to take advantage of NGRX's createFeatureSelector and createSelector. However, I can only seem to select pieces of the store by string. I've pulled the pieces of code out that are relevant (I think). I also added a plunker link below illustrating the issue.
When the application bootstraps, it throws an error that Uncaught TypeError: <CLASS> is not a constructor.
Things I tried and the resulting symptoms:
If I move the offending into the initialState file, where the reducer is getting it's default state, the error moves to the next class down the construction chain. In the example below, CopyRollbackState(code below) is a class that contains two sub classes CopyRollbackRemovables$ and CopyRollbackConfirm$; If I copy the first into the initialState file, the error moves to the second.
If I replace the top level class, CopyRollbackState, with a Immutable Map, the application fails to run with the same error.
If I remove all hard classes from my state init, e.g. use immutable.fromJS() method, the error goes away. However, I would like to use actual classes in the Store.
Thanks for any help.
e.g.
/** provider.ts */
// this works
this.copyRollbackRemovables$ = this._store.select('copyRollbackRemovables');
// this fails with the below error
// this is one of the methods of ngrx i want to use !!
this.copyRollbackRemovables$ = this._store.pipe(select(_RootReducer.getRollbackRemovables));
_
/** _RootReducer.ts */
export const appState: ActionReducerMap<AppState> = {
...
copyRollbackRemovables: fromCopyRollback.copyRollbackEntitiesReducer,
...
}
export const getRollbackRemovables =
createFeatureSelector<CopyRollbackRemovables$>('copyRollbackRemovables');
_
/** fromCopyRollback.ts reducer */
export function copyRollbackEntitiesReducer(
state: any = initialState.get('copyRollbackEntities'),
action: Action
): CopyRollbackRemovables$ {
switch (action.type) {
default:
break;
}
return state;
}
_
/** model.ts */
const copyRollbackStateRecord = Record({
copyRollbackEntities: null,
copyRollBackConfirm: null,
});
interface ICopyRollbackState {
copyRollbackEntities: CRM.ICopyRollbackRemovables$,
copyRollBackConfirm: CRM.ICopyRollbackConfirm$
}
export class CopyRollbackState extends copyRollbackStateRecord implements ICopyRollbackState {
copyRollbackEntities: CRM.CopyRollbackRemovables$;
copyRollBackConfirm: CRM.CopyRollbackConfirm$;
constructor(config: ICopyRollbackState) {
super(Object.assign({}, config, {
copyRollbackEntities: config.copyRollbackEntities && new CRM.CopyRollbackRemovables$(config.copyRollbackEntities),
copyRollBackConfirm: config.copyRollBackConfirm && new CRM.CopyRollbackConfirm$(config.copyRollBackConfirm)
}));
};
}
export const initialState: CopyRollbackState = new CopyRollbackState({
copyRollbackEntities: {
meta: {
loading: false
},
byBatchId: Map({})
},
copyRollBackConfirm: {
meta: {
loading: false
},
data: {
templateItemIds: [],
rollBackConfirmer: false,
rollBackReason: ''
}
}
});
// CRM.ts, similar extend and interface as above class
export class CopyRollbackRemovables$ extends copyRollbackRemovablesRecord$ implements ICopyRollbackRemovables$ {
meta: IPagedMeta;
byBatchId: Map<string, List<CopyRollback>>;
constructor(config: ICopyRollbackRemovables$) {
super(Object.assign({}, config, {
meta: config.meta && new PagedMeta(config.meta),
byBatchId: config.byBatchId && Map(config.byBatchId)
}));
}
}
Here is the transpiled code that is referenced in the error.
var CopyRollbackState = (function (_super) {
__extends(CopyRollbackState, _super);
function CopyRollbackState(config) {
return _super.call(this, Object.assign({}, config, {
copyRollbackEntities: config.copyRollbackEntities && new CRM.CopyRollbackRemovables$(config.copyRollbackEntities),
copyRollBackConfirm: config.copyRollBackConfirm && new CRM.CopyRollbackConfirm$(config.copyRollBackConfirm)
})) || this;
}
;
return CopyRollbackState;
}(copyRollbackStateRecord));
I have built a bare minimum Plunker to show the error. You can see the error in the console when the plunk loads. In the case of my application, the error prevents the app from loading.
The way I fixed this problem, was to move all of the Class definitions to a single file. In my case, I had class definitions in a *.store.ts file that relied on class definitions in a *.model.ts file. Moving all definition to one of the two solved the problem.
Still not sure where the actual problem lies. Maybe it was hard for DI to locate the correct class.

React to nested state change in Angular and NgRx

Please consider the example below
// Example state
let exampleState = {
counter: 0;
modules: {
authentication: Object,
geotools: Object
};
};
class MyAppComponent {
counter: Observable<number>;
constructor(private store: Store<AppState>){
this.counter = store.select('counter');
}
}
Here in the MyAppComponent we react on changes that occur to the counter property of the state. But what if we want to react on nested properties of the state, for example modules.geotools? Seems like there should be a possibility to call a store.select('modules.geotools'), as putting everything on the first level of the global state seems not to be good for overall state structure.
Update
The answer by #cartant is surely correct, but the NgRx version that is used in the Angular 5 requires a little bit different way of state querying. The idea is that we can not just provide the key to the store.select() call, we need to provide a function that returns the specific state branch. Let us call it the stateGetter and write it to accept any number of arguments (i.e. depth of querying).
// The stateGetter implementation
const getUnderlyingProperty = (currentStateLevel, properties: Array<any>) => {
if (properties.length === 0) {
throw 'Unable to get the underlying property';
} else if (properties.length === 1) {
const key = properties.shift();
return currentStateLevel[key];
} else {
const key = properties.shift();
return getUnderlyingProperty(currentStateLevel[key], properties);
}
}
export const stateGetter = (...args) => {
return (state: AppState) => {
let argsCopy = args.slice();
return getUnderlyingProperty(state['state'], argsCopy);
};
};
// Using the stateGetter
...
store.select(storeGetter('root', 'bigbranch', 'mediumbranch', 'smallbranch', 'leaf')).subscribe(data => {});
...
select takes nested keys as separate strings, so your select call should be:
store.select('modules', 'geotools')

Is there a proper way of resetting a component's initial data in vuejs?

I have a component with a specific set of starting data:
data: function (){
return {
modalBodyDisplay: 'getUserInput', // possible values: 'getUserInput', 'confirmGeocodedValue'
submitButtonText: 'Lookup', // possible values 'Lookup', 'Yes'
addressToConfirm: null,
bestViewedByTheseBounds: null,
location:{
name: null,
address: null,
position: null
}
}
This is data for a modal window, so when it shows I want it to start with this data. If the user cancels from the window I want to reset all of the data to this.
I know I can create a method to reset the data and just manually set all of the data properties back to their original:
reset: function (){
this.modalBodyDisplay = 'getUserInput';
this.submitButtonText = 'Lookup';
this.addressToConfirm = null;
this.bestViewedByTheseBounds = null;
this.location = {
name: null,
address: null,
position: null
};
}
But this seems really sloppy. It means that if I ever make a change to the component's data properties I'll need to make sure I remember to update the reset method's structure. That's not absolutely horrible since it's a small modular component, but it makes the optimization portion of my brain scream.
The solution that I thought would work would be to grab the initial data properties in a ready method and then use that saved data to reset the components:
data: function (){
return {
modalBodyDisplay: 'getUserInput',
submitButtonText: 'Lookup',
addressToConfirm: null,
bestViewedByTheseBounds: null,
location:{
name: null,
address: null,
position: null
},
// new property for holding the initial component configuration
initialDataConfiguration: null
}
},
ready: function (){
// grabbing this here so that we can reset the data when we close the window.
this.initialDataConfiguration = this.$data;
},
methods:{
resetWindow: function (){
// set the data for the component back to the original configuration
this.$data = this.initialDataConfiguration;
}
}
But the initialDataConfiguration object is changing along with the data (which makes sense because in the read method our initialDataConfiguration is getting the scope of the data function.
Is there a way of grabbing the initial configuration data without inheriting the scope?
Am I overthinking this and there's a better/easier way of doing this?
Is hardcoding the initial data the only option?
extract the initial data into a function outside of the component
use that function to set the initial data in the component
re-use that function to reset the state when needed.
// outside of the component:
function initialState (){
return {
modalBodyDisplay: 'getUserInput',
submitButtonText: 'Lookup',
addressToConfirm: null,
bestViewedByTheseBounds: null,
location:{
name: null,
address: null,
position: null
}
}
}
//inside of the component:
data: function (){
return initialState();
}
methods:{
resetWindow: function (){
Object.assign(this.$data, initialState());
}
}
Caution, Object.assign(this.$data, this.$options.data()) does not
bind the context into data().
So use this:
Object.assign(this.$data, this.$options.data.apply(this))
cc this answer was originally here
To reset component data in a current component instance you can try this:
Object.assign(this.$data, this.$options.data())
Privately I have abstract modal component which utilizes slots to fill various parts of the dialog. When customized modal wraps that abstract modal the data referred in slots belongs to parent
component scope. Here is option of the abstract modal which resets data every time the customized modal is shown (ES2015 code):
watch: {
show (value) { // this is prop's watch
if(value) {
Object.assign(this.$parent.$data, this.$parent.$options.data())
}
}
}
You can fine tune your modal implementation of course - above may be also executed in some cancel hook.
Bear in mind that mutation of $parent options from child is not recommended, however I think it may be justified if parent component is just customizing the abstract modal and nothing more.
If you are annoyed by the warnings, this is a different method:
const initialData = () => ({})
export default {
data() {
return initialData();
},
methods: {
resetData(){
const data = initialData()
Object.keys(data).forEach(k => this[k] = data[k])
}
}
}
No need to mess with $data.
I had to reset the data to original state inside of a child component, this is what worked for me:
Parent component, calling child component's method:
<button #click="$refs.childComponent.clearAllData()">Clear All</button >
<child-component ref='childComponent></child-component>
Child component:
defining data in an outside function,
referencing data object by the defined function
defining the clearallData() method that is to be called upon by the
parent component
function initialState() {
return {
someDataParameters : '',
someMoreDataParameters: ''
}
}
export default {
data() {
return initialState();
},
methods: {
clearAllData() {
Object.assign(this.$data, initialState());
},
There are three ways to reset component state:
Define key attribute and change that
Define v-if attribute and switch it to false to unmount the component from DOM and then after nextTick switch it back to true
Reference internal method of component that will do the reset
Personally, I think the first option is the clearest one because you control the component only via Props in a declarative way. You can use destroyed hook to detect when the component got unmount and clear anything you need to.
The only advance of third approach is, you can do a partial state reset, where your method only resets some parts of the state but preserves others.
Here is an example with all the options and how to use them:
https://jsbin.com/yutejoreki/edit?html,js,output

Categories

Resources