Update ngrx selector inside ngOnChanges - javascript

I have a parent component (B) that is getting data from it's parent input (A)
(C) have is (B) child component.
Inside (B) I'm having a selector that gets data from the store.
export class BComponent implements OnChanges {
#Input() branchId;
ngOnChanges() {
this.selectedDataByBranch$ = this.store.pipe(
select(selectBranchDirections, { branchId: this.branchId, dir: this.selectedDirection })
);
this.selectedDataByBranch$.subscribe(selectedDataByBranch => {
this.trainsDatasets = this.getDatasets(selectedDataByBranch);
this.lineChart.data.datasets = this.trainsDatasets ? this.trainsDatasets : [];
this.lineChart.update();
});
directionChanged(event) {
this.selectedDirection = event;
this.selectedDataByBranch$ = this.store.pipe(
select(selectBranchDirections, { branchId: this.branchId, dir: this.selectedDirection })
);
}
}
directionChanged is the Output event that I get from (C)
The issue this that selectedDataByBranch subscription is not getting the new data update triggered inside selectedDataByBranch$
I have also tried this way
directionChanged(event) {
this.selectedDirection = event;
select(selectBranchDirections, { branchId: this.branchId, dir: this.selectedDirection });
}

What i could suggest is. Turn your parameters into a Subject then merge with the store selection, in your directionChanged(event) method provide value to subject.
So your final code will be something like this:
export class BComponent implements OnChanges {
#Input() branchId;
criterias$= new Subject<{branchId:number,dir:number}>;
ngOnChanges() {
this.selectedDataByBranch$ = this.criterias$.pipe(mergeMap(criteria=> this.store.pipe(
select(selectBranchDirections, { branchId: criteria.branchId, dir: this.searchDirection})
)));
this.selectedDataByBranch$.subscribe(selectedDataByBranch => {
this.trainsDatasets = this.getDatasets(selectedDataByBranch);
this.lineChart.data.datasets = this.trainsDatasets ? this.trainsDatasets : [];
this.lineChart.update();
});
this.criterias$.next({branchId:this.branchId,dir:this.sortDirection}); // init first call
}
directionChanged(event) {
this.selectedDirection = event;
this.criterias$.next({ branchId: criteria.branchId, dir: this.searchDirection}});
);
}
}
This stackblitz tries to materialize what i say.

Related

Angular 4 function works only from second time

I am creating a shopping cart in Angular 4 and want to check if a new product prod yet exists in the cartProducts array.
Here's my Component:
Component
import { Component, OnInit } from '#angular/core';
import { Router } from "#angular/router";
import { ProductsService } from '../service/products.service';
#Component({
selector: 'app-store',
templateUrl: './store.component.html',
styleUrls: ['./store.component.css']
})
export class StoreComponent implements OnInit {
itemCount: number;
cartProducts: any = [];
productsList = [];
constructor( private _products: ProductsService ) { }
ngOnInit() {
this.itemCount = this.cartProducts.length;
this._products.product.subscribe(res => this.cartProducts = res);
this._products.updateProducts(this.cartProducts);
this._products.getProducts().subscribe(data => this.productsList = data);
}
addToCart(prod){
this.cartProducts.hasOwnProperty(prod.id) ? console.log("Added yet!") : this.cartProducts.push(prod);
console.log(this.cartProducts)
}
}
My addToCart function which is fired by click works fine, but only from second time.
1 click - we add a product in the empty cartProducts array, the product is added
2 click - although the product is added, it is added again and there are two same products in the array now. I've got the array with the two same products.
3 click - console shows "Added yet!", now it recognizes that the product is in the array yet.
UPD
The product is an object of type:
{
"id" : "1",
"title" : "Title 1",
"color" : "white"
}
How to fix the issue?
hasOwnProperty is for checking if a key exists in an object, you're using it for an array. Use this instead:
addToCart(prod){
this.cartProducts.indexOf(prod) > -1 ? console.log("Added yet!") : this.cartProducts.push(prod);
console.log(this.cartProducts)
}
try this :
let idx = this.cartProducts.findIndex(elem => {
return prod === elem
})
if (idx !== -1) {
console.log("Added yet!")
} else {
this.cartProducts.push(prod);
}

