I have this situation:
<app-component-test [dataSource]="dataSource"></app-component-test>
dataSource is a service inited in component's constructor. Now I change this service property by:
this.dataSource.list = [1, 2, 3];
but app-component-test doesn't have any changes. I need to have in component this service with the updated list.
Rather than passing through the service, instead try passing through the list on it's own.
#Input() list: number[];
<app-component-test [list]="dataSource.list"></app-component-test>
Even better, if your list comes from an Observable, you can just pass the observable through with the async pipe:
#Input() list: number[];
<app-component-test [list]="list$ | async"></app-component-test>
You can choose between
Send in the list rather than the service.
Inject the service in the app-component-test constructor and get the list inside the app-component-test component instead.
There is already some similar questions on this.
You can check this answer it is about variables not service, but it is in the same principle.
Click
Bascaly you make a local equivalent of your input object and modify it as you wish.
Related
I am trying to build a student-teacher result view portal in which teachers can login and see all of the students result and add to that list or edit that list. Students can login to search using their credentials and see their own result.
I have a list of students (Array). I want to use that list in my student-view to search the list and return the student with the matching credentials. I want to use that list to provide the teachers with the CRUD functionality for complete list.
I could not figure out the correct way to get access to the list of students in different parts of my project.
I tried using #Input and Service but I couldnt do it in the correct way and I am getting empty array in both of the methods.
What is the correct way to acheive this? I have data in sibling component. Should I store data in parent component? Please help me find the correct way to do this.
This is the component where I have the data, students component. You can also see the component and project structure in this photo. Currently I am trying to transfer data between siblings using service and failed. I am setting the data in constructor using :
this.studentTransferData.setData(this.students)
I am trying to get data in my StudentView Component using :
export class StudentViewComponent implements OnInit {
name: string = ""
rollno: number = 0
studentList = this.studentDataTransferService.getData();
#Input() students: Student[] = []
#Output() studentSearch: EventEmitter<Student> = new EventEmitter();
constructor(private studentDataTransferService: StudentDataTransferService) {
console.log(this.students)
console.log(this.studentList)
}
It consoles empty array.
How to get the data in my different components. I have it in students component.
Thanks.
Edit: I tried doing inside on ngInit as suggested in different post and it does not work as expected and I want it in complete project (siblings and parent to child) That is not what I want at the moment.
When you need to share data between several components, you usually use a service.
To be truly reactive, you should also use what are called proxies : those are RxJS elements that can act both as observables and observers.
private _students = new BehaviorSubject<Student[]>([]);
public students$ = this._students.asObservable();
get students() { return this._students.value; }
loadStudents() {
this.http.get<Student[]>(...).subscribe(students => this._students.next(students));
}
Then in other components
students$ = this.service.students$;
constructor(private service: StudentsService) {}
<ng-container *ngFor="let student of students$ | async">
...
</ng-container>
EDIT : using it in services
Yes, you can use it in services. If you need to make an HTTP call at some point, this is the best solution :
constructor(private service: StudentsService) {}
rewardStudent() {
const bestStudent = this.service.students[0];
this.http.post(...).subscribe();
}
Otherwise, you have access to students$ the same way you do with components.
You can create and store the student list inside a student service class and also create a public function: getStudentsList inside your service and then call it from your different views.
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.
The objective behind using BehaviouSubject was to use a single API call and pass the same data to multiple components in the same route.
I am able to do that. I am not able to filter the received data
Heres a stackblitz fiddle that i have created
https://stackblitz.com/edit/angular-xtne5y
In one component, i am displaying the table, in the other, i need to extract some info out of it based on the individual object key values. Like how many todos are complete / incomplete.
Since I am required to use the async pipe everywhere in the template, performing operations like filter are not possible.
Is there a better way to implement this?
I need to keep the data extracted as reusable
You're currently using the async pipe. To get the desired result, you can use (or chain) another custom pipe with your data to fetch specific properties.
I've forked your stackblitz example and modified the code with the solution.
Here's my working solution.
Essentially, all you need to do is use a custom pipe.
{{ todos$ | async | myCustomFilter }}
In my example (stackblitz), I'm doing:
<p>
No. of Completed Todos: {{ (todos$ | async | filterByCondition: {property: 'completed', value: true}).length }}
</p>
<p>
No. of Incomplete Todos: {{ (todos$ | async | filterByCondition: {property: 'completed', value: false}).length }}
</p>
Edit after your comments
There are two approaches to your desired result:
1) Use a custom pipe. You can parameterize the pipe the same as I've done or even create your own conditions and evaluate by passing a parameter to your pipe (as I've done in the example for the args property).
I.e
<div>{{$todos | async | filterTodo: 'byDate'}}</div>
Now, you can put the handler in your pipe filterTodo for this byDate value.
2) Use different observables for different data.
class MyComponent {
todos$;
completedTodos$;
constructor() {
this.todos$ = this.someService.getTodos(); // from behavior subject
this.completedTodos$ = this.todos$.pipe(
filter((item) => {
// your filter code here
})
)
}
}
So I've have worked on projects where when we create TODO's section, My approach would be when you subscribe to the URL to fetch the data, create a Redux Store
(Here's [a link] https://medium.com/supercharges-mobile-product-guide/angular-redux-the-lesson-weve-learned-for-you-93bc94391958), which has an interface like
interface ProductTodos {
product: {title:string,iscompleted:boolean,id:string/number},
todos?: any[]
}
When you fetch your Todos, you will dispatch an action like "PUSH" and every TODO will be an object of type product and will appended on to the todos array in the interface.
when you create an instance of the redux Store in the Component Class you will loop through the "todos" array and check for the id and isCompleted flag.
Based on this you can loop through each object and fill your tables based on their completed status.
Here is a link to my ReduxDemo in Angular 4, check it out https://github.com/nerdySingh/ReduxDemoAngular
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 ;
}
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>()