I'm currently learning and using Aurelia and something kind of weird (maybe normal) is happening.
When using the following code
export class NavBar {
get username() {
console.log('o_o')
return 'name' + Date.now()
}
}
And in the template ${username}, the username is always updating, several times per seconds (and console.log are logged several times as well of course).
The workaround is to simply use a function and not a getter and call ${username()} in the template. But is this behaviour normal? So should I use sometimes getter sometimes not?
Thanks!
This is normal, Aurelia polls your property for changes because it has no way of knowing when your property-getter will return a different value.
If it were a simple property (without a getter), Aurelia could observe the property directly, no polling would be needed.
To avoid the polling you could tell Aurelia's binding system what to observe:
import {computedFrom} from 'aurelia-framework';
export class Foo {
_username = 'hello';
#computedFrom('_username')
get username() {
return this._username;
}
}
Another option would be to use a one-time binding:
${username & oneTime}
Related
I have a component that has complex rendering logic.
I try to carry out this logic to helper classes, for simplifying.
To do this, in the data section (for reactivity), I create class references as follows:
export default {
data: () => ({
state: new InitialState(this),
query: new QueryController(this)
})
}
As I understand it, at this point the context of this is not yet defined.
So, I have two questions.
1) Is there a way to pass the this component context in the data section (without lifecycle hooks)?
2) Is the approach with references to external classes of vuejs philosophy contrary?
Component instance is already available when data function runs, this is one of reasons why it has been forced to be a function.
Due to how lexical this works with arrow functions, it's incorrect to use them to access dynamic this. It should be:
data() {
return {
state: new InitialState(this),
query: new QueryController(this)
};
})
The problem with InitialState(this) is that the entire component instance is passed instead of relevant data, this breaks the principle of least privilege.
Despite Vue isn't focused on OOP, there's nothing wrong with using classes. One of possible pitfalls is that classes may not play well with Vue reactivity because it puts restrictions on the implementation. Another pitfall is that classes cannot be serialized to JSON and back without additional measures, this introduces limitations to how application state can be handled.
As I understand it, at this point the context of this is not yet defined.
Only because of the way you've written the code. The component instance does exist and is available. It is sometimes used to access the values of props for determining the initial values of data properties.
For example, here is an example from the documentation:
https://v2.vuejs.org/v2/guide/components-props.html#One-Way-Data-Flow
export default {
props: ['initialCounter'],
data: function () {
return {
counter: this.initialCounter
}
}
}
The reason why your code doesn't work is because you are using an arrow function. If you change it to the following then this will be available:
export default {
data () {
return {
state: new InitialState(this),
query: new QueryController(this)
}
}
}
See also the note here:
https://v2.vuejs.org/v2/api/#data
Note that if you use an arrow function with the data property, this won’t be the component’s instance, but you can still access the instance as the function’s first argument
As to your other question about whether using classes like this is contrary to Vue...
I don't think the use of classes like this is encouraged but they can be made to work so long as you understand the limitations. If you have a clear understanding of how Vue reactivity works, especially the rewriting of properties, then it is possible to write classes like this and for them to work fine. The key is to ensure that any properties you want to be reactive are exposed as properties of the object so Vue can rewrite them.
If you don't need reactivity on these objects then don't put them in data. You'd be better off just creating properties within the created hook instead so the reactivity system doesn't waste time trying to add reactivity to them. So long as they are properties of the instance they will still be accessible in your templates, there's nothing special about using data from that perspective.
I think computed is a better way to do what you want
export default {
computed:{
state(){
return new InitialState(this);
},
query(){
return new QueryController(this);
}
}
}
I'm using a Static variable in my Class to store an initialised BehaviourSubject, so that I can provide a default, while I load the user's settings from the server.
(have put a cut down example version below)
#Injectable
export class AppSettings {
// Using a static to globalize our variable to get
// around different instances making lots of requests.
static readonly currency: Subject<string> = new BehaviorSubject('USD');
// Return a property for general consumption, but using
// a global/static variable to ensure we only call once.
get currency(): Observable<string> { return AppSettings.currency; }
loadFromServer():any {
// Broadcast the currency once we get back
// our settings data from the server.
this.someService.getSettings().subscribe(settings => {
// this is called lastly, but AppSettings.currency.observers
// seems to show as an empty array in the Inspector??
AppSettings.currency.next(settings.currency);
});
}
}
When I subscribe to it later in my code, it will run through it once (since it's a BehaviorSubject), but it won't fire after that.
export class myComponent {
public currency: string;
constructor(settings: AppSettings) {
// Called once with the default 'USD'
settings.currency.subscribe(currency => {
// only gets here once, before loadFromServer
console.log(currency);
this.currency = currency;
});
// Load from the server and have our subscription
// update our Currency property.
settings.loadFromServer();
}
}
The loadFromServer() is working exactly as expected, and the AppSettings.currency.next(settings.currency) line is being called, and after the first event. What is interesting however, is at this point, the AppSettings.currency.observables[] is empty, when it was previously filled in.
My thoughts we're initially an issue of different instances, but I'm using a static variable (have even tried a global one) to avoid different instances.
This is the current workflow...
myComponent.constructor subscribes
that subscription fires, giving the default 'USD'
the server data is loaded, and AppSettings.currency.next(settings.currency) is called
...then...nothing....
I'm expecting that at part 4 the Observer that subscribed in part 1 would be fired again, but it isn't, making my glorified Observer a constant. :(
Am I missing something?
Well I feel sheepish....
Figured out the issue was due to my import statement having the (wrong) file suffix on the file reference. So, in the myComponent file I had...
import { AppSettings } from './settings.js';
While everywhere else I have been using (the correct)
import { AppSettings } from './settings';
which was causing WebPack to compiling two versions of the class, the TypeScript and the (compiled) Javascript version, thus creating two different instances. I managed to see an AppSettings_1 somewhere, that lead me down the rabbit hole to finally gave it away.
I'm not sure how to tackle this issue because there's quite a bit into it, and the behavior is one I've never seen before from JavaScript or from Vue.js
Of course, I will try to keep the code minimal to the most critical and pieces
I'm using vue-class-component(6.3.2), so my Vue(2.5.17) components look like classes :)
This particular component looks like so:
import GameInterface from '#/GameInterface';
class GameComponent extends Vue {
public gameInterface = GameInterface();
public mounted() {
this.gameInterface.launch();
}
}
GameInterface return an object with a launch method and other game variables.
In the game interface file to method looks something like this:
const GameInterface = function () {
const obj = {
gameState: {
players: {},
},
gameInitialized: false,
launch() => {
game = createMyGame(obj); // set gameInitialized to true
},
};
return obj;
}
export default GameInterface;
Great, it works, the object is passed onto my Phaser game :) and it is also returned by the method, meaning that Vue can now use this object.
At some point I have a getter method in my Vue class that looks like so:
get currentPlayer() {
if (!this.gameInterface.gameInitialized) return null;
if (!this.gameInterface.gameState.players[this.user.id]) {
return null;
}
return this.gameInterface.gameState.players[this.user.id];
}
And sure enough, null is returned even though the player and id is clearly there.
When I console.log this.user.id I get 4, and gameInterface.gameState.players returns an object with getters for players like so:
{
4: { ... },
5: { ... },
}
Alright, so it does not return the player even though the object and key are being passed correctly...
But I found an extremely strange way to "FIX" this issue: By adding JSON.parse(JSON.stringify(gameState)) like so
get currentPlayer() {
// ...
if (!this.gameInterface.gameState.players[this.user.id]) {
// add this line
JSON.stringify(this.gameInterface.gameState);
return null;
}
return this.gameInterface.gameState.players[this.user.id];
}
It successfully returns the current player for us... Strange no?
My guess is that when we do this, we "bump" the object, Vue notices some change because of this and updates the object correctly. Does anyone know what I'm missing here?
After working on the problem with a friend, I found the underlying issue being a JavaScript-specific one involving Vue's reactive nature.
https://v2.vuejs.org/v2/guide/reactivity.html#Change-Detection-Caveats
In this section of the documentation, a caveat of Vue's change detection is discussed:
Vue cannot detect property addition or deletion. Since Vue performs the getter/setter conversion process during instance initialization, a property must be present in the data object in order for Vue to convert it and make it reactive.
When, in my game run-time, I set players like so:
gameObj.gameState.players[user.id] = {...playerData}
I am adding a new property that Vue has not converted on initialization, and Vue does not detect this change. This is a simple concept I failed to take into account when developing my game run-time.
In order to correctly set a new player, I've decided to use the spread operator to change the entirety of the players object, which Vue is reacting to, and in turn, Vue will detect my player being added like so:
gameObj.gameState.players = {
...gameObj.gameState.players,
[user.id]: {...playerData}
}
Vue also discusses another method called $set, which you can read on the same page.
A private (but open source) Angular package in the organisation I work for has some code that looks like this:
ngAfterViewInit(): void {
setTimeout(() => {
this.changeDetector.detectChanges();
// do more things
});
}
We implement this package in our application (the package is to introduce common components so that front-end devs across the org don't implement things multiple times, and to enforce a common design language for consistency). However, when using the component this is called in, and showing and then destroying the component repeatedly, eventually it will stop working, with this error in the console:
Error: ViewDestroyedError: Attempt to use a destroyed view: detectChanges
I'm trying to find a way of making sure the code above is not being run if the component has already been destroyed. I have considered using the ngOnDestroy lifecycle method:
ngOnDestroy(): void {
this.changeDetector.detach();
}
but I'm not sure what else I would put in there to check the view is ok. I also thought about something like:
if (!this.changeDetector.destroyed) {
this.changeDetector.detectChanges();
}
but nothing like destroyed exists on ChangeDetectorRef.
What is the correct way of ensuring this error does not show and the component works, even when repeatedly showing and destroying it?
I think the best solution I have found is to use !ViewRef.destroyed and if that returns truthy then proceed, otherwise don't.
if (!(this.changeDetector as ViewRef).destroyed) {
this.changeDetector.detectChanges()
// do other tasks
}
I think destroyed does work but the syntax is like this:
if (!this.changeDetector['destroyed']) {
this.changeDetector.detectChanges();
}
Then obviously, just keep the code you have on your ngOnDestroy hook.
Inside Angular service i have a variable count, which i want to observe whenever it's updated, i want to set that updated value to another variable in a different component.
import {BehaviorSubject} from "rxjs/BehaviorSubject";
import 'rxjs/Rx';
export class DashboardService{
count = new BehaviorSubject<number>(0);
count$=this.count.asObservable();
addCount(data){
this.count.next(data);
}
}
I get the error eventhough i have imported all the relevant libraries .Any idea why i'm getting this? can anyone tell me what's happening with the code ?
}
rxJs version 5.4.3 latest!
Try this:
export class DashboardService {
count = new BehaviorSubject<number>(0);
count$ = this.count.asObservable();
public addCount = (data) => {
this.count.next(data);
}
}
The issue you are encountering is that you are using addCount in a context where this inside the function is altered (i.e. pointing to something else). Using lamda based functions in typescript ensures this can not happen. This is not an excuse for always using them however.
I recommend you read up a bit on lambda/arrow functions, it might also be a good idea to look in to how this works in javascript and in typescript.