Propagating value changes with Observables

I use Observables to carry values from parent to child components.
Here is my top level app component:
import { Component } from '#angular/core';
import { Observable } from 'rxjs/Observable';
import { Observer } from 'rxjs/Observer';
import { ViewModel } from './ViewModel.data';
import { ViewComponent } from './ViewComponent.component';
#Component({
selector: ...,
directives: [
ROUTER_DIRECTIVES,
ViewComponent
],
precompile: [],
styles: [],
template: `
<div>
<ul class="nav nav-tabs">
<li *ngFor="let v of views"><a (click)="setView(v)">{{ v.name }}</a></li>
</ul>
<viewcomponent
*ngFor="let v of views"
[viewName]="v.name"
[activeViewObservable]="activeViewObservable"></viewcomponent>
</div>
`
})
export class AppComponent{
views:ViewModel[];
activeViewObservable:Observable<ViewModel>;
viewObserver:Observer<ViewModel>;
activeView:ViewModel;
constructor() {
this.views = [{name: 'one'}, {name: 'two'}, {name: 'three'}, {name: 'four'}];
this.activeViewObservable = new Observable<ViewModel>(observer => this.viewObserver = observer);
}
public setView(view:ViewModel):void {
this.viewObserver.next(view); // load values here
}
}
I use a component called viewcomponent here:
#Component({
selector: 'viewcomponent',
directives: [
ROUTER_DIRECTIVES
],
template: `
<div class="tab-pane" [ngClass]="{ 'active': isActive() }">
...
</div>
`
})
export class ViewComponent {
// these values are always the last view. why???
#Input() viewName:string;
#Input() activeViewObservable:Observable<TabViewModel>;
private activeView:ViewModel;
constructor() {}
ngOnInit() {
this.activeViewObservable.subscribe( //listen to values loaded with the viewObserver
activeView => {this.activeView = activeView;},
error => console.error(error));
}
public isActive():boolean {
let bool:boolean = false;
if (this.activeView) {
console.log(this.viewName); // <---- this value is always the last view, 'four'
bool = this.activeView.name == this.viewName;
}
console.log(bool);
return bool;
}
}
The data model im using is here:
export interface ViewModel {
name: string;
}
I'm trying to load an observer with values in the AppComponent and then subscribe to them in the child. However, the value emitted by the observable is always the last element.
I want to call the setView method in the parent and then apply a class to that specific child view.
Possible solution: use Subject if you want to call .next() on an Observer outside of the instantiation of the Observable. Subject behaves as both. You can subscribe to them separately from where you pass events to it. Plunker Example
Why your code does not work?
Your code that uses rxjs could write like this:
let viewObserver;
const myObservable = new Observable(observer => {
viewObserver = observer;
});
myObservable.subscribe(
activeView => {
console.log(1, activeView);
},
error => console.error(error));
myObservable.subscribe(
activeView => {
console.log(2, activeView);
},
error => console.error(error));
viewObserver.next({ name: 'one' });
https://jsfiddle.net/t5z9jyf0/
What is expected output?
2, { name: 'one' }
Why?
Let's open rxjs documentation
http://reactivex.io/rxjs/manual/overview.html#subscribing-to-observables
Key point there is:
When calling observable.subscribe with an Observer, the function
subscribe in Observable.create(function subscribe(observer) {...}) is
run for that given Observer. Each call to observable.subscribe
triggers its own independent setup for that given Observer.
let viewObserver;
var myObservable = new Observable<ViewModel>(function subscribe(observer) {
console.log(observer, observer.destination._next);
viewObserver = observer;
});
myObservable.subscribe( // this triggers subscribe function above
activeView => {
console.log(1, activeView);
},
error => console.error(error));
myObservable.subscribe( // this also triggers subscribe function above
activeView => {
console.log(2, activeView);
},
error => console.error(error));
viewObserver.next({ name: 'one' }); // notify subscriptions
https://jsfiddle.net/t5z9jyf0/1/
So that code does not work because after
myObservable.subscribe(
activeView => {
console.log(2, activeView);
},
error => console.error(error));
method is executed, viewObserver will be overwritten and it will be Subscriber object from activeView => { console.log(2, activeView); }, so viewObserver.next will give us
console.log(2, { name: 'one' });
That's why only last subscription is executed

