Scope of an imported variable in ionic v3 [duplicate] - javascript

I have a constants file constants.ts:
export const C0NST = "constant";
I access it in a service some.service.ts like so:
import { C0NST } from './constants';
console.log(C0NST); // "constant"
However, when I access it in a component template:
some.component.ts:
import { C0NST } from './constants';
some.component.html:
{{ C0NST }} <!-- Outputs nothing -->
However defining a member in the component class works:
some.component.ts
public const constant = C0NST;
some.component.html
{{ constant }} <!-- constant -->
I don't understand why I was able to access the imported constant directly in the service class but not in the component template even though I imported it in the component class.

In Angular2, the template can only access fields and methods of the component class. Everything else is off-limits. This includes things which are visible to the component class.
The way to go around this is to have a field inside the component, which just references the constant, and use that instead.
It's one limitation of the design, but perhaps you should think a bit more about why you need a constant in the template in the first place. Usually these things are used by components themselves, or services, but not the template.

Since in the Component's template you can only use attributes of the Component's class, you can't directly use any external constants (or external variables).
The most elegant way that I've found so far is the following:
import { MY_CONSTANT } from '../constants';
#Component({
// ...
})
export class MyTestComponent implements OnInit {
readonly MY_CONSTANT = MY_CONSTANT;
// ...
}
which basically just creates a new attribute MY_CONSTANT inside the component class. Using readonly we make sure that the new attribute cannot be modified.
Doing so, in your template you can now use:
{{ MY_CONSTANT }}

The scope of Angular2 template bindings is the component instance. Only what's accessible there can be used in bindings.
You can make it available like
class MyComponent {
myConst = CONST;
}
{{myConst}}

