This class will display this = undefined. How to pass the context in?
class MeteorAccount implements IService{
constructor() {
Tracker.autorun(function () {
//observe
Meteor.userId());
console.log(this);//undefined
});
}
}
You have two options:
The Function.bind function:
class MeteorAccount implements IService{
constructor() {
Tracker.autorun(function() {
//observe
Meteor.userId());
console.log(this);//undefined
}.bind(this));
}
}
Or the arrow function:
class MeteorAccount implements IService{
constructor() {
Tracker.autorun(() => {
//observe
Meteor.userId());
console.log(this);//undefined
});
}
}
Edit
There's another option, I just don't like it, but it's how people used to do it before the arrow function (not sure why not the bind option):
class MeteorAccount implements IService{
constructor() {
var self = this;
Tracker.autorun(function() {
//observe
Meteor.userId());
console.log(self);//undefined
});
}
}
Related
How I could efficiently pass value from MyElement to GrandChildrenElement?
index.html
<my-element></my-element>
myElement.ts
#customElement('my-element')
export class MyElement extends LitElement {
value = 'foo';
onChangeValue() {
this.value = 'bar';
}
render() {
return html`
<child-element></child-element>
`;
}
}
childElement.ts
#customElement('child-element')
export class ChildElement extends LitElement {
...
render() {
return html`
<grandchild-element></grandchild-element>
`;
}
}
grandChildElement.ts
#customElement('grandchild-element')
export class GrandChildElement extends LitElement {
#property()
value = '';
render() {
return html`
<p>${value}</p>
`;
}
}
The standard way would be to pass the value down through the child element using properties:
#customElement('my-element')
export class Element extends LitElement {
#state()
value = 'foo';
toggle() {
if (this.value === 'foo') {
this.value = 'bar';
} else {
this.value = 'foo';
}
}
render() {
return html`
<my-child value="${this.value}"></my-child>
<button #click="${this.toggle}">Toggle</button>
`;
}
}
#customElement('my-child')
export class Child extends LitElement {
#property({ type: 'string' })
value;
render() {
return html`<my-grandchild value="${this.value}"></my-grandchild>`;
}
}
#customElement('my-grandchild')
export class GrandChild extends LitElement {
#property({ type: 'string' })
value;
render() {
return html`<div>${this.value}</div>`;
}
}
Playground
But if you're looking for a way to bypass the elements in between, you'll have to get a bit more creative.
One solution could be to have the element collect subscribers and update them when its value changes.
In the example below, the grandchild dispatches a "subscribe" event when it connects, which bubbles up to the element. The element then gets the grandchild (via the event's composedPath), updates its value, and adds it to the set of subscribers. When the element changes its value within toggle(), it updates its subscribers.
import {html, css, LitElement} from 'lit';
import {customElement, property, state} from 'lit/decorators.js';
#customElement('my-element')
export class Element extends LitElement {
value = 'foo';
subscribers = new Set();
constructor() {
super();
this.addEventListener('subscribe', (e: CustomEvent) => {
const composedTarget = e.composedPath()[0] as any;
composedTarget.value = this.value;
this.subscribers.add(composedTarget);
});
this.addEventListener('unsubscribe', (e: CustomEvent) => {
const composedTarget = e.composedPath()[0];
this.subscribers.delete(composedTarget);
});
}
toggle() {
if (this.value === 'foo') {
this.value = 'bar';
} else {
this.value = 'foo';
}
for (const subscriber of this.subscribers) {
grandchild.value = this.value;
}
}
render() {
return html`
<my-child></my-child>
<button #click="${this.toggle}">Toggle</button>
`;
}
}
#customElement('my-child')
export class Child extends LitElement {
render() {
return html`<my-grandchild></my-grandchild>`;
}
}
#customElement('my-grandchild')
export class GrandChild extends LitElement {
#property({ type: 'string' })
value;
connectedCallback() {
super.connectedCallback();
this.dispatchEvent(new CustomEvent('subscribe', { bubbles: true, composed: true }));
}
disconnectedCallback() {
super.disconnectedCallback();
this.dispatchEvent(new CustomEvent('unsubscribe', { bubbles: true, composed: true }));
}
render() {
return html`<div>${this.value}</div>`;
}
}
Playground
I am having trouble sharing information between components. What I am trying to do is, show in the home.component.html data that is generated in the about.component.ts.
The operation would be:
By means of a button in the home.component.html, a function in the home.component.ts is called, this function calls a function of the about-service.ts, which collects the data from the about.component.html that obtains the about.component.ts data.
This is my home.component.html:
<button mat-menu-item (click)="goToAbout()">
<mat-icon svgIcon="logoBN"></mat-icon>
Acerca
</button>
This is my home.component.ts:
import { AboutComponent } from '../about/about.component';
export class HomeComponent {
public aboutInfo: AboutService;
goToAbout() {
.subscribe(emitData =>
}
}
From the goToAbout() function of the home.component.ts I need to get the data from the aboutBuild() function of the about.component.ts:
This is my about.component.ts:
import { AboutService } from '';
export class AboutComponent {
ngOnInit() {
}
aboutBuild() {
......code.........
}
}
This is my about.component.html:
<header class="title-color" fxFlex>Build Info</header>
I have created a function in the service to try to communicate both components.
about-service.ts:
observer = new Subject();
public subscriber$ = this.observer.asObservable();
emitData(aboutBuild) {
this.observer.next(aboutBuild);
}
But I can't access the aboutBuild() function of the about.component.ts, what do I need to include in the service to communicate the two components?
The AboutService is fine you have one public subscriber and a function to trigger event.
Your goToAbout() in home.component.ts :
export class HomeComponent {
constructor(public aboutService: AboutService) {}
goToAbout() {
let data = {key: 'value'};
this.aboutService.emitData(data);
}
}
Then your about.component.ts:
export class AboutComponent {
constructor(public aboutService: AboutService) {}
ngOnInit() {
this.aboutService.subscriber$.subscribe(data => {
this.aboutBuild(data);
});
}
aboutBuild(data) {
console.log(data)
}
}
I'm intrigued by webcomponents (and pretty new to javascript) and I am trying to set up a dummy microfrontend app. Ok, components are served by different web server (webpack devserver to be honest) and correctly displayed in the 'aggregator' page. I also add a customevent to send some text from a webcomponent to another and this is not working. Here is the sender webcomponent:
const tmplt = document.createElement('template');
tmplt.innerHTML = `
<div>
<label for="sendnpt">message:</label>
<input id="sendnpt" type="text"></input>
<button id="sendbtn">Send the message</button>
<div id="msglog"></div>
</div>
`;
var input = {};
window.customElements.define('team-zero', class TeamZero extends HTMLElement {
constructor() {
super();
this._shadowRoot = this.attachShadow({ 'mode': 'open' });
this._shadowRoot.appendChild(tmplt.content.cloneNode(true));
this.button = this._shadowRoot.getElementById('sendbtn');
input = this._shadowRoot.getElementById('sendnpt');
this.button.addEventListener('click', function(evt) {
var msg = {
bubbles: true,
detail: {}
};
msg.detail.name = input.value;
console.log('sending msg: ' + JSON.stringify(msg))
window.dispatchEvent(new CustomEvent('greet', msg));
});
}
connectedCallback() {
console.log('connected and ready!');
}
});
And this is the receiver:
const m = require('mithril');
window.customElements.define('team-one', class TeamOne extends HTMLElement {
constructor() {
super();
this._shadowRoot = this.attachShadow({ 'mode': 'open' });
window.addEventListener('greet', (msg) => {
console.log("a message arrives: " + JSON.stringify(msg));
});
}
connectedCallback() {
console.log('connected!');
m.mount(this._shadowRoot, {
view: function(vnode) {
return m("div", "team-0ne runs mithril!");
}
});
}
});
Problem is that event is emitted and received, but there is no detail in it. Why receiver component cannot log the text the other one is sending, what am I missing?
You can see the whole dummy project here.
Unlike default Events like click,
Custom Events require a composed:true to 'escape' shadowDOM
https://developer.mozilla.org/en-US/docs/Web/API/Event/composed
Note: besides data you can also pass Function references
<my-element id=ONE><button>Click One</button></my-element>
<my-element id=TWO><button>Click Two</button></my-element>
<script>
window.customElements.define('my-element', class extends HTMLElement {
constructor() {
super().attachShadow({mode:'open'}).innerHTML = `<slot></slot>`;
let report = (evt) =>
document.body.append(
document.createElement("br"),
`${this.id} received: ${evt.type} - detail: ${JSON.stringify(evt.detail)} `,
evt.detail.callback && evt.detail.callback(this.id) // not for 'click' event
);
window.addEventListener('greet', report); // Custom Event
window.addEventListener('click', report); // button click
this.onclick = () =>
window.dispatchEvent(new CustomEvent('greet', {
bubbles: true,
composed: true,
detail: {
fromid: this.id,
callback: this.callback.bind(this)
}
}));
}
callback(payload){
return `${this.id} executed callback function(${payload})`;
}
});
</script>
Also see: https://pm.dartus.fr/blog/a-complete-guide-on-shadow-dom-and-event-propagation/
I'm trying to use the same data in ngOnInit of several components, the problem is, the data are random every time I subscribe to it. I can only subscribe one time in the entire run of the app. That means, if I subscribe twice, it won't have any use because each component will have something else.
I want to subscribe once in a service and to create a global variable.
but when you try to use that global variable in all the components (on ngOnInit) it is always undefined.
How can I make the components wait for that same thing that can only be called once in the app?
export class UsersService {
public allUsers: Array<Object> = []
public allUsersUrl: string = 'https://glacial-escarpment-40412.herokuapp.com/users/'
constructor(private http: HttpClient) {
this.getAllUsers().subscribe(data => {
this.allUsers = data;
console.log(this.allUsers)
})
}
getAllUsers(): Observable<any> {
return this.http.get<any>(this.allUsersUrl);
}
getUser(id: number): Observable<any> {
return this.http.get<any>(this.allUsersUrl + id);
}
}
My components:
export class FirstComponent {
constructor(private usersService: UsersService){}
ngOnInit() {
console.log(this.usersService.allUsers) //undefined
}
export class SecondComponent {
constructor(private usersService: UsersService){}
ngOnInit() {
console.log(this.usersService.allUsers) //undefined
}
Please help me :)
You have a synchronism problem. This is your scenario
1- create first component
1.1 - injecting UsersService
1.2 - UsersService request for ASYNC data (execution continues)
2- FirstComponent get and print this.usersService.allUsers (not still populated because of async request)
3- this.usersService.allUsers is still undefined
You need Subject
Something like this:
UsersService
export class UsersService {
private _allUsersSource = new Subject<Array<Object>>();
private _allUsers$ = this._allUsersSource.asObservable();
public allUsersUrl: string = 'https://glacial-escarpment-40412.herokuapp.com/users/'
constructor(private http: HttpClient) {
this.getAllUsers();
}
getAllUsers(): Observable<any> {
return this.http.get<any>(this.allUsersUrl).subscribe(
data => this._allUsersSource.next(data)
);
}
get allUsers$(): Observable<Array<Object>> {
return this._allUsers$;
}
// OTHERS
}
FirstComponent
export class FirstComponent {
subscription: Subscription;
allUsers: Array<Object>;
constructor(private usersService: UsersService){}
ngOnInit() {
this.subscription = this.usersService.allUsers$.subscribe(users => {
this.allUsers = users;
console.log(this.allUsers);
});
}
Some thing for SecondComponent
Is there another way of writing this? By the way, this works perfectly but I feel it could be written better:
Profile = React.createClass({
mixins: [ReactMeteorData],
getMeteorData() {
return {
currentUser: Meteor.user(),
};
},
getInitialState(){
// we add an if statement to prevent undefined errors
// could this be written elsewhere?
if (Meteor.user().profile) {
this.profile = Meteor.user().profile;
} else {
this.profile = '';
};
return { firstname: this.profile.firstname };
},
...
)};
Yes, you can use an ES6 class, which is the way recommended when working with Meteor (ecmascript package has you covered). Also, you don't need to use Meteor.user() outside getMeteorData() method.
class Profile extends React.Component {
constructor(props){ super(props); }
mixins: [ReactMeteorData]
getMeteorData() {
return {
user: Meteor.user(),
};
}
getInitialState(){
return {
firstname: this.data.user && this.data.user.profile.firstname
}
}
}