Angular 10+ checkbox filtering - javascript

I'm trying to implement multiple checkbox filtering using Angular but can't seem to be able to figure out how to proceed exactly. I've looked over multiple similar questions here, but failed to figure out how to use any of the answers.
Ideally, I'd like to filter my data using event listeners.
My main two issues are:
Figuring out how to actually filter things, I can't figure out what the right approach would be for my goal
Actually displaying updated data
Stackblitz
Any push in the right direction would be greatly appreciated!

Basically, you need to share the data(filters) between two components because filtered-users and filters are two components in your project.
so, to share data between two components (which are not having parent-child relationship) we can use observables.
You can create a service called FilterService and in that, you can have an observable (filters)
#Injectable()
export class FilterService {
private filters = new Subject<{}>(); // creating a subject
filters$ = this.filters.asObservable(); // creating an observable
alertFilter(key: string, value: string) {
this.filters.next({ key, value }); // publishing the new fliter to the subscribers
}
}
and add this service to filtered-users, filters components through dependency-injection. and call this alertFilter() method of FilterService from filters component whenever the user checks the filter checkbox.
in filter.component.html
<input (change)="onCheck('gender', opt)"
in filter.component.ts
onCheck(key: string, value: string) {
this.filterService.alertFilter(key, value);
}
after this, subscribe to the observable(filters) of FilterService in filtered-users-component.
in filtered-users.component.ts
constructor(
private sortingService: SortingService,
private userService: UserService,
private filterService: FilterService
) {
this.filterService.filters$.subscribe({
next: filter => {
this.filteredUsers = this.filteredUsers.filter(user => {
return user[filter['key']] === filter['value'];
});
}
});
}
this.filterService.filters$.subscribe() will execute whenever a new filter has been added so, using filter variable you can filter the users accordingly.

Related

How to access data in different components in Angular?

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.

Angular - recalculate a variable on every change

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

Angular2/4 - Creating an array of data to share across multiple components via service

Overview:
I have a UI that allows a user to select one or more employees based on various search criteria. When they select them, I need to store the selected employees in an array, within my shared service.
Before any of this data is sent to the server, the array could be modified by adding more employees or removing some that exist in the array.
I need to be able to create and subscribe to an array of data in this shared service.
My Approach:
My initial approach was to use a BehaviorSubject so that I could call next and pass the data along when needed. This became an issue though because I didn't have a way to see all of the stored/selected users, only the last one that was passed through the BehaviorSubject.
Psuedo Code:
shared.service.ts
public selectedUsers = []; //<- How do I store stuff in here?
private selectedUsersSub = new BehaviorSubject<any>(null);
selectedUsers$ = this.selectedUsersSub.asObservable();
setSelectedUsers(data) {
this.selectedUsersSub.next(data);
}
get selectedUsers(){
return this.selectedUsers;
}
component.ts:
this._reqService.selectedUsers$.subscribe(
data => {
if (data) {
console.log('Observable Stream', data)
}
}
)
My goal here is to be able to store my selected employees in this selectedUsers array. My other components need to be able to subscribe so that they are always up-to-date with the current value of selectedUsers.
I also need to be able to access the current array of selected users at any time, not just the last value.
Delete public selectedUsers = [];
delete get selectedUsers(){
return this.selectedUsers;
}
And in any component you want to fetch the selectedUsers just subscribe to the public observable selectedUsers$
in a component
this.subscription = this.yourService.selectedUser$.subscribe((users)=>//do stuff here like push theusersto the users array of the component)
The service needs to be inject to a shared module in order all the components to get the same state (data).
More details: https://angular.io/guide/component-interaction#parent-and-children-communicate-via-a-service
Your approach is wrong here. You have 2 basic options in a shared service pattern. 1 is to use a store pattern where you have a predefined set of data manipulations and use the scan operator, this is more complex, the simpler is to pass the entire list every time you want to update the list.
So your components will not only send the update, they'll first get the entire list and then manipulate and then send it.

How to pass multiple data back from child to parent component in angular?

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>()

How to create an array from rxjs without completion of the adding sequence

I'm trying to figure out an rxjs way of doing the following:
You have two observables, one onAddObs and onRemoveObs.
let's say onAddObs.next() fires a few times, adding "A", "B", "C".
I would like to then get ["A", "B", "C"].
.toArray requires the observable be completed...yet more could come.
That's the first part. The second part is probably obvious...
I want onRemoveObs to then remove from the final resulting array.
I don't have a plunkr cuz I can't get anything close to doing this...
Thanks in advance!
UPDATE
Based on user3743222's advice, I checked out .scan, which did the job!
If anyone else has trouble with this, I've included an angular2 service which shows a nice way of doing this. The trick is to use .scan and instead of streams of what was added/removed, have streams of functions to add/remove, so you can call them from scan and pass the state.
#Injectable()
export class MyService {
public items: Observable<any>;
private operationStream: Subject<any>;
constructor() {
this.operationStream = Subject.create();
this.items = this.operationStream
// This may look strange, but if you don't start with a function, scan will not run....so we seed it with an operation that does nothing.
.startWith(items => items)
// For every operation that comes down the line, invoke it and pass it the state, and get the new state.
.scan((state, operation:Function) => operation(state), [])
.publishReplay(1).refCount();
this.items.subscribe(x => {
console.log('ITEMS CHANGED TO:', x);
})
}
public add(itemToAdd) {
// create a function which takes state as param, returns new state with itemToAdd appended
let fn = items => items.concat(itemToAdd);
this.operationStream.next(fn);
}
public remove(itemToRemove) {
// create a function which takes state as param, returns new array with itemToRemove filtered out
let fn = items => items.filter(item => item !== itemToRemove);
this.operationStream.next(fn);
}
}
You can refer to the SO question here : How to manage state without using Subject or imperative manipulation in a simple RxJS example?. It deals with the same issue as yours, i.e. two streams to perform operations on an object.
One technique among other, is to use the scan operator and a stream of operations which operates on the state kept in the scan but anyways go have a look at the links, it is very formative. This should allow you to make up some code. If that code does not work the way you want, you can come back and ask a question again here with your sample code.

Categories