Angular - on button click run function in different component - javascript

I'm trying to do a simple task in Angular but it's giving me some trouble.
I have 2 separate components with no relation between them, let's call them Component A and Component B - all I want is that when I press a button in Comp A to run a function in Comp B - the onClick event and corresponding function is already sorted, I just need to call a function that's coming from Comp B like in the example below:
Component-A.component.ts
onButtonClick() {
//do something
//call function from Comp B
functionB();
}
Component-B.component.ts
functionB() {
element.scrollTo({top: 0})
}
All I want is to call the function from Comp B inside the onClick function of my button from Comp A so that the container of Comp B is scrolled to top.
What would be the simplest way to achieve this? Thank you in advance!

Create a service which has a subject and inject in both the components. Do these 2 things:
in component B, subscribe to the subject
in component A, set the subject inside the function which you want to call
make sure to unsubscribe from the subject
one good example can be found here: https://medium.com/mobiosolutions/angular-communicating-between-components-with-observable-827180e43eb5

Depending on the structure of the components you could use an EventEmmitter. Assuming you can refactor to this structure:
<parent-component>
<component-a (clicked)="onComponentAClicked()"></>
<component-b #compB><>
</parent-component>
The parent-component can listen for the component-a click event to be emitted. It can then call the public scrollToTop function on component-b by refencing the ChildView.
commponent-a
<button (click)="onButtonClick()">Click here</button>
...
export class ComponentA {
#Output('clicked') public clicked = new EventEmitter();
public onButtonClick() {
this.clicked.next(true);
}
}
parent-component
...
export class ParentComponent {
#ViewChild('compB') public compB: ComponentB;
public onComponentAClicked() {
this.compB.scrollToTop();
}
}
If you can't refactor to the above structure then as others suggested; a shared service would solve the issue. In the service you could consider adding a Subject and then subscribe to changes in component-b.
component-a
onButtonClicked() {
this.scrollService.scrollToTop$.next(true);
}
component-b
ngOnInit(): void {
this.scrollService.scrollToTop$.subscribe(() => this.scrollToTop());
}

Related

Angular Two-Way Data Binding and Watching for Changes in Parent Component

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;
}

Call a method within a React Component from Electron's globalShortcut

I have a React component with a method:
class Timer extends Component{
start(){
this.timerInterval=setInterval(this.tick,1000);
}
[...]
}
I want to be able to call the method start() whenever the user presses a combination of keys.
In my main.js file I have:
app.on('ready', function(){
createWindow();
globalShortcut('Cmd+Alt+K',function(){
//call start() here
})
});
How can I achieve this? There's not much information I could find on this subject.
When in need of use of electron library inside react you should import it using electron remote
const { globalShortcut } = require('electron').remote;
class Timer extends Component{
componentDidMount = () => {
globalShortcut.register('Cmd+Alt+K', () => {
//your call
this.start()
});
}
start(){
this.timerInterval=setInterval(this.tick,1000);
}
[...]
}
I was hoping someone with Electron-specific experience would address this, but it's been a full day now.
To cross the outside world / React component barrier, you're probably best off using a custom event. To do that, you'll need access to the DOM element created by React in response to your render call, then listen for your custom event directly on the element. Here's an example, see the comments for details. Note that in this example I'm passing an object as the event detail; that's just to show you can do that (not just simple strings), but you could just do {detail: "Tick #" + ticker} instead and use event.detail directly as the message.
class Example extends React.Component {
// Normal component setup
constructor(...args) {
super(...args);
this.state = {message: ""};
// Our event handler. (We could also define this at the class level using the class
// fields syntax that's currently at Stage 3 in the ECMA TC39 process; most React
// setups with JSX transpile class fields syntax.)
this.doSomething = event => {
this.setState({message: event.detail.message || ""});
};
}
// Render with a way of finding the element in the DOM from outside (in this case
// I'm using a class, but it could be an ID, or just knowing where it is in the DOM).
// We use a ref so we can hook the event when the elemet is created.
render() {
return <div className="foo" ref={element => this.element = element}>{this.state.message}</div>;
}
// Hook the event on mount, unhook it on unmount
componentDidMount() {
this.element.addEventListener("my-event", this.doSomething);
}
componentWillUnmount() {
this.element.removeEventListener("my-event", this.doSomething);
}
}
// Top-level app rendering
ReactDOM.render(<Example />, document.getElementById("root"));
// Demonstrate triggering the event, in our case we do it every half-second or so
let ticker = 0;
setInterval(() => {
const foo = document.querySelector("#root .foo");
++ticker;
foo.dispatchEvent(new CustomEvent("my-event", {detail: {message: "Tick #" + ticker}}));
}, 500);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>

