Using a variable as a firebase reference in angular - javascript

I would like to have a string variable contain a value and then use it to set a reference path to my firebase database. Ex. The uid will store the variable from the routed paramMap and then used in the path to fetch the data from the database. I tried this below but it obviously didn't work.
export class HistoryComponent implements OnInit {
snaps: Observable<any[]>;
uid: string;
constructor(db: AngularFireDatabase
private ngZone: NgZone, private afs: AngularFirestore, private route:
ActivatedRoute){
this.snaps = db.list('snapity-tests/12-02-19/'+ $(uid)).valueChanges();
}
ngOnInit() {
this.route.paramMap
.subscribe(params => {
this.uid = params.get('uid');
console.log(this.uid)
})
}}

You need to move your code inside the constructor to the ngOnInit lifecycle hook, because the class constructor is always called before the onInit hook.
As stated in the Angular Docs:
A lifecycle hook that is called after Angular has initialized all data-bound properties of a directive
So, you only need to subscribe to the route params, and then, when you have the value that you want, then you can use it:
export class HistoryComponent implements OnInit {
snaps: Observable<any[]>;
uid: string;
constructor(
private db: AngularFireDatabase,
private ngZone: NgZone,
private afs: AngularFirestore,
private route: ActivatedRoute
){ }
ngOnInit() {
this.route.paramMap
.subscribe(params => {
this.uid = params.get('uid');
this.snaps = db.list('snapity-tests/12-02-19/'+ this.uid).valueChanges();
})
}}

Related

Where to put FirebaseAuth.onAuthStateChanged call in Angular app?

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.

How to assign global variable from subscribe() function and update the view

I am doing the task and I have a problem I can't assign value to global to be seen in the view in angular8. I know what is asynchronous but how to assign the value and update the view.
post-details.component.ts
export class PostDetailsComponent implements OnInit {
indexOfPost;
titles=[];
subscription: Subscription;
renderPost:boolean=false;
constructor(private activatedRoute:ActivatedRoute,private getSharedTitles:ShareInfoTitleService,private detectChanges: ChangeDetectorRef) {
this.subscription = this.getSharedTitles.getMessage().subscribe((title)=>{
this.titles=title.titles;
this.renderPost=true;
this.detectChanges.markForCheck();
});
}
ngOnInit() {
this.indexOfPost=this.activatedRoute.snapshot.paramMap.get("id");
}
}
post-details.component.html
<p *ngIf="renderPost">works</p>
It does not work. My aim is to show the titles in post-details.component.html. I'll be grateful for the tips.Regards
There is no need to write too much for a simple task. Just explore async pipe and it will automatically subscribe and unsubscribe.
export class PostDetailsComponent implements OnInit {
message$ = this.getSharedTitles.getMessage();
constructor(private activatedRoute:ActivatedRoute,private getSharedTitles:ShareInfoTitleService) {
}
ngOnInit() {
this.indexOfPost=this.activatedRoute.snapshot.paramMap.get("id");
}
}
In template
<p *ngIf="( message$ | async) as message">{{message.title}}</p>
You can use async pipe which unsubscribes auto-magically.
export class PostDetailsComponent implements OnInit {
indexOfPost;
titles: Observable<any> = this.getSharedTitles.getMessage();
constructor(private activatedRoute:ActivatedRoute,private getSharedTitles:ShareInfoTitleService,private detectChanges: ChangeDetectorRef) {
}
ngOnInit() {
this.indexOfPost=this.activatedRoute.snapshot.paramMap.get("id");
}
}
in template
<p *ngIf="(titles | async)">works</p>

Observable doesn't load data on router change

