Changing value in one component affects another - javascript

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.

Related

How to display a navbar component after logging in without reloading the page in angular 12

My landing page does not show a navbar, but I want to display a navbar after logging in successfully. Currently, I'm able to show navbar if I do a full page reload after logging in successfully. I'm very sure that there is a better way than this approach.
app.component.html
<app-navbar></app-navbar>
<router-outlet></router-outlet>
login.component.ts
login(){
this.credentials = this.myForm.value;
if(this.credentials){
this.loginService.authenticate(this.credentials)
.subscribe(data => {
this.storageService.setLocalStorageItem('auth', JSON.stringify(data));
this.dataService.global.showNav = true;
this.sharedService.getProjectMetadata()
.subscribe(metadata => {
this.storageService.setLocalStorageItem('projectMetaData', JSON.stringify(metadata));
this.router.navigate(['/home']);
})
}, err => console.log(err));
} else {
console.log('Please enter your username and password');
}
}
data.service.ts
import { Injectable } from '#angular/core';
import { Subject, Subscription } from 'rxjs';
import { filter, map } from 'rxjs/operators';
import { IGlobal, IMessage } from '../../Shared/interfaces';
import { MessageCallback } from '../../Shared/types';
#Injectable({
providedIn: 'root'
})
export class DataService {
constructor() { }
date: string = (new Date()).toString();
global: IGlobal = {
showNav: false,
sessionTimedOut: false,
timezone: this.date.substring(this.date.indexOf('GMT')),
projectMetaData: {
name: ''
},
isAdmin: false,
auth: {
roles: {
admin: false,
developer: false
}
}
}
private handler: Subject<IMessage> = new Subject<IMessage>();
broadcast(type: string, payload: any){
this.handler.next({type, payload});
}
subscribe(type: string, callback: MessageCallback): Subscription {
return this.handler.pipe(filter(message => message.type === type), map(message => message.payload))
.subscribe(callback);
}
}
navbar.component.html
<mat-toolbar fxLayout="row" color="primary" *ngIf='showNavbar'></mat-toolbar>
navbar.component.ts
export class NavbarComponent implements OnInit {
user: IAuth = {};
showNavbar: boolean;
progressbar: number = 0;
constructor(
private storageService: StorageService,
private dataService: DataService
) {
this.showNavbar = this.dataService.global.showNav;
}
ngOnInit(): void {
this.user = JSON.parse(this.storageService.getLocalStorageItem('auth'));
if(this.user){
this.showNavbar = true;
}
}
}
Please help me out. Your help is highly appreciated. Thank you.
The problem lies here,
once authentication is completed successfully in login() function, it is not communicated to navbar.component.ts
showNavbar in navbar.component.ts is used to display/hide navbar template.
Though dataService.global.showNav is set to true, it will not trigger change detection in navbar components. Since it is copied to `showNavbar' only in constructor.
So before login, navbar is already loaded, probably with showNavbar evaluated as false, and never recomputed until page reload.
During pagereload value is read from localStorage which provides latest value to showNavbar
I have two suggestions{S1,S2} to fix this.
S1.
1.broadcast via subject from login component about successful login status
2.And subscribe for that status in navbar component and upon subscription , control rendering of navbar template
3. Looks like as per your business logic,broadcast and subscribe functions in dataservice does that for you in IMessage type subject.
4. Consider example code below and update according to your application.
For eg:
login.component.ts
this.dataService.broadcast('authSuccess',{auth:'successful'})
in navbar.component.ts
OnInit() {
this.dataService.subscribe('authSuccess',setShowNavbar);
}
setShowNavbar() {
this.showNavbar=true;
}
S2:
This is not a clean approach and difficult for tracking, but it works for quick and dirty solutions.
navbar.component.html
<mat-toolbar fxLayout="row" color="primary" *ngIf="dataService.global.showNav"></mat-toolbar>
This case will run change detection whenever value in dataService.global.showNav is updated and evaluate ngIf accordingly.
Note: Better to add a small working proto in stackblitz/jsfiddle/codesandbox etc when posting questions in public forum. So it will be easier for everyone to identify exact problem and arrive on specific solutions quickly.

await dataService between components Angular, Behavior Subject

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>

httpClient.get is undefined when using dynamic function/observable references

So I asked a question a few days ago and got some headway on a solution, however now I'm stuck at another wall I'm unsure how to get over.
I have two parent components, a shared view/component with an extended base component, and a service all hooked together. The objective is to use the two parent components to drive what data is shown within the shared component. The two parent components use references to service methods passed into the shared component to get the data.
I've reached an issue where my http.get is always undefined no matter what I try. I've instantiated it like I do in my other services but I've had no luck. I suspect this is caused by how i pass in my service references. Code below:
Parent Component Code:
// PARENT COMPONENT
myData$: Observable<myType>;
searchMethod: Function;
constructor(private myService){
this.myData$ = this.myService.myData$;
this.searchMethod = this.myService.searchData;
}
// PARENT COMPONENT HTML
<app-shared-component
[myData$] = "myData$"
[searchMethod]="searchMethod">
</app-shared-component>
Shared Component Code:
export class MySharedComponent extends BaseComponent<MyType> implements OnInit {
#Input() myData$: Observable<myType>;
#Input() searchMethod: Function;
constructor() { super(); }
ngOnInit(): void {
this.data$ = this.myData$;
}
search(): void {
this.searchMethod().subscribe(//do something);
}
Base Component Code:
#Input data$: Observable<T>;
ngOnInit(): void {
this.data$.subscribe((response: T) => //do something);
super.ngOnInit();
}
Service Code:
private myDataSubject = new BehaviorSubject<MyType>(new MyType());
get myData$(): Observable<MyType> {
return this.myDataSubject.asObservable();
}
constructor(private http: HttpClient) { }
searchData(): Observable<void> {
return new Observable<void>(observer => {
this.http.get<MyType>(
'http://myuri'
).subscribe(
response => {
// do something
},
() => observer.error(),
() => observer.complete()
);
});
}
It looks like you're losing the context of your service when you set this.searchMethod = this.myService.searchData in your parent component. It should work if you change searchData() { to an arrow function: searchData = (): Observable<void> => {.

Unable to get variable outside subscribe in Angular

Well, maybe my issue sounds similar to the few questions here about Angular2-EventEmitter-Subject-Observable-Subscribe-stuff... but it's quite different.
My goal (in short):
Be able to go from route ItemsList to route AddNewItem and then push on the custom 'back' button and find myself in ItemsList WITH ALL SETTINGS THAT I HAD HERE. For a example - search params. So... the only way (afaik) in Angular2+ to store that params between non-related components is to use shared service. So... My code:
ItemsList ts\html (very briefly):
export class ListComponent implements OnInit {
public addItem(allSettings: ISettingsTable) {
this._shService.returnToList = true;
this._shService.allCurrentSettings = allSettings;
this._router.navigate(['someRoute', 'add']);
}
ngOnInit() {
this._settings = {
search: /* .. */,
/* .. */
}
this._shService.getSettings().subscribe((settings: ISettingsTable) => {
this._settings = settings;
}
}
}
<button (click)='addItem(_settings)'>
<mat-icon svgIcon='add'></mat-icon>
</button>
Service (in short):
export class SettingsService {
private _returnToList: boolean;
private _settingsChanged$: Subject<ISettingsTable> = new Subject();
public emitSettings(val: ISettingsTable) {
this._settingsChanged$.next(val);
}
public getSettings(): Subject<ISettingsTable> {
return this._settingsChanged$;
}
}
AddEditItems ts\html (very briefly as well):
export class EditComponent implements OnInit {
public back(par: ISettingsTable): void {
if (this.returnToList) {
this._router.navigate(['someRoute', 'list']).then(() => {
this._shService.emitSettings(par);
});
ngOnInit() {
if (this._shService.returnToList) {
this.returnToList = true;
this._p = this._testS.allCurrentSettings;
}
}
}
<button mat-raised-button (click)='back(_p)'>Go back</button>
So! When I go to the Add route I store the current params in service. Fine. Then on the Add route I get them from service. Fine. Then when I wanna go back I pass that params to the emit method of Subject\Event Emitter... Fine! But on the List route this._settings exists only INSIDE subscribe... It's undefined outside. Yeah, I know about asynchronous behavior and stuff... But how to resolve my issue properly??? Anyone! Please. Hope I made myself clear.
EDIT...
Obviously I always on all routes have some settings. The issue is I dunno how to code If I've got some settings from observable - use them, if not - use default this._settings ...

Javascript Angular 4 Change ngClass from another Component

I currently have this code in my app.component.ts
app.component.html
<div [ngClass]="myclass">
...rest of the content here
</div>
This I have the this:
<button (click)="changeClass('myFavClass')">Change Class to myFavClass</div>
app.component.ts
export class AppComponent {
myclass: string;
changeClass(myclass) {
this.myclass = myclass;
}
}
Now, all this works fine BUT I now want to put the triggering button on another component.
If I put this on another component:
<button (click)="changeClass('myFavClass')">Change Class to myFavClass</div>
How can I get it to change the class?
There are two ways you can do this you can use output with an EventEmit
Or you can set up a service that monitors the changes to a variable and use that as the control point for the change.
Personally, I use services for this instance as its easier to manage the code and its flow.
This answer has all the code in you need to look at.
Changing a value in two different components at the same time Angular 2
Hope that helps
There are at least two options. Subject and Observable or if this another component is a parent you can use #Input.
Subject and Observable method:
angular guide Highly recommended to read whole page.
Some component
export class SomeComponent {
constructor(private ClassService: ClassService) { }
private changeClass(class) {
this.ClassService.changeClass(class);
}
}
Another Component
export class AnotherComponent implements OnInit, OnDestroy {
constructor(private ClassService: ClassService) { }
private class: string = "";
private subscribtion: Subscribtion;
ngOnInit(): void {
this.Subscribtion = this.ClassService.someClass$.subscribe(
(class) => { this.class = class; }
)
}
ngOnDestroy(): void {
this.Subscribtion.unsubscribe();
}
}
Service
#Injectable();
export class ClassService{
constructor() { }
private someClassSource= new Subject<string>();
someClass$= this.someClassSource.asObservable();
changeClass(class) {
this.someClassSource.next(class);
}
}
taken from my answer
#Input method:
angular guide
This is very simple, when you click button changeClass method will change elClass which will be passed to another component by #Input decorator, every change of #Input will cause a detect changes which will detect that value has changed so class will change to myClass.
Parent component
parent.component.html
<another-component [elementClass]="elClass"></another-component>
<button (click)="changeClass('myClass')">change class<button>
parent.component.ts
export class ParentComponnet {
private elClass: string = "";
changeClass(class: string) {
elClass = class;
}
}
Another component (must be child component)
another.component.html
<div [ngClass]="elementClass">
another.component.ts
export class AnotherComponent {
#Input() elementClass: string;
}
There is also Child to Parent interaction via #Output (emitting event) angular guide

Categories

Resources