Can I call a method without using .simulate() - Jest Enzyme

I'm unit testing for a React flight seat selecting app using Jest/Enzyme. Is there a way I can test a method within my class based component which would run after a button is clicked, but without actually simulating the button click? This is because the button is within a child of a child component and the method is being passed down as a prop.
Albeit a very simple function, I'd still like to test it
inputSeats(chosenSeats) {
this.setState({
chosenSeats: chosenSeats
})
}
This is in a parent component called FlightSeats, with a child of SeatMaps, and SeatMaps has 2 children of SeatMap (inbound/outbound).
Within each of these SeatMap components there is the 'Reserve Seats' button which when clicked it performs some validation tests, calls another method in SeatMap and eventually calls inputSeats() from within SeatMaps component. I'm struggling to simulate the button click since it is deep within the app.
Ideally, in my unit test, I'd just to like to call it with something like
FlightSeats.inputSeats(chosenSeats)
and pass in my mock data as chosenSeats... Or would I have to import the child components and mount them and use .simulate('click') on the button?
My test so far:
let chosenSeats = {
outbound: [{
seatNo: "11A",
seatPrice: "11.11",
}, {
seatNo: "12A",
seatPrice: "12.12"
}],
inbound: [{
seatNo: "14A",
seatPrice: "14.14",
}, {
seatNo: "15A",
seatPrice: "15.15"
}]
};
let wrapper, buildEmptySeats, clearSeats, comp;
beforeEach(() => {
comp = ( < FlightSeats seats = {
seatsArr
}
party = {
partyArr
}
flights = {
flightArr
}
/>);
wrapper = shallow(comp); component = mount(comp);
});
test('should handle inputSeats correctly', () => {
// what to call here??
expect(component.state().chosenSeats.outbound[1].seatNo).toEqual("12A");
expect(component.state().chosenSeats.outbound[1].seatPrice).toEqual(12.12);
});
I assume you are just testing the functionality, then it should be fine to directly call the method in parent component but its usually good to test the button clicked simulation process as this what the user clicks.
Here is a simple example based on your code above
const chosenSeats = {...};
const component = mount(comp);
test('should handle inputSeats correctly', () =>{
//here get the instance of the component before you can call its method
component.instance().inputSeats(chosenSeats);
//here you call state like you've already done
expect(component.state().chosenSeats.outbound[1].seatNo).toEqual("12A");
expect(component.state().chosenSeats.outbound[1].seatPrice).toEqual(12.12);
};

How to access DOM event-handlers for JSX-components in server-sided React

I am building a simple static view-engine using React with the goal of rendering static HTML-markup and generating a js-file filled with that components DOM-events (onClick, etc).
The way I'm doing the first part is to require a specified JSX-file which, for example, looks like this:
import React from 'React';
export default class Test extends React.Component {
clicked() {
alert('Clicked the header!');
}
render() {
return (
<html>
<head>
<title>{this.props.title}</title>
</head>
<body>
<h1 onClick={this.clicked}>click me!!!</h1>
</body>
</html>
);
}
}
I am then rendering the JSX-file via a NodeJS-backend like this:
let view = require('path-to-the-jsx-file');
view = view.default || view;
const ViewElement = React.createFactory(view);
let output = ReactDOMServer.renderToStaticMarkup(ViewElement(props));
It works great for serving static HTML. But I am wondering if there is a way to access all components used in the JSX-file in an array or something, which I then could use to check what events are bound and to which handlers.
So in this example, be able to get that the <h1>-tag's onClick-handler? Is this even possible to do somehow?
To be able to get the function as a string from the onClick event, we want the following:
The DOM of the element
We can obtain this by attaching a ref attribute on our h1 element
The name of the function being passed into the onClick event (clicked)
The function itself from a string containing the name of the function
Since we're conveniently using methods within a React component, we can use this['functionName'] within our component to obtain the function.
A stringified version of the function
import React from 'React';
export default class Test extends React.Component {
componentDidMount() {
// Gets the name of the function passed inside onClick()
const nameBound = this.element.props.onClick.name;
// Removes 'bound ' from the function name (-> clicked)
const nameString = nameBound.replace('bound ', '');
// Gets the function from the function name as a string
const convertedFunction = this[nameString];
// Converts the function into string
const stringifiedFunction = convertedFunction.toString();
console.log(functionString);
}
clicked() {
alert('Clicked the header!');
}
render() {
return (
<html>
<head>
<title>{this.props.title}</title>
</head>
<body>
<h1 ref={(element) => { this.element = element; }} onClick={this.clicked}>click me!!!</h1>
</body>
</html>
);
}
}
After a lot of messing around I came up with a solution that works quite well.
If I create my own instance of the ReactElement I want to render (in the example ViewElement(props)), I can then render the element using it's standard render-function:
let element = ViewElement(props);
let instance = new element.type();
let render = instance.render();
From here I can go through all the props for this element, so, say, onClick-handlers would be in render.props.
So what I do is to check each prop if the key matches a react-event-name (ex. onClick, onDragEnd, onDragEnter etc). If it does, and the value of this property is of type function - I have the event-name and it's handler-function:
Object.keys(render.props).map((key) => {
if (bigArrayOfAllTheEventNames.indexOf(key) !== -1) {
item.events[key] = render.props[key];//check if type is function.
}
});
Then I also iterate through the render.props.children recursivly to reach all it's child components and add every component which has events to an array.
The only problem left was that I needed a way to bind the rendered DOM-string to the javascript handlers I now have. For this I added a need to use a custom DOM-attribute, which then can be used to ID the component with something like this
$("[data-id=value]").on({event-name}, {it's JS-handler}).
It might not be perfect yet, but I think that this is the best solution out there.

How to access one component's state from another component

How do I access one component's state in another component? Below is my code and I'm trying to access the state of component a in component b.
var a = React.createClass({
getInitialState: function () {
return {
first: "1"
};
},
render: function () {
// Render HTML here.
}
});
var b = React.createClass({
getInitialState: function () {
return {
second: a.state.first
};
},
render: function () {
// Render HTML here.
}
});
But I'm not getting anything.
Even if you try doing this way, it is not correct method to access the state. Better to have a parent component whose children are a and b. The ParentComponent will maintain the state and pass it as props to the children.
For instance,
var ParentComponent = React.createClass({
getInitialState : function() {
return {
first: 1,
}
}
changeFirst: function(newValue) {
this.setState({
first: newValue,
});
}
render: function() {
return (
<a first={this.state.first} changeFirst={this.changeFirst.bind(this)} />
<b first={this.state.first} changeFirst={this.changeFirst.bind(this)} />
)
}
}
Now in your child compoenents a and b, access first variable using this.props.first. When you wish to change the value of first call this.props.changeFirst() function of the ParentComponent. This will change the value and will be thus reflected in both the children a and b.
I am writing component a here, b will be similar:
var a = React.createClass({
render: function() {
var first = this.props.first; // access first anywhere using this.props.first in child
// render JSX
}
}
If two components need access to the same state they should have a common ancestor where the state is kept.
So component A is the parent of B and C.
Component A has the state, and passes it down as props to B and C.
If you want to change the state from B you pass down a callback function as a prop.
I would suggest you use a state manager like Redux (personal favorite), MobX reflux, etc to manage your state.
How these works is they allow you to contain all shared state in one state storage (called a store), and whatever component needs access to a part of that shared state, it will just get it from the store.
It looked very hard to get started with but once you get over the small challenges, get 2 or 3 "wtf's" out of the way. It gets easier.
Take a look here: http://redux.js.org/
EDIT: Redux is good but the boilerplate code is really a turn off... for those of you looking for a simpler, more magical (this can be good and bad) solution use mobx : https://mobx.js.org/
in the child component create function that sets the state:
changeTheState(){
this.setState({something:"some value"})
}
and in parent component give the child a ref as following:
<Child ref={component => this._child = component}/>
then in parent make a function to access the changeTheState()
parentFunction(){
this._child.changeTheState();
}
and just use the parentFunction.
If you have A and B component where B is a child of A, you can pass a function to change the state of A though props to B.
function B(props) {
return <button onClick={props.changeA} />
}
class A extends React.Component {
//constructor
//pass this function to B to change A's state
handleA() {
this.setState({});
}
render() {
return <B changeA={() => this.handleA()} />
}
}
Take a look at React Context
Context provides a way to pass data through the component tree without having to pass props down manually at every level.
You can also update Context from a nested component if required.

Categories