I have a model object representing "players" in DB. in it's implementation there is an array of players, which i would like to bind to from different VM's in my app. for example:
import {Players} from './models/players';
import {inject, BindingEngine} from 'aurelia-framework';
#inject(Players,BindingEngine)
export class App {
constructor(playersProvider,bindingEngine) {
this._playersProvider = playersProvider;
this._bindingEngine = bindingEngine;
this._subscription = this._bindingEngine.propertyObserver(this,this._playersCount)
.subscribe(this.objectValueChanged);
}
async activate() {
await this._playersProvider.initialize();
this._playersCount = this._playersProvider.players.length;
}
objectValueChanged(newVal,oldVal) {
console.log("new : " + newVal + ", old val : " + oldVal);
}
deactivate() {
this._subscription.dispose();
}
}
unfortunately, when a change is made to the players array (from other parts in the app) the change is not reflected in _playersCount property. e.g. - UI label bound to this property is not refreshed, and objectValueChanged never gets called.
U have the same issue in a different VM with a collectionObserver on the same array.
any help?
Have you tried to declare _playersCount in the constructor before subscribing to it?
Also the synthax does not seem correct, it should be according to this article:
import {BindingEngine, inject} from 'aurelia-framework';
#inject(BindingEngine)
class MyClass {
constructor(bindingEngine) {
this.bindingEngine = bindingEngine;
this.observeMe = 'myvalue'; // the property is first initialized in the constructor
let subscription = this.bindingEngine
.propertyObserver(this, 'observeMe') // <= you wrote this._bindingEngine.propertyObserver(this,this.observeMe)
.subscribe(this.objectValueChanged);
// Dispose of observer when you are done via: subscription.dispose();
}
objectValueChanged(newValue, oldValue) {
console.log(`observeMe value changed from: ${oldValue} to:${newValue}`);
}
}
The async keyword might affect the behaviour.
If it still does not work, you can use an event aggregator to broadcast the change.
Related
can anyone provide a solution to the problem that I'm currently encountering? I created a custom element where this custom element must have been detected on the dom, but I need to have the data contained in this custom element loaded, so my program code is like this.
import './menu-item.js';
class MenuList extends HTMLElement {
// forEach cannot be used if I use the ConnectedCallback () method
connectedCallback() {
this.render()
}
// my data can be from this method setter
set menus(menus) {
this._menus = menus;
this.render();
}
render() {
this._menus.forEach(menu => {
const menuItemElement = document.createElement('menu-item');
menuItemElement.menu = menu;
this.appendChild(menuItemElement);
});
}
}
customElements.define('menu-list', MenuList);
and this is the data I sent in the main.js file
import '../component/menu/menu-list.js';
import polo from '../data/polo/polo.js';
const menuListElement = document.querySelector('menu-list');
menuListElement.menus = polo;
please give me the solution.
The connectedCallback runs before the menus=polo statement.
So there is no this._menus declared.
If all the menus setter does is call render, then why not merge them:
set menus(menus) {
this.append(...menus.map(menu => {
const menuItemElement = document.createElement('menu-item');
menuItemElement.menu = menu;
return menuItemElement;
}));
}
It seems there is no way to watch changes in the parent component when using two-way data binding.
I have a custom input component for collecting a tag list. Two-way data binding is setup and working between this component and its parent.
// the parent component is just a form
// here is how I'm adding the child component
<input-tags formControlName="skillField" [(tags)]='skillTags' (ngModelChange)="skillTagUpdate($event)">
</input-tags>
In the parent component how do you watch the bound variable for changes? While it's always up to date (I've confirmed this) I cannot find any guidance on reacting to changes.
I've tried:
ngOnChanges(changes: SimpleChanges) {
if (changes['skillTags']) {
console.log(this.skillTags); // nothing
}
}
And
skillTagUpdate(event){
console.log(event); // nothing
}
UPDATE:
TWDB IMHO is not what it is advertised to be. Whenever I arrive at this place where TWDB seems to be a solution I rearchitect for a service and or observable communication instead.
When you implement a two way binding of your own, you have to implement an event Emitter. The syntax for that is mandatory.
this means that you have a hook to listen to if the value changes.
Here is a demo :
<hello [(name)]="name" (nameChange)="doSomething()"></hello>
_name: string;
#Output() nameChange = new EventEmitter();
set name(val) {
this._name = val;
this.nameChange.emit(this._name);
}
#Input()
get name() {
return this._name;
}
counter = 0;
ngOnInit() {
setInterval(() => {
this.name = this.name + ', ' + this.counter++;
}, 1000);
}
Stackblitz
From what I know, this seems the less annoying way to use it, and any two way binding will follow the same rule no matter what, i.e. it ends with the Change word !
Your implementation is actually not two-way databinding, the parent and child component are just sharing a reference on the same skillTags variable.
The syntax [(tags)]='skillTags' is syntaxic sugar for [tags]='skillTags' (tagsChange)='skillTags = $event'
You need to implement tagsChange in the child component like this: #Output('tagsChange') tagsChange = new EventEmitter<any>();, then any time you want to modify tags into the children component, dont do it directly, but use this.tagsChange.emit(newValue) instead.
At this point, you'll have real two-way databinding and the parent component is the unique owner of the variable (responsible for applying changes on it and broadcasting changes to the children).
Now in your parent component, if you want to do more than skillTags = $event (implicitly done with [(tags)]='skillTags'), then just add another listener with (tagsChange)='someFunction($event)'.
StackBlitz Demo
Don't know if this is what you're looking for, but have you tried using #Input()?
In child component
#Input() set variableName(value: valueType) {
console.log(value);
}
In parent component
<input-tags formControlName="skillField" [(tags)]='skillTags'
[variableName]="skillTagUpdate($event)"></input-tags>
The input function is called every time the object binded to the function is changed.
you could listen to the change:
<input-tags formControlName="skillField" [tags]='skillTags' (tagsChange)='skillTags=$event; skillTagUpdate();'></input-tags>
or use getter and setter:
get skillTags(): string {
return ...
}
set skillTags(value) {
variable = value;
}
another approach:
export class Test implements DoCheck {
differ: KeyValueDiffer<string, any>;
public skillTags: string[] = [];
ngDoCheck() {
const change = this.differ.diff(this.skillTags);
if (change) {
change.forEachChangedItem(item => {
doSomething();
});
}
}
constructor(private differs: KeyValueDiffers) {
this.differ = this.differs.find({}).create();
}
}}
1.you can use output(eventemitter)
2.easiest solution is rxjs/subject. it can be observer and observable in same time
Usage:
1.Create Subject Property in service:
import { Subject } from 'rxjs';
export class AuthService {
loginAccures: Subject<boolean> = new Subject<boolean>();
}
2.When event happend in child page/component use :
logout(){
this.authService.loginAccures.next(false);
}
3.And subscribe to subject in parent page/component:
constructor(private authService: AuthService) {
this.authService.loginAccures.subscribe((isLoggedIn: boolean) => {this.isLoggedIn = isLoggedIn;})
}
Update
for two-way binding you can use viewchild to access to your child component items and properties
<input-tags #test></<input-tags>
and in ts file
#ViewChild('test') inputTagsComponent : InputTagsComponent;
save()
{
var childModel = this.inputTagsComponent.Model;
}
I'm trying to understand deep observability in MobX.
In particular, in the following code I'd like the autorun to be called every time I run setCommentCountForPost, but currently it isn't.
How should I fix this code? And, observable on a property of Post is enough to activate the autorun when I read the list in which the post is contained? (as I'm doing in the autorun)
I'm using MobX 5.
Edit: I discovered the code is working properly if I use the following call inside the autorun: console.log(toJS(p.getPosts()));.
This is interesting, but why, and how should I do if I only want to call getPosts()?
This is the code
import { useStrict, configure, autorun } from 'mobx';
import { toJS, observable, action, computed } from 'mobx';
configure({ enforceActions: true });
class Post {
#observable commentCount = 0;
setCommentCount(c) {
this.commentCount = c;
}
}
class PostList {
#observable _posts = {};
#action createPost(id) {
this._posts[id] = new Post();
}
#action setCommentCountForPost(id, c) {
this._posts[id].setCommentCount(c);
}
getPosts() {
return this._posts;
}
}
let p = new PostList();
p.createPost(1);
autorun(function test () {
console.log(p.getPosts());
});
p.setCommentCountForPost(1, 22);
MobX tracks property access, not value
in your example, the autorun function only tracking the _posts, but not the property of _posts, so if you change the _posts value the tracking function will worked
console.log(toJS(p.getPosts())) worked bacause of the toJS function in order to convert the observable value to normal value , it access the property of _posts.
if you hope the p.getPosts() worked, you should iteration access the property of _posts.
I found when I use the service within forEach,the parameter I send to the service beacome the last one of the circulation.
I thought it should be the closure cause this problem,so I try to use the anonymous function to solve it,but didn't work
Pass the obj in Component
this function is triggered in ngOnInit may cause the problem
when I put this code into a click event,it works fine
for (var y = 0; y < this.model.data.length;y++) {
if (this.model.data[i].to[x].id === this.model.data[y].id) {
let obj = {
selectedItem : this.model.data[i],
item : this.model.data[y]
};
(function(_obj,a) {
console.log ('obje in component:');
console.log (_obj.item.id) //each obj over here is correct now
a._drawLineService.drawLine(_obj);
a._dragLineService.dragLine(_obj.item);
})(obj,this)
}
}
Get the obj in directive
this.subscription = this._drawLineService.drawLine$
.subscribe(obj => {
console.log ('drawLine:')
console.log (obj.item.id) //each obj over here become the last one of array
});
I use the observable to pass the event and parameter from component to directive
My service
import {Injectable} from '#angular/core'
import {BehaviorSubject} from 'rxjs/BehaviorSubject';
#Injectable()
export class DrawLineService {
private _drawLine = new BehaviorSubject<any>('');
drawLine$ = this._drawLine.asObservable();
drawLine(item) {
this._drawLine.next(item);
}
}
Console result:
How to solve this?
change var obj=... to let obj=...
let define variable to block scope while var define variable to function or global scope.
You are actually keep overwriting the obj variable with your loop.
Let's say I have an array of elements and in addition to displaying the list in my app, I want to sync the list to the server with HttpClient. How can I observe changes to the array? I tried:
#inject(ObserverLocator)
export class ViewModel {
constructor(obsLoc) {
this.list = [];
obsLoc.getObserver(this, 'list');
.subscribe(li => console.log(li));
}
}
But I got neither error nor log message.
getObserver returns a property observer which will notify you when the ViewModel class instance's list property changes. This will only happen when you assign a new value to the list property, ie this.list = [1,2,3]. If you're not assigning new values to the list property and instead are mutating the value of the property via push, pop, splice, etc, you'll want to use an array observer. Use the ObserverLocator's getArrayObserver method- it takes one parameter, the array you want to observe:
import {ObserverLocator} from 'aurelia-binding'; // or from 'aurelia-framework'
#inject(ObserverLocator)
export class ViewModel {
constructor(obsLoc) {
this.list = [];
obsLoc.getArrayObserver(this.list);
.subscribe(splices => console.log(splices));
}
}
October 2015 update
The ObserverLocator is Aurelia's internal "bare metal" API. There's now a public API for the binding engine that could be used:
import {BindingEngine} from 'aurelia-binding'; // or from 'aurelia-framework'
#inject(BindingEngine)
export class ViewModel {
constructor(bindingEngine) {
this.list = []; // any Array, Map and soon Set will be supported
// subscribe
let subscription = bindingEngine.collectionObserver(this.list)
.subscribe(splices => console.log(splices));
// be sure to unsubscribe **later**
subscription.dispose();
}
}