There are two best directions in my opinion:
Wrapping constants as internal component property
enum.ts
export enum stateEnum {
'DOING' = 0,
'DONE',
'FAILED'
}
component.ts
import { stateEnum } from './enum'
export class EnumUserClass {
readonly stateEnum : typeof stateEnum = stateEnum ;
}
Example uses enum, but this can be any type of defined constant. typeof operator gives you all of benefits of TypeScript typing features. You can use then this variable directly in templates:
component.html
<p>{{stateEnum.DOING}}<p>
This solution is less efficient in memory usage context, because you are basically duplicating data (or references to constants) in each component you wish to use it. Beside that, syntax
readonly constData: typeof constData = constData
in my opinion introduce a lot of syntax noise and may be confusing to newcommers
Wrapping external constant in component function
Second option is to wrap your external variable/constant with component function and use that function on template:
enum.ts
export enum stateEnum {
'DOING' = 0,
'DONE',
'FAILED'
}
component.ts
import { stateEnum } from './enum'
export class EnumUserClass {
getEnumString(idx) {
return stateEnum[stateEnum[idx]];
}
}
component.html
<p>{{getEnumString(1)}}</p>
Good thing is that data is not duplicated in controller but other major downside occur. According to Angular team, usage of functions in templates is not recommended due to change detection mechanism, which works way less efficient in case of functions returning values to templates: change detection have no idea does value return by a function has changed, so it will be called way often than needed (and assuming you returning const from it, it's actually needed only once, when populating template view. It may be just a bit efficiency killing to your application (if you are lucky) or it may totally break it down if function resolves with Observable for instance, and you use async pipe to subscribe to results. You can refer to my short article on that HERE

You can create a BaseComponent , it is a place where you should create your constant instances and then you can create your FooComponent extends BaseComponent and you can use your constants.

Related

Custom hook that only returns helper functions

I made a custom hook that only returns helper functions. Nowhere in the custom hook do I use another hook (useState, useEffect...)
Example:
import backend from '../lib/backend';
import axios, { AxiosError } from 'axios';
interface PresignedData {
fields: any;
url: string;
key: string;
}
type Resource = 'users' | 'events';
function useBucket() {
const uploadViaPresignedPost = async function (
resource: Resource,
file: File
) {
...
};
const buildImageUrl = function (key: string) {
return `${process.env.NEXT_PUBLIC_S3_BUCKET_DOMAIN}/${key}`;
};
return { uploadViaPresignedPost, buildImageUrl };
}
export default useBucket;
Is this common practice? Or would it be better to create a class with static methods? export helper functions from separate file? What is best practice?
Using a class for this wouldn't make all that much sense because a class is meant for when you need to tie together data associated with an instance with methods that can operate on that data. If there's no data associated with an instance - if the class never has new called on it - then there's not much point to a class in the first place. A few plain functions or a plain object with functions in it would make more sense than a class.
Your current approach of a custom hook that doesn't use any other hooks inside it seems a bit weird, but it's not forbidden. Feel free to use that approach if you want. Using a custom hook has an added benefit that if you later decide to change the logic and, for example, feel the desire to add a useState or useEffect or something to the custom hook, it's trivial to add them into the custom hook. In contrast, if you used anything other than a custom hook and later found that you needed to add something that required hook logic, you would not be able to without first refactoring everything back into a custom hook again.

sveltejs - static properties on components

I have a scenario where I need to provide information to a component class such that downstream instantiations can make use of that info.
For instance:
import { AComponent } from 'AComponent.svelte'
...
AComponent.classInfo = {something: somedata}
And then, the component could access that information as in:
<script>
let something = AComponent.classInfo.something
</script>
There seemed to be some effort in providing this kind of functionality in V2 (I'm using 3) that was discussed in these issues: Support Component Static Method #480, which resulted in Added setup function which can supply static methods/properties. #572.
However, scanning the current docs reveals no such setup method. So, did this survive from V2 to 3 & if not, is there some way to do this?
You can define static properties that are not instance specific in the module script block
<script context="module">
export const someValue = 123
</script>
<script>
// Normal component stuff
</script>
and then import it directly from the component file:
import { someValue } from './MyComponent.svelte'
Note that this is a value shared among all instances of this component.
At least in version v3.32, it's not possible to define static properties in a Svelte Component. Only named exports are possible.
The only workaround I known is using a custom webpack loader/rollup plugin, and the implementation is never pretty.

Static functions in Angular-template

In our project we use the linting-config from AirBnB. The is a rule, that says class methods must use this or be declared as static. In theory this rule makes a lot of sense to me, but in the angular context seems to cause some problems. Imagine a component like this (Stackblitz):
import { Component, VERSION } from '#angular/core';
#Component({
selector: 'my-app',
template: '<p>{{doSomething("hello stack overflow")}}'
})
export class AppComponent {
doSomething(string: string): string {
return string.toLocaleUpperCase();
}
}
Now, the linter would complain about doSomething not using this. Wen can now make the function static to satisfy it - but than we would not be able to use the function in the template.
One conclusion would be, that doSomething should not be part of AppComponent but another service for example. But than we would have to wrap the static function in non-static one again. In the end the wrapping function is not much smaller than the original one, so the whole outsourcing to service thing seems kind of pointless. Especially since we speak of functions which are explicitly only useful for the template of the component. It seems to be problematic especially with tracking function for trackBy of ngForOf - they tend to not use a this keyword by nature and are only used in template, so they can not be static.
See Call static function from angular2 template
So is there a meaningful pattern how to handle functions which are used in templates together with this rule or is it just not not a useful rule for angular?
you can also define in a .ts externals functions like:
export function myFunction(name){
return "Hello "+name;
}
You only need in one component
import {myFunction} from './myfile.ts'
Then you can use in .ts
myFunction("Me");
If you want to use in the html you need declare in your .ts
myFunctionI=myFunction;
And use
{{myFunctionI('me')}}
Other option: your .ts like
export function Util() {
return new UtilClass()
}
class UtilClass {
greet(name){
return "Hello "+name;
}
}
And you can
import {Util} from './myfile-util.ts'
console.log(Util.greet("me"))
I found a satisfying solution myself:
I convert the those function - small, UI-related, used (only) in template, not using the scope (this) as fields, holding arrow functions.
doSomething = (string: string): string => string.toLocaleUpperCase();
For your case, I think a pipe is better.

Using Mobx inject store with Typescript and React stateless component [duplicate]

