I have a Behavior Subject dataService in Angular that makes a request to server to get the user details when admin-layout-component loads (parent component), and then injects the data in the user component, this is my user component onInit:
ngOnInit(){
this._dataService.currentUserDetails.subscribe(userDetails => this.userDetails = userDetails);
console.log(this.userDetails);
if(this.userDetails === ''){
this.getUser();
}
}
When i refresh the browser in user component, first loads the current component ngOnInit, i have to make the conditional to check userDetails and make a new request to server, but admin-layout-component is making the request too, my question: There is a way to await the dataService so i don't have to make the getUser() twice?. Thanks in advance.
You can use Subject to store information about user and if you need, you always get data from subject by this.userDetails anywhere in you component:
export class Component implements OnInit {
userDetails$ = new BehaviorSubject<any>(null);
ngOnInit(){
this._dataService.currentUserDetails.subscribe(userDetails => {
this.userDetails.next(userDetails)
});
}
get userDetails(): any {
return this.userDetails$.getValue();
}
}
and in template you can subscribe to your service Subject
<div> {{ userDetails$ | async }} </div>
Related
I have component A which is navbar and component B which is list view
Navbar has dropdown where all the users are listed, onInit method of component B all the data is loaded in the Component B Table that selected user is in local storage, now i want when the user changes the users from dropdown then that users data should automatically be refreshed in the Component B selected users are saved in localstorage. Is there anyway i can call method of second component from first component.
Navbar Component
<select class="form-control"(change)="selectUsers($event)" [(ngModel)]="UserId">
<option *ngFor="let opt of userlist" value={{opt.UserId}}>{{opt?.userName}}
</option>
</select>
selectUsers(event) {
this.Service.SelectedUserId = event.target.value;
}
In Service File
set SelectedUserId(value: string) {
if (value) localStorage.setItem('ActiveUserId', value);
}
Component B:
fetch() {
this.Userslist = [];
this.param.userId = localStorage.getItem('ActiveUserId');
this.service.getUsers(this.param).subscribe((data => {
this.Userslist = data.response;
}
}
In View:
<tbody>
<tr *ngFor="let data of Userslist | paginate:{itemsPerPage: 10, currentPage:p} ; let i=index"
..
..
</tbody>
Any solution to resolve this issue, Thanks
setup a common service which is shared by the two components. Lets assume its called testService.
so service will be like.
export class testService {
testEmitter: Subject<void> = new Subject<void>()
testObserver$: Observable<void>;
constructor () {
this.testObserver$ = this.testEmitter.asObservable();
}
}
Now the power of this pattern is that we can listen for next of the subject from any component in the application and perform action!
so in the navbar we start the next which will trigger action on all the observables!
selectUsers(event) {
this.Service.SelectedUserId = event.target.value;
this.testService.next();
}
So we subscribe on the component where the refresh should happen, and then reload the grid.
child component.
export class ChildComponent implements OnInit, OnDestroy {
subscription: Subscription = new Subscription();
ngOnInit() {
this.subscription.add(
this.testService.testObserver$.subscribe(() => {
this.fetch();
})
);
}
ngOnDestroy() {
this.subscription.unsubscribe();
}
}
You can also emit objects like {eventType: 'test', data: {}} inside the next and then add multiple if conditions to validate the eventType and perform multiple actions on the same subject!
My frontend application contains the ApiClient class which hides details about http communication with my server.
That's simple TypeScript class with an axios client as private field.
I faced with doubt about initializing client at root component and passing it to some children.
At this moment I initiate my client in the root component as simple js field from constructor:
constructor() {
super()
... // init state here
... // some initializations like this.handler = this.handler.bind(this)
this.apiClient = new ApiClient()
}
Some children components depend on apiClient too (e.g. login component should send request to the login endpoint, editModal component sends request for updating entity by id).
Now I'm passing apiClient as props:
<Login show={this.state.show}
handleModalClose={this.handleModalClose}
handleSuccessfulLogin={this.handleSuccessfulLogin}
httpClient={this.apiClient}
/> }
...
<EditModal
httpClient={this.apiClient}
...
/>
What is the idiomatic way for passing it to the component? Is it correct to pass the client as props?
If I understand react documentation correctly, props and state are used for rendering, and that's a bit confusing for me
If your api client doesn't depend on any props/state from the components, the best way is to initialise it separately and then just import in the file where you need to use it:
// apiClient.js
export const apiClient = new ApiClient();
// component.js
import {apiClient} from '../apiClient';
If you need to handle login/logout inside component, which sets the token inside the api client, you can add login and logout methods, which would be called after the client is initialised. Since you have only one instance of the client inside your app, these changes (login and logout) will have effect inside all the components that use the client:
// Client.js
class ApiClient {
constructor() {
// do intance init stuff if needed
this.token = null;
}
login(token) {
this.token = token;
}
logout() {
this.token = null;
}
}
// apiClient.js
export const apiClient = new ApiClient();
// component.js
import { apiClient } from '../apiClient';
const LoginPage = props => {
const handleLogin = () => {
const token = // get the token
apiClient.login(token);
}
}
// anotherComponent.js
const User = props => {
useEffect(() => {
apiClient.getUser()
}, [])
}
I've developed a component with two views. Component A has a contact form, and Component B is the "Thank you" page.
Component A:
You fill the form and submit it.
As soon as the response arrives, a new ReplaySubject value is created.
The user will be routed to the component B.
Component B:
The component is initialized.
The component gets the value from the subject.
The view is rendered and displays the thank you message.
HTTP Call response (Returned after a successful Form data post request):
{
"status": 200,
"body": {
"message": "We have received your request with the card information below and it will take 48h to be processed. Thank you!",
"card": {
"name": "John Doe",
"email": "john.doe#gmail.com",
"accountNumber": "12345-67890"
}
},
"type": "ItemCreated"
}
Component A (Form) code:
import { Component } from '#angular/core';
import { FormBuilder, Validators } from '#angular/forms';
import { RequestCardWidgetService } from './request-card-widget.service';
import { RouterService } from '#framework/foundation/core';
import { Item } from '#pt/request-card-data'
#Component({
selector: 'pt-request-card-form',
templateUrl: './request-card-form.template.html',
providers: [RouterService]
})
export class RequestCardFormComponent {
constructor(private fb: FormBuilder, private data: RequestCardWidgetService, private router: RouterService){}
item: Item = {
name: '',
email: '',
accountNumber: ''
};
requestCardForm = this.fb.group({
name: ['', Validators.required],
email: ['', Validators.email],
accountNumber: ['', Validators.required]
})
onSubmit() {
this.item = this.requestCardForm.value;
this.data.requestCard(this.item)
.subscribe(data => {
this.data.processResult(data);
this.router.navigate(['/success']);
});
}
}
Component B (Thank you page) code:
import { Component } from '#angular/core';
import { RequestCardWidgetService } from './request-card-widget.service';
#Component({
selector: 'pt-request-card-success',
templateUrl: './request-card-success.template.html'
})
export class RequestCardSuccessComponent {
messages: any; // TODO: To use the proper type...
constructor( private requestCardService: RequestCardWidgetService) {
this.messages = this.requestCardService.message;
}
}
Component B Template (Thank you page):
<div *ngIf='(messages | async) as msg'>
{{ msg.message}}
</div>
Component Service code:
import { Injectable } from '#angular/core';
import { HttpResponse } from '#angular/common/http';
import { Observable, ReplaySubject } from 'rxjs';
import { map, take } from 'rxjs/operators';
import {
RequestCardDataService,
Item,
ItemCreated
} from '#example/request-card-data';
#Injectable()
export class RequestCardWidgetService {
constructor(private dataService: RequestCardDataService) { }
private readonly results = new ReplaySubject<ItemCreated>();
readonly message: Observable<ItemCreated> = this.results; // Message Line. This is the variable that I'm rendering in the template. Is this the correct way of extracting subject values?
requestCard (card: Item): Observable<ItemCreated> {
return this.dataService.postCardRecord(card).pipe(
take(1),
map((response: HttpResponse<ItemCreated>): ItemCreated | {} => {
return response.body
? response.body
: {};
})
);
}
processResult(data: ItemCreated) {
this.results.next(data);
}
}
Recap:
Component A has a form. After you submit the form, the results are stored as a new value in the subject. The user is routed to the thank you page.
The thank you page component renders the element and gets the newest value from the subject. Then it renders the contents.
This code works, but I do have some questions.
Question:
Is this the proper way of using the Subject?
Is this:
readonly message: Observable<ItemCreated> = this.results;
the proper way of extracting the values from a subject? (I'm passing 'message' to the view.)
Are there better ways to achieve the same result?
Thanks a lot in advance.
Is this the right way to use a subject?
ReplaySubect unconstrained will replay all of the values it has previously emitted to new subscribers. This could lead to situations where a user could receive previously emitted messages until they finally receive the current message. Therefore, either constrain the subject or consider using a BehaviorSubject instead.
Extracting values from the subject
A Subject and all of its derivatives are both Observables and Observers. When providing a subject to a consumer, you do not want to expose the Observer interface, i.e., a consumer should never be able to call next, error or complete. Thus, as suggested in a comment, you should ensure you are only exposing the Observable interface to consumers by first calling the asObservable method.
readonly message: Observable<ItemCreated> = this.results.asObservable();
Next Steps
If you want to continue using service-based communication between components, then I think you have opportunities to clean/refine your code per the docs linked in the question comments.
If your application is going to grow in complexity, I would steer you down the Redux-style architecture and look into NgRx and specifically, the use of effects to manage side effects.
Effects can meet all of your requirements with simple, discreet observable constructs, i.e., an effect to handle the form submission, receive the response, even navigate to the success page. More information about effects can be found here.
A redux architecture can be overkill for simple tasks, but if you're working on a large app managing a large state tree, I prefer this approach over service-based integrations.
I have an angular 6 application, which has a top bar and a content area below this. These are different component and I am currently developing the user profile page. Name of the user is also displayed in the top bar.
My problem is like whenever I have updated the user's name in EditUser page, it successfully saves, but the same is not updated on the top bar. In Vue.js, I can simply handle this with a Vuex store; but how can I handle this in Angular 6.
Any help would be much appreciated.
Next time post a bit of code. Since there isn't, I'll show you how to do it with an example.
Let's assume we have two component, A and B. And the changes will be reflected on both of two components.
Service :
export class YourService{
private data$ = new BehaviorSubject<string>(null);
data = this.data$.asObservable();
public setData(data: string){
this.data$.next(data);
}
}
Component A/B.html :
<div>{{something}}</div>
Component A/B.ts :
isAlive = true;
something: string;
constructor(
private service: YourService
) { }
ngOnInit(){
this.service.data
.takeWhile(() => this.isAlive)
.subscribe( res => {
if(!!res){
this.something = res;
}
});
}
ngOnDestroy(){
this.isAlive = false;
}
Component that change the status:
export class AnotherComponent{
constructor(
private service: YourService
) { }
private changeData(data: string){
this.service.setData(data);
}
}
Now everything is working fine. BehaviorSubject allow the communication between components. whenever the function changeData is fired, you will see the changes on both of your components.
takeWhile is for unsubscribing when the component die.
If you have more question, feel free to ask them to me and I will edit this answer.
You can create service to exchange data between components. It could be UserService that provide access to current user information.
#Injectable()
export class UserService {
user: UserInfo;
// define user here (load from backend or somehow else)
}
In user-profile.component.ts
export class UserProfileComponent {
constructor(public userService: UserService) { }
}
user-profile.component.html
<input type="text" [(ngModel)]="userService.user.name">
In header.component.ts
export class HeaderComponent {
constructor(public userService: UserService) { }
}
header.component.html
<span>{{ userService.user.name }}</span>
So the anggular DI will create a singleton UserService and injects the same object to both components. And when you change it in any of them the changes will be shown in other.
I have a master list app with two component: usersList and usersMap both of which, are holding a private users variable, that gets its data from
the service.
Both are subscribed to a users-service that has a Users array with users data.
Every change on this array will cause both components to render and update their users variable.
Photo of the app on runtime
When I create a new user, the users array gets updated, and the usersList gets re-rendered,
but the usersMap isn`t.
I tried some solutions provided on stack, but they didn't work.
for example: Angular2 Observable BehaviorSubject service not working
A link to the github: https://github.com/TomerOmri/angular-users-map/tree/master/test-app/src/app
UserList component:
https://github.com/TomerOmri/angular-users-map/blob/master/test-app/src/app/userlist/userlist.component.ts
UserMap component: https://github.com/TomerOmri/angular-users-map/blob/master/test-app/src/app/usersmap/usersmap.component.ts
User Service:
import { Injectable } from '#angular/core';
import { Observable } from "rxjs/Observable";
import { of } from "rxjs/observable/of";
import { User } from "./user";
import { USERS } from "./users-mock";
#Injectable()
export class UserService {
constructor() { }
getUsers(): Observable<User[]> {
var userList = of(USERS);
return userList;
}
}
Use following code to do your task:
#Injectable()
export class UserService {
private userList = USERS;
public users = new BehaviourSubject<any>(userList);
constructor() { }
addUser(user): {
this.userList.push(user);
this.users.next(this.userList);
}
Now use this users property in your both component to get data. When you need to add just call addUser method of service.
Hope it will help.