How to extract data from Behaviour Subject in Angular service? - javascript

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

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.

What is the proper way for binding data to cascade dropdown / autocomplete lists in Angular?

I use mat-autocomplete in several places in my project and fille them as shwon below on form loading:
countries: CountryDto[] = [];
cities: CityDto[] = [];
getCountries() {
this.demoService.getCountries().subscribe((list: CountryDto) => {
this.countries = list;
});
}
getCities(countryId) {
this.demoService.getCities(countryId).subscribe((list: CityDto) => {
this.cities= list;
});
}
However, when I tried to use a second list as cascade, I cannot make the second one to fill according to the first selection. Actually I pass the countryId and then retrieve the city list without any problem. But, I cannot refresh the list of cities. I also tried to use changeDetectorRef.detectChanges() and ngZone.run(), but it does not make any sense. I am not sure if the problem is related to subscribe section, and for this reason I am too confused after trying lots of different tgings to fix the problem. So, first I need to start with a proper approach that is suitable for cascade binding. How can apply a proper way?
You are mixing imperative and reactive programming by fetching the data and then assigning the parsed response to a variable. Angular can handle observable values just fine without ever having to subscribe to anything, by instead using the async pipe.
There's nothing wrong with using [(ngModel)] to bind values and to handle changes, but as a rule of thumb, when using Observables to populate the form based on selected values, I like to use only reactive programming with reactive forms instead of model-bindings.
A simple reactive form for you in this case could look something like
citiesForm = new FormGroup({
state: new FormControl(),
city: new FormControl()
});
with the corresponding HTML
<div [formGroup]="citiesForm">
<select formControlName="state" >
.....
This makes it very easy to use the valueChanges event on either the FormGroup or it's individual FormControls.
To demonstrate with a working example, we can replace everything with observable values and a reactive form.
First, we declare our observables that will populate the form.
states$: Observable<Array<any>>;
cities$: Observable<Array<any>>;
We initiliaze the observables in the ngOnInit function (note the valueChanges).
ngOnInit() {
this.states$ = of(this.states);
this.cities$ = combineLatest(
this.citiesForm.get("state").valueChanges, // Will trigger when state changes
of(this.cities)
).pipe(
map(([selectedState, cities]) =>
cities.filter(c => c.state === +selectedState) //SelectedState is a string
)
);
}
and lastly bind everything to our form (note the async pipe and the form bindings to our reactive form)
<select formControlName="state" >
<option value="">Select State</option>
<option *ngFor="let s of states$ | async" [value]="s.id">{{s.name}}</option>
</select>
Since you mentioned manual triggering of change detection using ChangeDetectorRef in your question, notice that my proposed answer uses the OnPush change detection strategy, and there's no need to think about change detection since the async pipe will know that emission of new values in the observables will affect the template and render it properly.
Simplifly the Daniel's idea, you can defined a observable like
cities$=this.citiesForm.get('state').valueChange.pipe(
switchMap(countryId=>this.demoService.getCities(countryId),
tap(_=>this.citiesForm.get('city').setValue(null)
)
And use
<options *ngFor="let s of cities$ | async">
Usig ngModel is equal
<select [ngModel]="state" (ngModelChange)="stateChange($event)" >
...
</select>
stateChange(countryId)
{
this.state=state;
this.cities$=this.demoService.getCities(countryId).pipe(
tap(_=>this.city=null
)
}
See that the idea is return an observable and use pipe async in the .html. The operator "tap" make that, when you subscribe -remember that the pipe async is a way to subscribe- the city becomes null

Transforming an Array of Observables to an Array

I'm using forkJoin to turn several observables into one which I then map over to transform into some view model that I use for my view in my component.
My model is as follows:
export interface MyModel {
itemOne: Observable<ItemOne[]>;
itemTwo: Observable<ItemTwo[]>;
itemThree: Observable<ItemThree[]>;
}
My forkJoin looks like:
this._subscriptions.push(this.myService.getData()
.flatMap(data => Observable.forkJoin(data
.map(data => {
let myModel: MyModel = {
itemOne: this.myService.getSomeData(),
itemTwo: this.myService.getSomeData(),
itemThree: this.myService.getSomeData()
};
return Observable.forkJoin(Observable.from([myModel]));
}))).subscribe(details => {
details.map(this.toMyModelViewModel.bind(this));
}));
Now, in my toMyModelViewModel method, I'd like to pick each value from these Observables and was wondering how I could accomplish this?
toMyModelViewModel(value: MyModel): any {
return {
itemOne: value.itemOne,
itemTwo: value.itemTwo,
itemThree: value.itemThree
};
}
Do I have to subscribe to each value to get the current value or is there another, possible better/cleaner way of doing this? Ultimately, I'd like to have an array of MyModel objects that way I don't have to worry about using data | async in my view.
Thanks
From a quick scan of your code, it seems that you have an initial api call (this.myService.getData()) from which you will construct your result, but that result is based on combining the results of several api calls (this.myService.getSomeData()).
I feel like you need the MyModel interface to have no Observables:
export interface MyModel {
itemOne: ItemOne[];
itemTwo: ItemTwo[];
itemThree: ItemThree[];
}
... toMyViewModel is a factory method:
function toMyModelViewModel(itemOne: any, itemTwo: any, itemThree: any): MyModel {
return {
itemOne: itemOne as ItemOne[],
itemTwo: itemTwo as ItemTwo[],
itemThree: itemThree as ItemThree[]
} as MyModel;
}
... then you'd use switchMap/forkJoin as follows:
this.myService.getData().switchMap(data => {
return Observable.forkJoin(
this.myService.getSomeData(),
this.myService.getSomeData(),
this.myService.getSomeData(),
toMyModelViewModel
}).subscribe(...);
You can convert the array of observables to an observable of an array, using whichever method you like -- the one that Miller shows in his answer will work. I personally like the combineLatest trick (quoting from redent84's answer to this question: How do I create an observable of an array from an array of observables?).
let arrayOfObservables: [Observable<E>] = ...
let wholeSequence: Observable<[E]> = Observable.combineLatest(arrayOfObservables)
Now, for your real question:
Do I have to subscribe to each value to get the current value or is there another, possible better/cleaner way of doing this? Ultimately, I'd like to have an array of MyModel objects that way I don't have to worry about using data | async in my view.
Unfortunately, the only way to get an Array<any> out of an Observable<Array<any>> is to subscribe to that observable and save the inner value to an array defined elsewhere, or (if you're using Angular as you are), to use the async pipe.
You can think of it this way: once an Observable, always an Observable. If the value is wrapped up in an Observable, you cannot get it out. The closest thing to this behavior might be using BehaviorSubject with the getValue method, but it's definitely not an option for Observables. This is a good thing because it prevents you from trying to use an asynchronous value "synchronously" in your program.
I also feel like I should mention that the async pipe is a really useful utility and should be preferred to manually subscribing in general. When you manually create a subscription in your component class, you will need to remember to save that subscription and clean it up in the ngOnDestroy lifecycle hook. The async pipe is great because it does all of this cleanup for you. Plus, you can use it with the safe navigation operator to handle values that are possible undefined.
<ul>
<li *ngFor="let obj of (possiblyUndefined$ | async)?.objects">
{{ obj.title }}
</li>
</ul>
That code would be a lot more complicated if you wrote it all in your component class.
To summarize:
1) You can use Observable.combineLatest(arrayOfObservables) to squish your array of observables into an observable of an array.
2) You cannot get an array "out of" an observable. You must either subscribe to that observable or use the async pipe.

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.

Not able to get dynamic translation of text using ngx translate/core - angular 2 typescript

Problem :
I have dynamic text that is coming from Json file.
I am using translate.get() method like this:
this.translate.get('keyInJson').subscribe(res => {
this.valueFromJson = res;
/*
creating an object using above text
*/
});
As this is asynchronous I am not able to get the translated text when the page renders.
I tried wrapping above method inside Observables , Promises but it's not able to get translated version of text during page load.
I was able to get the translated text after trying different approaches but code became too complex and not reliable.
Expected/desired behavior
Should load translated version of text
Reproduction of the problem
Dynamically generate the text instead of hardcoding it on html and then try to render translated version.
Environment
Angular2 , Typescript, Ionic 2
#nkadu1
instant(key: string|Array, interpolateParams?: Object): string|Object: Gets the instant translated value of a key (or an array of keys). This method is synchronous and the default file loader is asynchronous. You are responsible for knowing when your translations have been loaded and it is safe to use this method.
const translated = this.translate.instant('keyInJson');
#masterach TranslateHttpLoader is what you're looking for. Here's an article which might be helpful for you.
I been using #ngx-translate from a while right now. I use the module in two ways:
Using the pipe:
{{'code_to_translate' | translate }}
Using the service
const translateText: string =
this.translateService.instant('code_to_translate')
the translate service should be passed in the constructor of you component (or service )
usually I declare the string result in my app.ini function before load the components and use only one translation service for my entire application.
I also declare my custom TranslateLoader to manage the source of translations from any place (external api, json file, etc)
Why don't you use the pipe in html instead of translating in ts?
<div>{{ 'HELLO' | translate:param }}</div>
or
<div [innerHTML]="'HELLO' | translate"></div>
In your global service:
private _valueFromJson: string;
constructor(private translate: TranslateService) {
translate.get('keyInJson').subscribe(res => {
this._valueFromJson = res;
});
}
get valueFromJson() {
return this._valueFromJson;
}
Or:
public valueFromJson: string;
constructor(private translate: TranslateService) {
translate.get('keyInJson').subscribe(res => {
this.valueFromJson = res;
});
}
Then, in your component template you can bind directly:
<div>{{ globalService.valueFromJson }}</div>
Alternatively, you can use a synchronous method:
this.valueFromJson = translate.instant('keyInJson');

Categories