What is the appropriate place for adding a call to initialize a global listener in Angular app?
Here is the code:
export class AuthService {
constructor(
private store: Store<fromAuth.State>,
private afAuth: AngularFireAuth
) {
this.afAuth.auth.onAuthStateChanged(payload => {
if (payload) {
const user: UserBeta = {
uid: payload.uid,
displayName: payload.displayName,
email: payload.email,
emailVerified: payload.emailVerified
};
this.store.dispatch(AuthActions.authenticated({ user }));
} else {
this.store.dispatch(AuthActions.notAuthenticated());
}
});
}
As you could see I've added it to the constructor of the AuthService but it doesn't seem right for me.
What I'm also concerning about is that the following code has two dependencies: Ngrx and AngularFireAuth.
In this case, would it be correct to move somewhere to the FirebaseModule (i.e. firebase.module.ts) and if yes, how is the call will look like?
You can add it inside ngOnInit(), from the docs:
A callback method that is invoked immediately after the default change detector has checked the directive's data-bound properties for the first time, and before any of the view or content children have been checked. It is invoked only once when the directive is instantiated.
Check here for more info:
https://angular.io/api/core/OnInit
Thank all of you for replies.
I've finally decided to introduce a new initialize() method inside the AuthService and call it inside the ngOnInit() method of the AppComponent.
auth.service.ts:
#Injectable({ providedIn: 'root' })
export class AuthService {
constructor(
private http: HttpClient,
private store: Store<fromAuth.State>,
private afAuth: AngularFireAuth
) { }
initialize() {
this.afAuth.auth.onAuthStateChanged(payload => {
if (payload) {
const user: UserBeta = {
uid: payload.uid,
displayName: payload.displayName,
email: payload.email,
emailVerified: payload.emailVerified
};
this.store.dispatch(AuthActions.authenticated({ user }));
} else {
this.store.dispatch(AuthActions.notAuthenticated());
}
});
}
}
app.component.ts:
export class AppComponent implements OnInit {
constructor(private authService: AuthService) { }
ngOnInit(): void {
this.authService.initialize();
}
}
Update: At my project I'm using ngrx for state management. Since AngularFireAuth also manages user information I've faced difficulty in managing the same state in multiple places, which increased the complexity, so the final solution became quite complicated. In the end, I've decided to stop using the onAuthStateChanged listener and start persisting the ngrx state locally.
Related
I am fairly new to angular. I have two components namely header and profile component. The header component handles the login functionality and maintains two information- the user details which is json object and a isLoggedIn which is a boolean that saves current state of login. The general layout of the profile page is-
<header-component>
<profile-component>
Now since the header component handles the login. I want to avoid writing the logic for getting userDetails and the isLoggedIn status again for profile component. So i decided writing a shared service called profile service so that i can upload userDetails and isLogged from header and access that info in the profile component. The input in the loginlogout method comes from the header component.
SharedService code -
import { Injectable } from '#angular/core';
import { HttpService } from './https.service';
import { Observable, BehaviorSubject, of as observableOf } from 'rxjs';
import * as _ from 'lodash';
import { HttpHeaders, HttpParams } from '#angular/common/http';
import { BaseService } from './base.service';
#Injectable()
export class ProfileServices{
constructor(){};
userDetailsBS = new BehaviorSubject<any>('original value');
userDetails= this.userDetailsBS.asObservable();
isLoggedIn:boolean;
loginlogout(userDetails:any , isLoggedIn:boolean){
this.userDetails=userDetails;
this.userDetailsBS.next(this.userDetails);
console.log("Value of user details set in profile service",this.userDetails); //debug
console.log(".getValue() method:",this.userDetailsBS.getValue()); //debug
this.isLoggedIn=isLoggedIn;
}
getUserDetails(){
return this.userDetailsBS.getValue();
}
}
Post login from the header-component.ts i call the loginlogout method in the profile service to set the values. I also tried to access the value passed to the shared Service using the getUserDetails which shows that the userDetails object is passed correctly to the shared service.
The issue arises when i try to access the data from the profile component-
export class ProfileT1Component implements OnInit {
userDetails:any;
constructor(
public profileService: ProfileServices){
this.profileService.userDetails.subscribe((result)=>{
console.log(result);
this.userDetails=result;
console.log("received user details in profile component constructor: ", this.userDetails);
})
}
}
the result still shows "original value" and not the updated value. Is this wrong approach altogether or am i handling the observables incorrectly. Help would be much appreciated.
You need to make a couple of changes in your service to make it work. Add providedIn: root and remove all declarations from other modules. Secondly, you do not need this.userDetailsBS.asObservable() and you can use the subscribe directly on userDetailsBS. Your code will look something like the following.
Service:
#Injectable({
providedIn: 'root'
})
export class ProfileServices {
constructor() {}
userDetailsBS = new BehaviorSubject<any>('original value');
isLoggedIn: boolean;
loginlogout(userDetails: any, isLoggedIn: boolean) {
this.userDetailsBS.next(userDetails);
this.isLoggedIn = isLoggedIn;
}
getUserDetails() {
return this.userDetailsBS.getValue();
}
}
Component:
export class ProfileT1Component implements OnInit {
userDetails: any;
constructor(public profileService: ProfileServices) {
this.profileService.userDetailsBS.subscribe((result) => {
console.log(result);
this.userDetails = result;
console.log('received user details in profile component constructor: ', this.userDetails);
});
}
}
the implementation seems to be OK
(except you should make the BehaviorSubject private and expose only the observable)
probably you have multiple instance of the service.
try to add :
#Injectable({
providedIn: 'root',
})
and remove the service declaration from all the modules provider array
https://angular.io/guide/singleton-services
I'm new to Angular 7 and I'd like to know if I'm on the right path.
I have an 'alert' component that just displays a boostrap alert box on the page at the top.
I want to be able to call this alert and display it from any component.
I'm pretty sure I need a service that I can call to pass a message and then have the alert component subscribe to the service to listen for incoming messages?
So far I can call the service and pass it a 'message' I just don't know how to subscribe/listen (I think that's the right terminology) in the alert component to listen for incoming messages to display.
ex. LoginComponent
constructor(public authService: AuthService, private router: Router, private alert: AlertService) {}
login() {
this.authService.login(this.model).subscribe(next => {
this.alert.success('Logged in successfully');
}, error => {
this.alert.failure('Log in failed');
}, () => {
// do something else
});
}
and then here is my service
ex. AlertService
import {
Injectable
} from '#angular/core';
#Injectable({
providedIn: 'root'
})
export class AlertService {
constructor() {}
success(message: string) {
// do something here?
}
error(message: string) {
// do something here?
}
}
and then I have my AlertComponent, but not sure how I would subscribe/listen for incoming messages ti display from the AlertService.
ex. AlertComponent.ts
export class AlertComponent implements OnInit {
dismissible = true;
alerts: any[];
constructor() { }
ngOnInit() {
this.add();
}
// do something here to subscribe/listen to incoming messages from the service??
add(): void {
this.alerts.push({
type: 'info',
msg: `This alert will be closed in 5 seconds (added: ${new Date().toLocaleTimeString()})`,
timeout: 5000
});
}
}
and the html
<div *ngFor="let alert of alerts">
<alert [type]="alert.type" [dismissible]="dismissible" [dismissOnTimeout]="alert.timeout">{{ alert.msg }}</alert>
</div>
You can also read Angular Dependency Injection.
To have injectable service at disposal in some component you must put it constructor and let Angular DI to provide it: Costructor of AlertComponent shoud have:
constructor ( private/proteced alertService:AlertService) {
alertService.subsribe ((par)=> {
this.add(par);
...})
}
You have preaty a lot to learn. This is just lazy made example becouse overwrite observable every time. It's not an perfect code but shows a little bit how Observables work.
Alert Service:
import {
Injectable
} from '#angular/core';
import { Observable, of } from 'rxjs';
#Injectable({
providedIn: 'root'
})
export class AlertService {
alerts: Observable<any>
constructor() { }
success(message: any) {
this.alerts = of(message)
}
error(message: string) {
this.alerts = of(message)
}
}
Allert component where alert showns:
export class AlertComponent implements OnInit {
dismissible = true;
// just inject service
constructor(public alerts$: AlertService) { }
ngOnInit() {
}
}
Template:
<div *ngIf="alerts$ | async as alerts"> <!-- | async is an pipe it will subscribe for you. importat for observables to first be in *ngIf then in *ngFor loops-->
<ng-container *ngFor="let item of alerts">
<alert[type]="alert.type"[dismissible]="dismissible" [dismissOnTimeout]="alert.timeout"> {{ item }}</alert>
</ng-container>
</div>
Command triggering alert in any component You want:
login() {
this.authService.login(this.model).subscribe(next => {
this.alert.success({ type: 'info', timeout: '5000', msg: "Success!"});
}, error => {
this.alert.failure({ type: 'info', timeout: '5000', msg: "Success!"}); // `this function u can delete meend failure just succes refactor to 'open'`
}, () => {
// do something else
});
}
About services You need to remember to provide them in app.module.ts or any other module like providers: [AlertService] So application will know that this is an service. And you inject them eny where You wat by class constructor(). When injecting you need allways set an scope for them like 'private public or protected' Or You will end up with regular Variable in type or service class.
About Observables:
There are endless Observables and when you subscribe to them You need to unsubscribe read it abot it some where on internet. | async Pipe will do it for You if variable is an endless loop.
I have an angular application which has a number of steps to complete. Each step can only be done once and must have all previous steps complete. To achieve this I have added route guards to each route. The application makes a http request on start to check the status. However the route guard canActivate method doesn't seem to be subscribing to changes.
In the below example statusService updates the status which should trigger an update in the guards.
statusService
#Injectable({
providedIn: 'root'
})
export class StatusService {
private stepOneComplete: BehaviorSubject<boolean> = new BehaviorSubject(false);
private stepTwoComplete: BehaviorSubject<boolean> = new BehaviorSubject(false);
constructor(
private http: HttpClient
) { }
public getStepOneComplete(): Observable<boolean> {
return this.stepOneComplete;
};
public updateStepOneComplete(newValue: boolean): void {
this.stepOneComplete.next(newValue);
};
public initialize(): void {
this.http.get(`${apiUrl}status`)
.subscribe((data: any) => {
this.stepOneComplete(data.stepOne);
});
};
};
stepOneGuard
#Injectable()
export class StepOneGuard implements CanActivate {
constructor(
private service: StatusService,
private router: Router
) {}
canActivate(): Observable<boolean> {
return this.service.getStepOneComplete().pipe(
tap(complete => {
if(complete){
this.router.navigate(['/step-two']);
}
}),
map(complete => {
return !complete;
})
);
}
}
What I expect to happen is that after the initialize method runs and updates stepOneComplete then the router should navigate to step two. However no navigation occurs. If I put a console.log in the tap method of the guard it fires on initial load but not when stepOneComplete.next is called.
I think the answer is here:
public getStepOneComplete(): Observable<boolean> {
return this.stepOneComplete.asObservable();
}
This is what I have in my own production code, works just fine.
You should not see a guard as a singleton that controls navigation. Its only purpose is to control whether the user can access a page or not.
I suggest you to have a state service that holds the state of your "wizard", and then you would simply check it in every guard. You shouldn't even need Subjects at all.
I'm using ngx-translate with no-problems in views, using pipes. What i need to do is to use it in a component, for example to show an error message, or define a datatable column default content.
I'm trying to do something like:
translate.instant("AREA.NEW");
or
translate.get("AREA.NEW").subscribe((res: string) => {
console.log(res);
});
I've tried calling it in ngOnInit() and ngAfterViewInit()
But in both cases i just get "AREA.NEW", not the translated word. I asume the json dictionary is loaded after my call, so i don't realize how make it work.
Import the TranslateService and use it wherever you want.
import { TranslateService } from '#ngx-translate/core';
export class YourComponent {
constructor(private translateService: TranslateService) {
console.log('translation', this.translateService.instant('my_i18n_json_defined_key'));
}
}
It's work.
constructor(private readonly translateService: TranslateService) { }
ngOnInit() {
this.translateService.get(['']).subscribe(translations => {
console.info(this.translateService.instant('key1'));
console.info(this.translateService.instant('key2'));
console.info(this.translateService.instant('key3'));
});
}
Another cause for "instant" translation not working can be the translations not being available when required. This might happen when translations are loaded using the TranslateHttpLoader (example here) which loads from translation files in an async way.
One trick to overcome this is shown here (just tested and it works in Angular 13+) and forces the component to wait until the translation is ready by adding a special resolver on its route:
The resolver
#Injectable()
export class TranslationLoaderResolver {
constructor(private translate: TranslateService){ }
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<any>{
return this.translate.get("does.not.matter");
}
}
Routing usage
{path: "segment", component: YourComponent, resolve: {model: TranslationLoaderResolver}
How can I create a global object in angular2. I am collecting data from one page to another means page by page(may be for 4-5 pages) using navParams in ionic2. but want to store it in global object & finally submit it. I have tried it using global provider but haven't got most of it & not getting any clue also. Please any suggestion.
As with my question ionic community suggest to use global provider. i did use global provider to hold my 7 step form data and final submit on last step.
i used with ionic 3.
provider code:
import { Injectable } from '#angular/core';
#Injectable()
export class NcConnectionProvider {
public statecode:any;
public districtcode:any;
constructor() {
//console.log('Hello NcConnectionProvider Provider');
}
}
Page.ts
import { Component } from '#angular/core';
import { IonicPage, NavController, NavParams } from 'ionic-angular';
import { NcConnectionProvider } from '../../providers/nc-connection/nc-connection';
#IonicPage()
#Component({
selector: 'page-nc-connection',
templateUrl: 'nc-connection.html',
})
export class NcConnectionPage {
public distbform:FormGroup;
public statedata:any;
public districtdata:any;
constructor(public navCtrl: NavController,
public navParams: NavParams,
public ncdatashare: NcConnectionProvider) {
}
locateDistb(form: NgForm){
this.ncdatashare.statecode = this.distbform.value.state;
this.ncdatashare.districtcode = this.distbform.value.district;
}
}
you can inject provider to multiple pages as you want and access your global variable/object. like in example value set to this.ncdatashare.statecode = this.distbform.value.state; now you can access this.ncdatashare.statecode to other page but provider must inject there.
Make sure to provide some sample code to elaborate the problem better. Moreover, following is the way you can follow to collect the data in single object.
Create a service with a variable and its getter setter. Inject this service in your component and set the data wherever needed and later use its get method to collect it back again.
#Injectable()
export class DataService{
private _data: any;
get data() {
return this._data;
}
set data(data: any) {
this._data = data;
}
}
and in side your component
#Component({
// configurations
})
export class LoginComponent {
constructor(public dataService: DataService) {
this.dataService.data = { userAge: 37 }
}
}
and where you need to submit this data, just use the getter to collect it back.
let finalData = this.dataService.data;
Not sure about your use case. But I hope it might help you.