I have the following use case where i want to pass a part of a complex object to an angular component.
<app-component [set]="data.set"></app-component>
Now i want the object 'data.set' in the parent class to always be the same like the object 'set' inside the child class.
If I instead do it the following way, both objects are the same and changes are "synced".
<app-component [set]="set"></app-component>
How can i achieve this behaviour, when binding 'data.set' instead of 'set', without manually triggering an EventEmitter?
If you need changes done to set within app-component to be visible in parent component, then, you need to use two-way binding.
<app-component [(set)]="data.set"></app-component>
In the app-component.component.ts file, you need to declare two members:
#Input()
public set: any;
#Ouput()
public setChange:EventEmitter = new EventEmitter();
And whenever, there is change to the value of set, you need to emit the updated value.
this.setChange.emit(newVal);
You could refer to this article if you need more details.
Related
I have a variable that stores the available cars at any moment. Is there a way to automatically re-evaluate this function on every change?
Just using this.carFactory.available in this case is not a solution, because this example I'm showing is simplified - the real calculation in my project is alot more complex.
calculateAvailableCars(){
this.carFactory.available.forEach(function(item){
this.availableCars.push(car.id);
}.bind(this));
}
How could I do this in Angular 2? In Angular JS there was the possibility to $watch a function.
I could of course manually call this function everytime something changes, but it would be nice not to have to call this function in every part of the application that can change the data.
Using template function reference with auto change detection
You can use this function output on template:
carOutput(): cars[] {
this.calculateAvailableCars()
return this.availableCars;
}
and use output on template:
<p>My car ratio is {{ carOutput() }} </p>
However this will trigger very aggressive change detection strategy on this variable. This solution is the simpliest one, but from engineering perspective rather worst: consumes tons of unnecessary function calls. One note, that hosting element must not be set to detect changes onPush.
Separate data model to parent component and pass as property to child
You can store car list display in separate component, and pass new car array as input property to this component:
<car-display [cars]="availableCars"></car-display>
Then you can set changeDetetcion policy in this component to onPush, and each time input property bind to availableCars will change, <car-display> will re-render.
If update relays on some host binding
If some external host action is triggering new cars calculation, then hostBinding may help:
#hostListener(`hover`) recalculateCars() {
this.calculateAvailableCars()
}
And finally, (because you describe your use case quite cryptically, without many details, thus I'm scratching all possible scenarios) if some external component action shall trigger re-calculation, you can hook to ngLifecycle ngOnChanges() if for example external input property change shall re-trigger cars calculation.
In other words and summing all that up, it depends who and from where triggers changes, that shall re-trigger available cars recalculation.
And very important, see an answer from #chiril.sarajiu, because what we are trying to work around here can be handled automatically by single observable. This requires additional setup (service, provide observable to components, e.c.t.) but it's worth.
--- EDIT ---
If each variable change shall retrigger data
As OP clarified, that changes are related with model bound to component. So another option with mentioned by #marvstar is using set, where each model variable change will retrigger fetching function:
modelSchangeSubject: Subject<Model> = new Subject<Model>();
ngOnInitt() {
this.modelSchangeSubject
.subscribe((v: Model) => {
this.calculateAvailableCars()
})
}
/* Rest of controller code */
set modelBounded(v: Model) {
this.modelSchangeSubject.next(v);
}
You need RxJS. What you do is you create a data service, which will store an Observable (in my case a BehaviorSubject, which is mostly the same, but in my case I start with a value).
export class DataService {
private dataStorage$ = new BehaviorSubject(null); //here is the data you start with
get getDataStorage() {
return this.dataStorage$.asObservable(); // so you won't be able to change it outside the service
}
set setDataStorage(data: any) {
this.dataStorage$.next(data);
}
}
Then you subscribe to this data changes everywhere you need to:
constructor(private dataService: DataService){}
ngOnInit() {
this.dataService.getDataStorage.subscribe((data) => this.calculateAvailableCars(data));
}
calculateAvailableCars(){
this.carFactory.available.forEach(function(item){
this.availableCars.push(car.id);
}.bind(this));
}
Read more about best practices of using RxJS in Angular, as there can be quite a bit of pitfalls and problems.
Try using setter and getter.
private _YourVariable:any;
public set YourVariable(value:any){
this._YourVariable = value;
//do your logik stuff here like. calculateAvailableCars
}
public get YourVariable():any{
return this._YourVariable ;
}
I have a service that has a public property declared as follows:
public indiceUsuarios: IndiceUsuario[] = [];
After changing data and doing operations I reload that property with the following method in the same class:
private reloadIndiceLocal(indiceUsuarios: IndiceUsuario[]): void {
let self = this;
self.indiceUsuarios.length = 0;
self.indiceUsuarios.push(...indiceUsuarios);
}
I'm injecting this service into another component and referencing that property on the ngOnInit() using the following line this.indiceUsuarios = this.sesionService.indiceUsuarios; (this local property is not initialized before this). I'm then using this local property on this component's view. It shows perfectly fine and is changing when I add elements to the array.
The issue is that if I remove elements from the array they will keep showing (the view is not updating to reflect these changes).
Any ideas?
Well, In my case it turned out to be that I was relying on a singleton that was poorly written and wasn't acting as a singleton (effectively linking my view to an object with a different instance to the one I was changing). My bad.
Currently, I am using angular 4 for my school project. I have an array, each item is a child component which can be updated and deleted, which means I should know the index and the data.
parent.ts:
updOne(i:number,stc:string):void{
this.myarray[i]=stc
}
delete(edu:string):void{
this.myarray=this.myarray.filter(x=>x!==edu)
}
parent.html:
<child-com [edu]=x [num]=i (updstr)="updOne($event)" (delstr)="delete($event)"></child-com>
child-com.ts:
#Input() edu:string
#Input() num:number
#Output() updstr: EventEmitter<string> = new EventEmitter<string>()
#Output() delstr: EventEmitter<string> = new EventEmitter<string>()
//some other code here
save():void{
this.updstr.emit(this.edu)
this.updating=false
}
del():void{
this.delstr.emit(this.edu)
}
delete works well, without a doubt. The problem is updating. Actually, using *ngFor, trackBy, and printing it all manually, this problem can be solved. But I wanna try using child component, as in React. When I play around with react, I can just use javascript closure, i.e. myfunc.bind(this,i,stc).
I've tried using bind here, no results
code when using bind:
parent.ts:
#Output() updstr: EventEmitter<number,string> = new EventEmitter<number,string>()
parent.html:
//I've tried some order
//this,i,$event
//$event,this,i
<child-com [edu]=x (updstr)="updOne.bind(this,$event,i)" (delstr)="delete($event)"></child-com>
And generics in typescript doesn't allow multiple data, so I cant emit more than one data
So my question is, how can I pass some data at once from child to parent, using emit or bind?
Thanks to Alex, using an object can substitute multiple data passing. Just to make sure that the data is correct, an interface is used, kind of like this
export interface Interview{
num:number
payload:{
dt:string
seeker:string
}
}
and used it like
#Output() updstr: EventEmitter<Interview> = new EventEmitter<Interview>()
I have experience with Angular/Ember/React and in every of these frameworks you have a clear notion of component that accepts argument as initial data and callbacks as reference to the parent (I believe in Ember its called "Data down, action up"). I tried to learn ExtJS but i dont see what should i use as equivalent of this interface, how communication between nested components should look like?
The common pattern in Ext for passing initial data is through an object parameter. The properties of the passed object correspond to a config object in the class definition.
Ext automatically creates getter and setter methods for all properties in the config object, as well as apply and update methods for transforming values before and after they're set.
For communication between components, you can wire up listeners that handle events fired from the nested component. The events can be standard provided by Ext (such as 'click' fired from an Ext.button.Button) or custom ones you make up and send using fireEvent.
Another way of communicating between components is to use the parent's view model and the component's bind config.
View models come with the benefit of being visible by all descendant components, so child components can bind to a view model property, which if not found on the child component itself, will be propagated up the chain of ancestors until a matching view model property is found.
Initial data
• In Ext JS every component accepts a JSON object as its initial config data.
• You can even set the configs at initComponent method associated with every component you create. The initComponent template method is an important initialization step for a Component. The initComponent method of the class being created is called first, with each initComponent method up until the hierarchy to Ext.Component being called thereafter. This makes it easy to implement and, if needed, override the constructor logic of the Component at any step in the hierarchy.
Ext.create('Ext.Component', {
html: 'Hello world!',
width: 300,
height: 200,
padding: 20,
style: {
color: '#FFFFFF',
backgroundColor:'#000000'
},
renderTo: Ext.getBody()
});
Data Communication between components.
• In Ext JS communication between components is via event (listeners and fireEvent with listen config).
• Communication can be also between child and parent ViewModels. The most commonly used aspect of a ViewModel is the bind method. This method takes a "bind descriptor" and a callback to call when the data indicated by the bind descriptor either becomes available or changes.
Every component can be associated with ViewModel and Controller. These are the most commonly used once.
I'm new to Flux as a whole, but I'm trying to get a grip on it by starting with Reflux, which seems a bit more opinionated and simpler to learn.
As I understand, Reflux stores have a trigger method which indicates the store's data has changed, and they pass the updated data into it. This data can then be set as a React component's state, (or as one of the state's properties) using the Reflux.connect mixin or similar methods.
But what if a store has multiple sets of data that need to be listened to separately? Let's say I'm modifying the TodoMVC RefluxJS example, and I wanted the TodoStore to also include a title property that indicated the name of the todo list (as well as the list, the list of TODO items). Lets say there is also a <Title> component that is listening for changes to the title property, and setting the title as its state when it does.
A call to this.trigger(title) would update the title component, but would also cause the todo component to try to use the title string as its state, so we need a way to indicate which data has been changed. Should these two properties (title and list) be separated into different stores? Or should all calls to trigger include a string that indicates the property: this.trigger("title", this.title) or this.trigger("todos", this.list). Or should all the data be combined into one object which is then picked by the listeners (e.g. using Reflux.connectFilter)?
this.trigger("todos", {
todos: this.list,
title: this.title
});
These last two examples introduce new data to the this.trigger() call, meaning that Reflux.connect can't be used any more, because connect takes the data returned from a store and directly sets the components state to it. Does this mean we have to use Reflux.listenTo(TodoStore,"onTodoChange"), and then filter out the trigger calls that aren't relevant to this component?
(1) Its very important stores broadcast data change event to the subscribed top level view components.(The so-called controller views, as explained in http://facebook.github.io/flux/docs/overview.html).
(2) The re-usable components, such as List, Title etc,etc. are self complete, these components should not understand store data structure. Use properties instead of setState for display data.
(3) Do you really want the store to hold different type of data, or does the data belong to a different store.
(4) If the store must hold different type of data, my preference is not to "filter" by action type. Update all the view components listening to the store for simplicity.