I have a following function inside a child component:
reload() {
clearInterval(this.points);
this.initiateInterval();
this.redrawImages();
}
It's redrawing few images on window.resize event.
But in the parent component, I have a button menu-burger which shrunks the window. I want to bind to this button the reload() function from the child component.
This is my button from the parent component:
<button class="collapseMenuIcon" (click)="toggleMenu()">
I was looking for other questions about it on Stack, but in my case - this reload function uses a lot of stuff in this child component.
Also few solutions from other questions are deprecated, because Angular2 changes often.
Thank you in advance, I will upvote all answers.
A shared Service is a very good option. Just to throw another option out there, depending on your use case, you can just use a Subject between the parent and child without the service, if you want.
Declare Subject in child:
import { Subject } from 'rxjs/Subject';
public static fireEvent: Subject<boolean> = new Subject();
and subscribe in your constructor:
constructor(....) {
MyChildComponent.fireEvent.subscribe(res => {
this.reload();
});
}
And in your parent:
import { MyChildComponent } from './my-path';
and on your button click, tell child to fire reload-method:
toggleMenu() {
MyChildComponent.fireEvent.next(true);
}
There are several ways to communicate between components. I personally use a service for that purpose, and it works great for me.
Read here: https://angular.io/docs/ts/latest/cookbook/component-communication.html
The last example (the service one) is the one that I use, and is quite simple to implement.
Edit:
Using services allow you to have independent components, being able to use them in other parts of the app. You also can send/receive data and the communication is both sides. I strongly recommend this method.
Related
I have a problem working with two components (pop ups) in which i have to send data from a chlid component to another one (parent) who doesn't have an event to extract this data.
logically i have to find a sort of function that makes the parent listen to the changes made in the child.
The Changes have to appear in the same time in both components.
Could any one help ?
The answer is in your question. You need an Output property, which is the Angular generalization of a JS event.
In your child component:
class ChildComponent {
#Input() someProperty: string;
#Output() dataChanged = new EventEmitter<string>();
whenSomethingHappensInChild() {
this.dataChanged.emit('something');
}
}
In your parent template:
...
<app-child [someProperty]="'someValue'" (dataChanged)="doSomething($event)"></app-child>
...
And in you Parent code:
class ParentComponent {
...
doSomething(theValue: string) {
// TA-DAA! you have the value.
}
...
}
Please, do yourself a favor and READ THE DOCS, or, better, a book ;)
In particular:
https://angular.io/guide/architecture-components has a full overview of the basics of binding, which this problem falls into.
Have a nice day.
Yes, you can use a shared BehaviorSubject to push values and both components have to subscribe to get this changes
Problem Solved: I used the #Host() tag to get the current instance of the Parent component and access the methode that changes it's attributes.
Here is what you should do.
First:
You should declate your parent component in the child
parent:ParentComponent;
Second :
you should pass your current parent instance to your new declaration in the constructor
constructor(#Host() currentParent:ParentComponent){
this.parent=currentParent
}
Third:
Now try just to access the methods and attributes in the parent components
changeParentAttribute(){
this.parent.iAmInTheParent();
}
I hope you find this helpful
I'm looking for advice in architecting my Vue app. There's a map with stuff you can click on, and a sidepanel that shows information about what you clicked on. The side panel is wrapped in a new Vue(...) (I'm not sure what to call that - a Vue object?) Currently I attach every Vue object to window so I do stuff like (simplified):
map.on('click', e => window.sidepanel.thingName = e.feature.thingName);
Now, the sidepanel code and the map code are in different modules that otherwise have little reason to communicate.
My approach seems to work ok, but I just wonder what some better patterns would be, other than using globals.
new Vue() => called a vue instance (aka vm)
I think what you are doing is reasonable if you are working inside of constraints. There are a few alternatives
Create an event bus (which is itself a vue instance) that you can use to manage the shared events. Benefits here are that you don't have to reach into the components as deeply, but you also add complexity. https://alligator.io/vuejs/global-event-bus/
Have you considered rending this as a page with the 2 vue instances as components inside of a parent? That would allow you to have the parent take care of the state and pass it down to the components. I think the main benefit of this approach is that it will be more simple to add additional functionality.
Both of these, you would end up doing something like this in the map
map.on('click', e => emit('mapClick', e.feature));
Then in your component listen for the mapClick either on the event bus if you go route 1, or in the parent container component if you go route 2
Hope that helps, good luck!
Example of Parent, the sidepanel would emit
sidepanel = Vue.component('sidepanel')
map = Vue.component('map')
parentComponent = Vue.component('parent', {
components: { map, sidepanel },
template: `
<div>
<map #mapClick="handleMapClick" :dataAsProp="someData"></map>
<sidepanel #userClicked="handleUserClick" :dataAsProp="someData"/>
</div>
`,
data() {
return { someData: [] }
},
methods: {
handleMapClick(event, data) {
// handle your update here, save data, etc
},
handleUserClick(event, data) {
// handle your update here, save data, etc
}
}
})
I'm facing a problem which has been mentioned some times ago but those solutions like this one:
What is the proper use of an EventEmitter?
I want to take this example on the shared link to keep it easy:
Let's start:
First, I have those routes in app.module.ts:
{path: 'dash/:project_id', component: DashProject, children: [
{path: '', component: null},
{path: 'task/form', component: TaskForm},
{path: 'task/:task_id', component: TaskView}
As you can see, DashProject is my parent and those other are children. I've also included to the template of DashProject the required
<router-outlet></router-outlet>
part to include children there.
But in this mentioned example I need to include
<child (notifyParent)="getNotification($event)"></child>
Now I made it like this in my parent template:
<child (notifyParent)="getNotification($event)"></child>
<router-outlet></router-outlet>
Problem: When I add <child (notifyParent)="getNotification($event)"></child> to my parent template the child component is already included to the parent, even it's not called by URL routing. When I remove the part, the interaction between parent-child doesn't work anymore.
If I add those to the child template I get a never ending loop and crash.
Can anyone see my problem or know what is causing this error? I saw some examples on the net, like the shared one above, and all were using a similar solution, but it won't work for me.
Thanks in advance!
Kind regards, yadbo
It looks like you may be merging two different techniques.
Use routing when you want to route to a component. For example, route to the dash project page or the task form page. Normally routed components have no selector and are not directly referenced in the HTML. Rather, they appear in the <router-outlet>.
Use nested components when you want to use a component as a child component of another component. For example, display a set of stars instead of a rating in the project page. The nested component must have a selector and that selector is used in the HTML (like your <child> example.)
When using nested components, you can use #input and #output to communicate between the parent and child.
When using routed components, you can communicate between the components by passing parameters (required, optional, or query parameters), by using a shared resolver, or by setting up properties in a service.
Thanks you #DeborahK for your hint, that was I was missing.
Now I'm using a shared service for solving this problem, I'm passing a callback to the shared service which I call from the child.
Here is an example, at least the idea, how it works:
export class SharedService {
private callback:any = null;
public constructor() {
}
public getCallback() {
return this.callback;
}
public setCallback(call) {
this.callback = call;
}
}
Parent:
this._shared.setCallback(this.test.bind(this));
Child:
this._shared.getCallback()();
And yes, it works :)
How do you properly pass a function from a parent to a child component when the function takes in parameters?
In the ngOnInit, how to scope a function like:
addToList(id) {
this.store.dispatch(this.listActions.addToList(id));
}
ngOnInit, which is wrong right now.
ngOnInit() {
this.addToList = this.addToList.bind(this, id);
}
In my parent component, I have the addToCart(id) function.
I want to pass that function to my child component, which has a list of items, and on clicking the ADD button on an item, I want to callback addToCart(item_id) to the parent.
#Maarek's answer is a good one, and is the 'right' way to do it, probably. What I am presenting here is a simpler means of communicating specifically from the Child to the Parent.
What you proposed in the original post was to have the Parent send a callback method to the Child, so the Child can call it with data when appropriate. To accomplish this specific task (data from Child to Parent on some action in the Child) using Events is appropriate, using the EventEmitter from inside the Child. See this API reference which has an example: https://angular.io/docs/ts/latest/api/core/index/EventEmitter-class.html and this Plunker I made as a demo: https://embed.plnkr.co/T1wFqVOhMXgX6NRfTuiC/
In the child, you have code like this:
import { Component, Input, Output, EventEmitter } from '#angular/core';
#Component({
selector: 'item',
template: `
<div class="item">
<button type="button" (click)="addItem()">Add</button>
<p>{{id}}
</div>
`
})
export class ItemComponent {
#Input() id: string;
//key line here: this emitter can be bound to by parent to get notifications
#Output() add: EventEmitter<string> = new EventEmitter<string>();
constructor() { }
addItem() {
//then when the button is clicked, emit events to the parent.
this.add.emit(this.id);
}
}
The Parent would call create the component like this:
<item id="1" (add)="addToList($event)"></item>
Where addToList() is a function on the Parent that does the work your callback was intended to do. The $event is the data passed from the child (the id).
There's not a lot of detail here, but from what I'm gathering I think what you will want is an injectable service (demonstrated here: https://angular.io/docs/ts/latest/tutorial/toh-pt4.html) to handle the data objects being shared between the components. Rather than type a bunch of code in here (which is better shown at that page in the tutorial) I'll describe what I think you're trying to do and how I'd go about doing it.
The entire store data model can be handled via a service (store.service.ts maybe). Which will have your CRUD functions exposed for the different properties of the store model. The list you are adding to here should have a public getter that returns an observable of the list in the service as well as a public function for adding and deleting from the list. Something like this:
#Injectable
export class StoreService {
private _storeList:BehaviorSubject<Array<any>> = new BehaviorSubject<Array<any>>([]);
/*I'm sure the store has other properties, set them up here. I'd suggest
breaking any arrays out of the general object (unless you want to use
pipes which are awesome but keeping it simple here) but if the store has
a lot of general properties (name, address, whatever) they can be stored
in a single BehaviorSubject of type any.
*/
constructor(){}
get StoreList() { return this._storeList.asObservable() }
public addToList(id) {
let curVal = this._storeList.getValue();
curVal.push(id);
this._storeList.next(curVal);
}
}
You would then inject this service into the constructor of both the parent and the child constructor(private _storeService:StoreService){} (and any other components that need it). The child could then subscribe to the list: get List() { return this._storeService.StoreList } and the parent can call the add function to add to the list. One thing to note, when you add this to your template as an *ngFor, make sure to pass the value through the async pipe. *ngFor="List | async" or your may tear your hair out trying to figure out why you're getting errors.
This article helped me a lot with this as well (although I might suggest avoiding immutable at first until you're comfortable with Angular 2 completely): http://blog.angular-university.io/how-to-build-angular2-apps-using-rxjs-observable-data-services-pitfalls-to-avoid/
I'm playing with React for the first time and I think I really like it. I've implemented (large parts of) the board game Go with it and so far, but I've run into something strange that I don't know how to approach in the idiomatic React way. Basically, I've got a model--the board game--implemented as its own class Board. It exposes only it's constructor, and methods play(i,j) and pass. It handles all of the game logic and updates its own internal state appropriately. It has no reference to anything related to a view/component. I've got a React Component called BoardView which maintains a reference to an instance of a Board. I've also got a Component called AlertView that displays messages about the game state (illegal moves and such) when appropriate.
Everything works well now, and I like the separation of concerns between the Board class and its views. However, the way I have my Board class communicate its changes to the views is unusual, and I feel that it is inconsistent with other React code. Basically, I abuse jQuery's event system to allow me to trigger arbitrary events like ["update", "atari", "suicide"]. In this scheme, the Component has an onClick listener that calls Board.play, which triggers 0 to many events on the Board instance. The Component listens for an "update" event, and calls this.setState, which will force it to re-render(), putting the view into a state that correctly depicts the game. The AlertView listens for the "atari" and "suicide" events on the same board instance and similarly calls this.setState, which triggers another render().
Should I cut out the jQuery events? If so, what's the best way of doing this?
All code is available here and you can play with the app here.
Edit:
For posterity's sake, this question was asked at commit 3f600c.
I'm not sure if this is idiomatic React, but from the React tutorial, the onSubmit handler is passed from the parent to the children as a props.
In your case that would mean to pass the onPlay handler from BoardView to BoardIntersection like this:
var BoardView = React.createClass({
getInitialState: function() {
return {"board": this.props.board}
},
playHandler: function(i, j) {
this.props.board.play(i, j)
},
render: function() {
...
intersections.push(BoardIntersection({
color: this.state.board.board[i][j],
row: i,
col: j,
onPlay: this.playHandler
}));
...
}
})
and BoardIntersection will call onPlay as needed:
var BoardIntersection = React.createClass({
handleClick: function() {
this.props.onPlay(this.props.row, this.props.col);
},
})
tungd's comments pointed me in the right direction, but I decided to answer my own question for a more complete answer.
I ended up removing all of the custom events being fired on the model. I found the following snippet from the React docs to be especially helpful:
A common pattern is to create several stateless components that just render data, and have a stateful component above them in the hierarchy that passes its state to its children via props. The stateful component encapsulates all of the interaction logic, while the stateless components take care of rendering data in a declarative way.
Instead of firing events like "atari" and "suicide" on the model, I just set boolean properties on the model in_atari and attempted_suicide. Now, only one "parent" Component in my application has state. It renders all sub-components via declarative props. The AlertView is one such sub-component whose render method now checks the new boolean flags to render the appropriate text. The main parent Component passes a handler to its sub-components that updates the component state (and subsequently forces a re-render).
In the relevant commit, I've named the parent component ContainerView.