React pass all events to child component

Is it possible in React to pass all event to child element.
as an example I've got a custom Button class, that (simplified) looks something like this:
class Button extends Component {
constructor (props) {
super(props);
this.onClick = this.onClick.bind(this);
}
/* .... */
onClick (ev) {
const { disabled, onClick } = this.props;
if (!disabled) {
onClick(ev);
}
}
render () {
const {
children,
disabled,
type
} = this.props;
return (
<button
disabled={disabled}
onClick={this.onClick}
ref="button"
type={type}
>{children}</button>
}
}
I don't know what events i may want to use in the future (onMouseDown, onMouseUp, onBlur, onKeyDown, onTouchStart, and so on...)
Is it possible to pass all possible events to the button element without writing out a prop for every possible event?
adding {...this.props} to the button element is not what I want because it passes all props and some props (like className which is omitted in this example) should not be passed directly.
I thought about cloning the props object and deleting the props which should not be passed directly but this feels like a hack. Does anybody know a cleaner way?
I've written a function to iterate over the props and filter out all properties starting with 'on' this is the closest I've come so far. In case it helps anyone else:
/* helpers.js */
export function filterEvents (props, ignore = []) {
let events = {};
for (let property in props) {
if (props.hasOwnProperty(property)) {
if (property.startsWith('on') && ignore.indexOf(property) === -1) {
events[property] = props[property];
}
}
}
return events;
}
/* Tests for the filterEvents */
import { expect } from 'chai';
import { filterEvents } from './helpers';
describe('filterEvents', () => {
const props = {
className: 'someClass',
disabled: true,
onBlur: 'onBlur',
onClick: 'onClick',
onMouseDown: 'onMouseDown',
onMouseUp: 'onMouseUp'
};
it('only returns keys starting with on', () => {
const expected = {
onBlur: 'onBlur',
onClick: 'onClick',
onMouseDown: 'onMouseDown',
onMouseUp: 'onMouseUp'
};
expect(filterEvents(props)).to.deep.equal(expected);
});
it('only returns keys starting with on minus the ones in the ignore array', () => {
const expected = {
onBlur: 'onBlur',
onMouseUp: 'onMouseUp'
};
const ignore = ['onClick', 'onMouseDown'];
expect(filterEvents(props, ignore)).to.deep.equal(expected);
});
});
/* Using the function inside a component */
import { filterEvents } from './helpers'; //at the top of the components file
//Inside the render method:
const events = filterEvents(this.props, ['onClick']); //don't include onClick it's handled like the questions example
return (
<button
disabled={this.props.disabled}
onClick={this.onClick}
{...events}
>
{this.props.children}
</button>
);
I've taken Barry127's answer and added all the event handlers from React and put them in an object.
const acceptedEventHandlersForComponentValidationFunction = {
clipBoard: [
"onCopy",
"onCut",
"onPaste",
"onCopyCapture",
"onCutCapture",
"onPasteCapture"
],
composition: [
"onCompositionEnd",
"onCompositionStart",
"onCompositionUpdate",
"onCompositionEndCapture",
"onCompositionStartCapture",
"onCompositionUpdateCapture"
],
keyboard: [
"onKeyDown",
"onKeyPress",
"onKeyUp",
"onKeyDownCapture",
"onKeyPressCapture",
"onKeyUpCapture"
],
focus: ["onFocus", "onBlur", "onFocusCapture", "onBlurCapture"],
form: [
"onChange",
"onInput",
"onInvalid",
"onReset",
"onSubmit",
"onChangeCapture",
"onInputCapture",
"onInvalidCapture",
"onResetCapture",
"onSubmitCapture"
],
generic: ["onError", "onLoad", "onErrorCapture", "onLoadCapture"],
mouse: [
"onClick",
"onContextMenu",
"onDoubleClick",
"onDrag",
"onDragEnd",
"onDragEnter",
"onDragExit",
"onDragLeave",
"onDragOver",
"onDragStart",
"onDrop",
"onMouseDown",
"onMouseEnter",
"onMouseLeave",
"onMouseMove",
"onMouseOut",
"onMouseOver",
"onMouseUp",
"onClickCapture",
"onContextMenuCapture",
"onDoubleClickCapture",
"onDragCapture",
"onDragEndCapture",
"onDragEnterCapture",
"onDragExitCapture",
"onDragLeaveCapture",
"onDragOverCapture",
"onDragStartCapture",
"onDropCapture",
"onMouseDownCapture",
"onMouseMoveCapture",
"onMouseOutCapture",
"onMouseOverCapture",
"onMouseUpCapture"
],
pointer: [
"onPointerDown",
"onPointerMove",
"onPointerUp",
"onPointerCancel",
"onGotPointerCapture",
"onLostPointerCapture",
"onPointerEnter",
"onPointerLeave",
"onPointerOver",
"onPointerOut",
"onPointerDownCapture",
"onPointerMoveCapture",
"onPointerUpCapture",
"onPointerCancelCapture",
"onGotPointerCaptureCapture",
"onLostPointerCaptureCapture",
"onPointerOverCapture",
"onPointerOutCapture"
],
selection: ["onSelect", "onSelectCapture"],
touch: [
"onTouchCancel",
"onTouchEnd",
"onTouchMove",
"onTouchStart",
"onTouchCancelCapture",
"onTouchEndCapture",
"onTouchMoveCapture",
"onTouchStartCapture"
],
ui: ["onScroll", "onScrollCapture"],
wheel: ["onWheel", "onWheelCapture"],
media: [
"onAbort",
"onCanPlay",
"onCanPlayThrough",
"onDurationChange",
"onEmptied",
"onEncrypted",
"onEnded",
"onError",
"onLoadedData",
"onLoadedMetadata",
"onLoadStart",
"onPause",
"onPlay",
"onPlaying",
"onProgress",
"onRateChange",
"onSeeked",
"onSeeking",
"onStalled",
"onSuspend",
"onTimeUpdate",
"onVolumeChange",
"onWaiting",
"onAbortCapture",
"onCanPlayCapture",
"onCanPlayThroughCapture",
"onDurationChangeCapture",
"onEmptiedCapture",
"onEncryptedCapture",
"onEndedCapture",
"onErrorCapture",
"onLoadedDataCapture",
"onLoadedMetadataCapture",
"onLoadStartCapture",
"onPauseCapture",
"onPlayCapture",
"onPlayingCapture",
"onProgressCapture",
"onRateChangeCapture",
"onSeekedCapture",
"onSeekingCapture",
"onStalledCapture",
"onSuspendCapture",
"onTimeUpdateCapture",
"onVolumeChangeCapture",
"onWaitingCapture"
],
image: ["onLoad", "onError", "onLoadCapture", "onErrorCapture"],
animation: [
"onAnimationStart",
"onAnimationEnd",
"onAnimationIteration",
"onAnimationStartCapture",
"onAnimationEndCapture",
"onAnimationIterationCapture"
],
transition: ["onTransitionEnd", "onTransitionEndCapture"],
other: ["onToggle", "onToggleCapture"]
}
/*
- Component props event handler vilidation
Return all valid events to be used on a component
{
acceptedEventHandlerTypes: [
"${event handler type}"
],
eventHandlers: {
${event}: "${callback function}" // ${event} can contain "Capture" at the end to register the event handler for the capture phase
}
}
*/
const validateComponentPropsEventHandlers = (
acceptedEventHandlerTypes,
eventHandlers = {}
) => {
if (Object.keys(eventHandlers).length == 0) {
return {}
}
// Fill eventsForSpecifiedType with only the required events
let eventsForSpecifiedType = {}
let eventsCount = 0
for (const eventHandlerType in acceptedEventHandlerTypes) {
if (
acceptedEventHandlerTypes[eventHandlerType] in
acceptedEventHandlersForComponentValidationFunction
) {
const newEvents =
acceptedEventHandlersForComponentValidationFunction[
acceptedEventHandlerTypes[eventHandlerType]
]
eventsForSpecifiedType[
acceptedEventHandlerTypes[eventHandlerType]
] = newEvents
eventsCount += newEvents.length
}
}
// Fill events
let events = {}
let eventsCountCheck = 0
const checkIfEventsCountHasBeenReached = () =>
eventsCountCheck == eventsCount
for (const eventHandler in eventHandlers) {
if (checkIfEventsCountHasBeenReached()) {
return events
}
// Append event handler to events object if it is recognised
Object.values(eventsForSpecifiedType).forEach(
(EVENT_HANDLERS) => {
if (
EVENT_HANDLERS.includes(eventHandler) &&
!(eventHandler in events)
) {
events[eventHandler] = eventHandlers[eventHandler]
eventsCountCheck += 1
}
if (checkIfEventsCountHasBeenReached()) {
return events
}
}
)
}
return events
}
// Usage
const test = () => {console.log("test")}
const events = validateComponentPropsEventHandlers(["mouse"], { onClick: test })
console.log(events)
// <button {...events}>Button</button>

How to reset ViewContainerRef in angular2 after change Detection?

So I am working on this app in which I have used ViewContainerRef along with dynamicComponentLoader like below:
generic.component.ts
export class GenericComponent implements OnInit, OnChanges{
#ViewChild('target', { read: ViewContainerRef }) target;
#Input('input-model') inputModel: any = {};
constructor(private dcl: DynamicComponentLoader) { }
ngAfterViewInit() {
this.dcl.loadNextToLocation(DemoComponent,this.target)
.then(ref => {
if (this.inputModel[this.objAttr] === undefined) {
ref.instance.inputModel = this.inputModel;
} else {
ref.instance.inputModel[this.objAttr] = this.inputModel[this.objAttr];
}
});
console.log('Generic Component :===== DemoComponent===== Loaded');
}
ngOnChanges(changes) {
console.log('ngOnChanges - propertyName = ' + JSON.stringify(changes['inputModel'].currentValue));
this.inputModel=changes['inputModel'].currentValue;
}
}
generic.html
<div #target></div>
So It renders the DemoComponentin target element correctly.
but when I am changing the inputModel then I want to reset the view of target element.
I tried onOnChanges to reset the inputModel , its getting changed correctly but the view is not getting updated for respective change.
So I want to know if is it possible to reset the view inside ngOnChanges after the inputModel is updated?
any inputs?
There is no connection between this.inputModel and ref.instance.inputModel. If one changes you need to copy it again.
For example like:
export class GenericComponent implements OnInit, OnChanges{
componentRef:ComponentRef;
#ViewChild('target', { read: ViewContainerRef }) target;
#Input('input-model') inputModel: any = {};
constructor(private dcl: DynamicComponentLoader) { }
ngAfterViewInit() {
this.dcl.loadNextToLocation(DemoComponent,this.target)
.then(ref => {
this.componentRef = ref;
this.updateModel();
});
console.log('Generic Component :===== DemoComponent===== Loaded');
}
updateModel() {
if(!this.componentRef) return;
if (this.inputModel[this.objAttr] === undefined) {
this.componentRef.instance.inputModel = this.inputModel;
} else {
this.componentRef.instance.inputModel[this.objAttr] = this.inputModel[this.objAttr];
}
}
ngOnChanges(changes) {
console.log('ngOnChanges - propertyName = ' + JSON.stringify(changes['inputModel'].currentValue));
this.inputModel=changes['inputModel'].currentValue;
this.updateModel();
}
}

Reactjs: How to update component when a new prop comes up?

I want to update/re-render the component after a new update comes up. All I am doing is:
I have a list of dealers for a casino game, what I want is to add a new dealer, and once the new dealer is added then display it in the view. It is actually happening, but in order for me to see the new dealer, I have to reload the page.
I am not updating the state, I am working with this.props. Look at my code
#connectToStores
export default class Dealers extends Component {
constructor (props) {
super(props);
this.state = {}
}
componentWillMount () {
GetDealersActions.getDealers();
}
static getStores () {
return [ GetDealersStore, CreateDealersStore ];
}
static getPropsFromStores () {
return {
...GetDealersStore.getState(),
...CreateDealersStore.getState(),
}
}
render () {
return (
<div>
{!!this.props.dealerData ?
this.props.dealerData.dealersData.map((dealer) => {
return (here I am rendering what I need);
: <p>Loading . . .</p>
</div>
}
_addDealer = () => {
CreateDealersActions.createDealer({
DealerName : this.refs.DealerName.getValue(),
CardId : this.refs.CardId.getValue(),
NickName : this.refs.NickName.getValue(),
});
}
}
as you see the component above in the code is doing the initial rendering properly, the problem comes up when you hit _addDealer(), which is not updating the component, you should reload the page in order to see the new item in the view.
If you do a console.log(this.props); within _addDealer(), you will get something like this
{params: Object, query: Object, dealerData: Object, newDealerData: null}
where dealerData holds the full data of the dealers in the view but you can't see there the new dealer created. And newDealerData remains null
so, what do you think I should do in order to update the component everytime a new prop/dealer comes up ? or how do I update the props? which is the proper method in this situation ?
here is the full code for stores and actions just in case
action
#createActions(flux)
class CreateDealersActions {
constructor () {
this.generateActions('createDealerSuccess', 'createDealerFail');
}
createDealer (data) {
const that = this;
that.dispatch();
axios.post(`${API_ENDPOINT}/create-dealer/create-dealer`, data)
.then(function success (data) {
that.actions.createDealerSuccess({data});
})
}
};
store
#createStore(flux)
class CreateDealersStore {
constructor () {
this.state = {
newDealerData : null,
};
}
#bind(CreateDealersActions.createDealerSuccess)
createDealerSuccess (data) {
this.setState({
newDealerData : data.response.config.data,
});
}
}
the Dealers component is within a tab named management, which is this one:
const menuItems = [
{ route : 'dealers', text : 'Dealers' },
{ route : 'game-info', text : 'Game Info' },
{ route : 'player-info', text : 'Players Info' },
{ route : 'money', text : 'Money' }
];
export default class Management extends React.Component {
static propTypes = {
getActivePage : React.PropTypes.func,
menuItems : React.PropTypes.arrayOf(React.PropTypes.object),
}
static contextTypes = {
router : React.PropTypes.func,
}
render () {
return (
<div>
<TabsMainMenu menuItems={menuItems} getActivePage={this._getActivePage} />
<RouteHandler />
</div>
);
}
_getActivePage = () => {
for (const i in menuItems) {
if (this.context.router.isActive(menuItems[i].route)) return parseInt(i, 10);
}
}
}

Categories

Resources