In my Aurelia SPA I have some functions I want to use in different modules. It relys on parameters given when called and parameters of a singleton. Is there a way to create an export function that I can inject my Auth singleton into without having to pass it as a parameter everytime I call the function?
A simple example of how I'd like it to look would be this.
import Auth from './auth/storage';
import { inject } from 'aurelia-framework';
#inject(Auth)
export function A(foo: boolean): boolean {
let auth = Auth;
if (auth.authorized && foo) {
return true
}
else {
return false
}
}
I know I could just wrap it in a class and use that but want to know if there is any way to achieve it similar to this
If you want to use dependency injection in a function, use Container from aurelia-dependency-injection:
import Auth from './auth/storage';
import { Container } from 'aurelia-dependency-injection';
export function A(foo: boolean): boolean {
let auth = Container.instance.get(Auth);
if (auth.authorized && foo) {
return true
}
else {
return false
}
}
Related
I would like to pass my object based on JavaScript class from mixin to component without reactivity. I use TypeScript, so I can't set the object to this of mixin without setting types in data(). But data() reactivity breaks some thing in my object.
mixin.js:
export default {
setup() {
const foo = new Foo()
return {
foo,
}
}
}
component.js:
import mixin from './mixin.js'
export default {
setup() {
// How can I get foo here?
}
}
Update 1
Yes, it is good solution for using only one foo instance for everywhere.
But how can I use different foo instances for each component?
Same as in JS classes:
class Mixin {
foo() {
// ...
}
}
class Component extends Mixin {
bar() {
this.foo()
}
}
mixin.js
export const foo = new Foo()
Import (and use) foo anywhere in your app, including any setup() function:
import { foo } from './path/to/mixin'
The above uses the same instance everywhere. If you want to use different instances of foo in each separate component, mixin.js:
export const useFoo = () => new Foo()
Anywhere else:
import { useFoo } from './path/to/mixin'
const foo = useFoo()
However, take note the second approach creates a new intance of Foo() every time useFoo() is called. So once you called it, you must use foo in that component.
Calling useFoo() multiple times in the same component will generate multiple instances, unlike how you'd use useStore(), for example.
But, I'm wondering, why do you need a mixin in the first place? Why not use:
const foo = new Foo()
...in the components where you need it?
What are you trying to achieve? And, more importantly, why?
Good day.
I am trying to preserve a property of ItemViewModel via config helper. I am able to successfully save the property (conf directory with appropriate .properties file is generated), however upon next start, the property does not restore its value, just remains null. Here's a sample code to demonstrate my issue:
import javafx.beans.property.SimpleStringProperty
import tornadofx.*
data class Foo(val doNotPreserveMe: String, val preserveMe: String)
class FooModel : ItemViewModel<Foo>() {
val doNotPreserveMe = bind { item?.doNotPreserveMe?.toProperty() }
val preserveMe = bind { SimpleStringProperty(item?.preserveMe, "pm", config.string("pm")) }
}
class FooApp : App(FooView::class)
class FooView : View() {
private val model = FooModel()
override val root = form {
fieldset {
field("Do not preserve me") { textfield(model.doNotPreserveMe).required() }
field("Preserve me") { textfield(model.preserveMe).required() }
button("Do something") {
enableWhen(model.valid)
action {
model.commit {
// ...
with(config) {
set("pm" to model.preserveMe.value)
save()
}
}
}
}
}
}
}
Any ideas on why the model is not restoring the value?
Each Component has it's own config store, which is backed by a separate file. Either make sure to use the same config file, or the app global config file.
You can refer to other component's config store, so one solution would be to let the View access the ViewModel's config store like this:
button("Do something") {
enableWhen(model.valid)
action {
model.commit {
// ...
with(model.config) {
set("pm" to model.preserveMe.value)
save()
}
}
}
}
However, there is a much simpler and more contained solution, which is simply to handle save in the FooModel's onCommit callback
override fun onCommit() {
with(config) {
set("pm" to preserveMe.value)
save()
}
}
In this case you'd simply call model.commit() in the button callback.
You can also use a common, or even global config object. Either use a Controller's config store, or the global store. To use the global config object, just refer to app.config in both the model and the view.
Let's assume I have a global state class that contains a TimeFrame class which has some useful properties
export class StateContainer {
public timeframe: TimeFrame;
public setTimeframe(startTime: Date, endTime: Date) {
// treat this.timeframe as immutable
this.timeframe = { startTime: startTime, endTime: endTime };
}
}
export class TimeFrame {
public readonly startTime: Date;
public readonly endTime: Date;
}
Then, I need to consume this state elsewhere, so I do so via DI and then use the bindingEngine.propertyObserver to get changes on the timeframe object as one would do.
However, I would like be able to do something similar to the following, if it's possible:
#autoinject
export class Display {
#observable
timeFrame: TimeFrame = this.state.timeframe;
constructor(private state: StateContainer) {
}
timeFrameChanged(newValue: TimeFrame, oldValue: TimeFrame) {
...
// everytime this.state.timeFrame is changed via setTimeFrame()
// this change handler should fire, and so should any
// bindings from this component for this.timeFrame
}
}
However, when I do the previous, I only get timeFrameChanged(...) notifications on the inital creation, not whenever I call setTimeFrame(). Am I doing something wrong or is this not possible?
Yes, you are doing something wrong, I think you are misunderstanding how the observable decorator and the corresponding timeFrameChanged method are meant to be used.
Let's take it bit by bit. Take a look at these lines:
#observable
timeFrame: TimeFrame = this.state.timeframe;
timeFrameChanged(newValue: TimeFrame, oldValue: TimeFrame) { ... }
The observable decorator tells Aurelia that whenever the property to which it is applied changes, execute the corresponding method. Unless otherwise configured, the corresponding method is nameOfInvolvedProperty + "Changed" (in this case, as you are correctly doing, timeFrameChanged).
However, you are never actually changing the value of that property! If you actually changed that property, it would work:
<button click.delegate="changeProp()">
Change prop
</button>
and the VM:
changeProp() {
this.timeFrame = null;
this.timeFrame = this.state.timeframe;
}
Now you'd see that the handler correctly fires.
But in your code, this property is only ever assigned once, here:
timeFrame: TimeFrame = this.state.timeframe;
Remember, this is just dereferencing. In other words, this code tells the app to take the value of this.state.timeframe, store the momentary value of it in that variable and forget about this.state.timeframe. So it does not get updated whenever this.state.timeframe is updated.
As described in the documentation, you can configure the observable decorator to some extent, however, to my knowledge, there is no way to configure it in such a way that set it up to observe nested properties (or maybe it's just me not knowing how to do that).
Even if it was possible, I think a cleaner way to deal with such situations would be to use an event aggregator. This enables you to employ a subscription mechanism - whenever the value you are interested in changes, you publish an event and whichever component is interested in that change can subscribe to it and update itself accordingly.
A simple implementation:
import { Router, RouterConfiguration } from 'aurelia-router';
import { autoinject } from 'aurelia-framework';
import { observable } from 'aurelia-binding';
import { StateContainer } from './state-container';
import { EventAggregator, Subscription } from 'aurelia-event-aggregator';
#autoinject
export class App {
subscription: Subscription = null;
constructor(private state: StateContainer, private eventAggregator: EventAggregator) {
}
activate() {
this.subscription = this.eventAggregator.subscribe('state:timeframe', (e) => {
console.log('Timeframe changed!', e);
});
}
deactivate() {
// Make sure to dispose of the subscription to avoid memory leaks.
this.subscription.dispose();
}
change() {
this.state.setTimeframe(new Date(), new Date());
}
}
And StateContainer:
import { TimeFrame } from "./time-frame";
import { autoinject } from "aurelia-framework";
import { EventAggregator } from "aurelia-event-aggregator";
#autoinject
export class StateContainer {
public timeframe: TimeFrame = null;
constructor(private eventAggregator: EventAggregator) {
}
public setTimeframe(startTime: Date, endTime: Date) {
// treat this.timeframe as immutable
this.timeframe = { startTime: startTime, endTime: endTime };
this.eventAggregator.publish('state:timeframe', this.timeframe);
}
}
Hope that helps.
I am looking for specialized singleton implementation, probably I might be using wrong terminology and hence looking for expert suggestion. Here is my scenario:
There is common code which can be called by ComponentA or ComponentB. I need to push telemetry data from the common code. Telemetry needs to have information that whether this common code get called by ComponentA or ComponentB.
So common code will have just this line of code:
telemetry.pushData(this._area, data);
where this._area tells the telemetry data is getting pushed for which component
I need to push telemetry data from multiple places so it would be good if object got created once and used through out the code lifetime
One option I can think of passing component context to the common code which in mind doesn't look right, hence looking for suggestion what kind of pattern one should use in this case?
This is what I am thinking
// Telemetry.ts file present in shared code
export class Telemetry extends Singleton {
public constructor() {
super();
}
public static instance(): Telemetry {
return super.instance<Telemetry>(Telemetry);
}
public publishEvent(data): void {
if (!this.area) {
throw new Error("Error: Initialize telemetry class with right area");
}
pushtelemetryData(this.area, data);
}
public area: string;
}
// Create Telemetry object from component A
Telemetry.instance().area = "ComponentA";
// Shared code will call telemetry publishEvent
Telemetry.instance().publishEvent(data);
Thanks
It's not a good pattern to use in TypeScript where you would generally inject dependencies.
If you must absolutely do it then you can do it by faking it somewhat:
namespace Telemetry {
var instance : SingletonSomething;
export function push(data: Any) : void {
if (instance == null) {
instance = new SingletonSomething();
}
instance.push(data);
}
class SingletonSomething() { ... }
}
and then you could call
Telemetry.push(data);
You can imitate the singleton pattern in typescript easily:
class Telemetry {
private static instance: Telemetry;
public static getInstance(): Telemetry {
if (Telemetry.instance == null) {
Telemetry.instance = new Telemetry();
}
return Telemetry.instance;
}
...
}
If you have your code in some sort of closure (module, namespace, etc) then you can replace the static member with:
let telemetryInstance: Telemetry;
export class Telemetry {
public static getInstance(): Telemetry {
if (telemetryInstance == null) {
telemetryInstance = new Telemetry();
}
return telemetryInstance;
}
...
}
But then you can also replace the static method with:
let telemetryInstance: Telemetry;
export function getTelemetryInstance(): Telemetry {
if (telemetryInstance == null) {
telemetryInstance = new Telemetry();
}
return telemetryInstance;
}
export class Telemetry {
...
}
At this point, in case you are using some sort of closure, you might ask yourself if you really need the class at all?
If you use this as a module:
// telemetry.ts
export interface TelemetryData {
...
}
export function pushData(data: TelemetryData): void {
...
}
Then you get exactly what you're looking for, and this is more of the "javascript way" of doing it.
Edit
In the telemetry module there's no need to know the users of it.
If the Telemetry.pushData function needs to have information about the object that called it then define an interface for it:
// telemetry.ts
export interface TelemetryData {
...
}
export interface TelemetryComponent {
name: string;
...
}
export function pushData(data: TelemetryData, component: TelemetryComponent): void {
...
}
Then in the other modules, where you use it:
// someModule.ts
import * as Telemetry from "./telemetry";
class MyComponent implement Telemetry.TelemetryComponent {
// can also be a simple string property
public get name() {
return "MyComponent";
}
fn() {
...
Telemetry.pushData({ ... }, this);
}
}
2nd Edit
Because you are using a module system, your module files are enough to make singletons, there's no need for a class to achieve that.
You can do this:
// telemetry.ts
let area: string;
export interface TelemetryData {
...
}
export function setArea(usedArea: string) {
area = usedArea;
}
export function pushData(data: TelemetryData): void {
...
}
Then:
Telemetry.setArea("ComponentA");
...
Telemetry.publishEvent(data);
The telemetry module will be created only once per page, so you can treat the entire module as a singleton.
Export only the functions that are needed.
I have this little TypeScript app for node.js that needs to connect to a MongoDB:
module Engine
{
export class EngineCore
{
public launch()
{
var mongo = require('mongodb');
var client = mongo.MongoClient;
client.connect('mongodb://localhost:27017/', (error, db) => {this.onDBConnect(db)});
}
private onDBConnect(db)
{
}
}
}
Now, I know that I'm getting a Db class on connect callback, but I can't figure out the way to explicitly type the db argument to the Db class. I'm using a DefinitelyTyped definition file for mongodb, it goes like this:
declare module "mongodb" {
export class MongoClient {
constructor(serverConfig: any, options: any);
static connect(uri: string, options: any, callback: (err: Error, db: Db) => void): void;
static connect(uri: string, callback: (err: Error, db: Db) => void): void;
}
export class Db {
constructor (databaseName: string, serverConfig: Server, dbOptions?: DbCreateOptions);
[...]
}
}
I've tried importing it like this:
module Engine
{
import MongoDB = require('mongodb');
[...]
}
But I'm getting the following error:
error TS2136: Import declarations in an internal module cannot reference an external module.
Is there any way to use the Db class for explicit typing from outside the module?
Have you tried re-positioning the import like this? the below compiles and runs for me
import mongo = require('mongodb');
module Engine
{
export class EngineCore
{
public launch()
{
var client = mongo.MongoClient;
client.connect('mongodb://localhost:27017/', (error, db) => {
this.onDBConnect(db);
});
}
private onDBConnect(db:mongo.Db)
{
console.log("connected");
}
}
}
var x = new Engine.EngineCore();
x.launch();