working with Angular (v7.2) and Firebase Auth to build a simple web app with a members area. I've set the dashboard up in a separate module with its own router. I've got a service which does most of the heavy lifting for the auth which is shared between all the components.
I'm not convinced this is the best way of doing things but my auth.service.ts file looks like this:
export class AuthService {
private userData$ = new Subject();
private userData;
public userDataObs = this.userData$.asObservable();
constructor(public afs: AngularFirestore, public afAuth: AngularFireAuth, public functions: AngularFireFunctions, public router: Router, public ngZone: NgZone) {
this.afAuth.authState.subscribe((user) => {
if(user) {
this.userData = user;
this.getCurrentUserAccessLevel().subscribe((result) => {
let userAccessLevel = result.result;
let userObj = {
displayName: this.userData.displayName,
email: this.userData.email,
emailVerified: this.userData.emailVerified,
phoneNumber: this.userData.phoneNumber,
photoURL: this.userData.photoURL,
uid: this.userData.uid,
accessLevel: userAccessLevel
}
this.userData$.next(userObj);
});
localStorage.setItem('user', JSON.stringify(this.userData));
JSON.parse(localStorage.getItem('user'));
} else {
localStorage.setItem('user', null);
JSON.parse(localStorage.getItem('user'));
}
})
}
...
In short the service fetches the user data from the Firebase auth service and stores it into a userData property. It then fetches the accessLevel which is a custom claim in Firebase auth and merges this with some fields in the userData to create a custom userObj object. The userObj is then submitted to the userData$ subject for which there is an observable userDataObs.
Each component subscribes to userDataObs.
In the main dashboard.component.ts there is:
export class DashboardComponent {
private userDataObs;
private userData;
private loaded: boolean = false;
constructor(public authService: AuthService, public router: Router, public ngZone: NgZone) {
this.authService.userDataObs.subscribe((data) => {
this.userData = data;
this.loaded = true;
})
}
}
which simply subscribes to the userDataObs in the authService. In the main dashboard component there is a menu with various routerLinks to other components which are served through a router-outlet in the component. If I click the link to another component the route changes and the component initialises but the observable does not load. There is no error message or anything - just nothing:
dashboard-profile.component.ts:
export class DashboardProfileComponent {
private userDataObs;
private userData;
private loaded: boolean = false;
private fileUploadEvent;
private fileUploadProgress;
private fileUploadURL;
constructor(private authService: AuthService, private router: Router, private uploadService: UploadService) {
console.log('constructor');
this.authService.userDataObs.subscribe((data) => {
this.userData = data;
this.loaded = true;
console.log('loaded');
});
}
If, however, I just navigate to the route, without clicking the routerLink it works fine.
I assume it's something to do with sharing the observable? Ideally I'd just like the child components to somehow inherit the observable from the dashboard.component but from what I can tell this isn't possible, hence I need to set up a new subscription.
Would appreciate any help at all! Thank you!

Typescript: How can I set an optional parameter if no value is passed in a derived class?

I have a base class:
export class ClientBase {
constructor(private uri: string, private httpClient: HttpClient) { }
// My Various Methods
}
I have several classes that derive from the ClientBase
For example:
#Injectable()
export class GizmoService extends ClientBase {
constructor(private http: HttpClient)
{
super(environment.gizmoUri, http);
}
};
So in the GizmoService I want to specifically pass the Uri in one particular case..
So ideally I would like to do something like this:
#Injectable()
export class GizmoService extends ClientBase {
constructor(private http: HttpClient, uri?: string)
{
if (!uri) uri = environment.gizmoUri;
super(uri, http);
}
};
However I get error message that super must be the first call in the constructor.
How can I get around this without having to make massive changes to the existing code base? (i.e. Not changing every call to pass 2 parameters)
You could always give the uri parameter a default value.
#Injectable()
export class GizmoService extends ClientBase {
constructor(private http: HttpClient, uri: string = environment.gizmoUri) {
super(uri, http);
}
}

Angular 5 multiple Conditional Routing : "AuthGuards"

i am trying to create conditional routing in angular 5 and i have seen this post and the official milestone page of angular:
Angular2 conditional routing.
But i didn't find a way to create multiple conditions based on input variables.
here is my code :
import { Injectable } from '#angular/core';
import {CanActivate, Router, RouterStateSnapshot, ActivatedRouteSnapshot} from '#angular/router';
import {AuthServiceService} from './auth-service.service';
#Injectable()
export class AuthguardService implements CanActivate{
constructor(
private router: Router,
private AuthService:AuthServiceService
) {}
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
if(this.AuthService.ValidAs("User")!=true){
this.router.navigate(['pages/login']);
return false;
}else{
return true;
}
}
}
My AuthService.ValidAs() method takes an argument to determine the type of user to try and validate as rightful to access a route and returns a boolean. i have three types of users, how do i pass extra arguments to the canActivate property in my routes in order to use that as argument to my ValidAs method.
If there is any other way to create multiple canActivate instances without multiplying files, let me know.
You can pass a data object to any route like that:
...
{
path:'dashboard',
component:DashboardComponent,
canActivate:[
AuthGuard
],
data:{
roles: ['ROLE_ADMIN', 'ROLE_EDITOR', 'OTHER_ROLE'],
// any other relevant data to make your checks
}
},
...
Then in your AuthGuard you can retrieve the data like:
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
...
const roles = route.data[ 'roles' ] as Array<string>;
// Do other checks here
...
}
You have two options.
First is to use params or queryParams.
canActivate(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot): boolean {
const userType = router.paramMap.get('userType');
}
Second is to use the dependency injection.
constructor(
private router: Router,
private AuthService: AuthServiceService,
#Inject(CURRENT_USER) user: User,
) {}
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
const userType = this.user.type;
}

Categories

Resources