I'm converting a react project from redux to mobx, and I'm having the following problem: I was using the container/presenter pattern with redux, which meant using the redux "connect" function, like this:
export default connect(mapStateToProps, mapDispatchToProps)(Leads);
The problem I'm having is that there's no equivalent mobx function, so instead, I tried to simply create an instance of the component in the container. Something like:
render() {
return (
<MyComponent
store={mystore}
/>
);
}
Unfortunately, that doesn't work, because MyComponent has injected properties from react-router, something like this:
class MyComponent extends React.Component<ReportProps & RouteComponentProps<ReportProps>> {
constructor(public routeProps: ReportProps & RouteComponentProps<ReportProps>) {
super(routeProps);
}...
I tried getting rid of the container concept, but the same problem occurs in other places because I'm using the mobx-react #inject decorator. For example, I have a component like this:
export interface AddressProps {
report: IReportStore;
}
#inject((rootStore: RootStore) => ({
report: rootStore.report
}))
#observer
class Address extends React.Component<AddressProps> {
...
If I then try to use my component somewhere, typescript complains that I'm not passing the required property (report, in this instance), even though I shouldn't need to, since I'm injecting the properties.
I figure I must be missing something basic, as this is a fairly straightforward use of mobx. Or maybe it's just a typescript problem...? If so, any ideas how to fix or work around it?
Thanks in advance,
Jonathan
There are a lot of problems around the mobx inject method.
the original idea was to return a Partial<TProps> but you can't do this typed without losing your original Class: React.Component<Partial<TProps>,TState> != YourComponent - the set properties.
Read the discussed problem here: https://github.com/mobxjs/mobx-react/issues/256
Simplistic solution
Use optional parameters in props and set them in a getter property:
interface AppProps {
store?: SDtore;
}
class App {
get store(): TimeEntryStore {
return this.props.store as Store;
}
method(){
this.store;///
}
}
Other solution
If you want to keep your required parameters (eg: for using the component outside of mobx etc).
You can consider casting the component to React.Component<TPropsReduced,State> where TPropsReduced is a self defined interface with required props after injected.
The downsides are:
Losing type safety at casting (eg if you make mistakes/typo's in the interface properties. (can be solved by extending/subclassing interfaces.
Losing methods calls. You will no longer have typed methods on the component (eg: when you use ref={()=>}), but using this is dis-advised anyways.
Example:
interface AddressPropsMobx {
}
interface AddressProps extends AddressPropsMobx {
report: IReportStore;
}
//Pure react component
class Address extends React.Component<AddressProps> {}
// Mobx version:
const MobxAddress = inject((rootStore: RootStore) => ({
report: rootStore.report
}))(observer(Address)) as React.Component<AddressPropsMobx>; //unsafe cast

Angular/TypeScript: How to get a reference to a class from a string at runtime without importing

I'm trying to figure out how to get a reference to a class in Angular 2+ (Angular 5) from a string at runtime. I tried the examples on this page. This one didn't work for me:
console.log((<any>Object).create(window[className])); // undefined
And the others are using an import, which I'm trying to avoid.
I'm not using a namespace, but don't know if Angular has one of its own. I tried snooping on the window object to see if I could find anything. All I found were getAllAngularRootElements, getAllAngularTestabilities, and getAngularTestability, but those didn't seem like what I was looking for.
I had a similar need once for dynamically rendering components and only having a string reference to the class that needed to be injected into the page (dynamic dashboard type of app). I ended up doing the following:
Create service to hold onto reference of component by string name
Inject service into module component was part of and register the component
Inject the service into the component that needed to get the component by string name
This was roughly what I had for the service (the class took care of actually creating the dynamic component instead of getting the reference like below):
export class DynamicComponentService {
private dynamicComponentTypes: { [type: string]: Type<BaseInterfaceSectionComponent> } = {};
registerDynamicComponentTypes(...dynamicComponentTypesToRegister: { component: Type<BaseInterfaceSectionComponent>, name: string }[]) {
dynamicComponentTypesToRegister.forEach(dynamicComponentType => {
this.dynamicComponentTypes[dynamicComponentType.name] = dynamicComponentType.component;
});
}
getDynamicComponentType(name: string): Type<BaseInterfaceSectionComponent> {
return this.dynamicComponentTypes[name];
}
}
I was unaware until doing this, but you can actually inject dependencies into a module's constructor. I used this feature to use the service to register the dynamic components:
export class BarChartContentModule {
constructor(dynamicComponentService: DynamicComponentService) {
const dynamicComponent = { component: BarChartContentComponent, name: 'BarChartContentComponent' };
dynamicComponentService.registerDynamicComponentTypes(dynamicComponent);
}
}
Not sure if this is what you were looking for, but figured I'd share.

Categories

Resources