I'm starting to use Firebase with AngularJS.
Coming from a php/serverside rendered pages.
I dont't get how we're supposed to hide parts of an app to some users.
I have basically 3 levels of users (guests / members / admins)
I could hide with a ng-show based on user, but this only hides client-side.
Data is still sent to the user
Real life example:
The menu items are different based on user level.
I was thinking about using ngshow and check for the uuid , but then again, is exposing the admins uuid a good idea? sounds terrible to me.
Then I thought about putting the menu inside a database and requesting the elements.
Not all users would access all items, but this means a lot of 'unauthorised access attempts on purpose'
What is the correct way of handling this? I feel like I've missed something important about client-only apps relying on Firebase services.
Look at userStatus method in service layer and it usage in other layers.
Service layer :
import { Injectable } from '#angular/core';
import { Observable } from 'rxjs/Observable';
import { AngularFireAuthModule, AngularFireAuth } from 'angularfire2/auth';
import * as firebase from 'firebase/app';
#Injectable()
export class AuthService {
user: Observable<firebase.User>;
constructor(private fireAuth: AngularFireAuth) {
}
loginGoogle() {
this.fireAuth.auth.signInWithPopup(new firebase.auth.EmailAuthProvider())
.catch(function (error) {
alert('Please try again');
});
}
logout() {
this.fireAuth.auth.signOut();
}
userStatus() {
return this.fireAuth.authState;
}
}
Navbar Component :
import { Component, OnInit } from '#angular/core';
import { AuthService } from '../../services/auth.service';
import { Observable } from 'rxjs/Observable';
import * as firebase from 'firebase/app';
#Component({
selector: 'app-navbar',
templateUrl: './navbar.component.html',
styleUrls: ['./navbar.component.css']
})
export class NavbarComponent implements OnInit {
user: Observable<firebase.User>;
constructor(private authservice: AuthService) {
this.user = this.authservice.userStatus();
}
ngOnInit() {
}
login() {
this.authservice.loginGoogle();
}
logout() {
this.authservice.logout();
}
}
And here is Navbar view, where you want to hide or show the elements based on if user is authenticated :
<nav class="navbar navbar-expand-md navbar-dark bg-dark fixed-top">
<a class="navbar-brand" routerLink="/">Firebase</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarsExampleDefault" aria-controls="navbarsExampleDefault" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarsExampleDefault">
<ul class="navbar-nav mr-auto">
<li class="nav-item ">
<a class="nav-link" routerLink="/">Home</a>
</li>
<li class="nav-item">
<a class="nav-link" *ngIf="(user | async)?.uid" routerLink="/listings">Listings</a>
</li>
<li class="nav-item">
<a class="nav-link" *ngIf="(user | async)?.uid" routerLink="/add-listing">Add Listing</a>
</li>
</ul>
<ul class="navbar-nav navbar-right">
<li class="nav-item">
<a class="nav-link" *ngIf="!(user | async)?.uid" (click)="login()">Login</a>
<a class="nav-link" *ngIf="(user | async)?.uid" (click)="logout()">Logout</a>
</li>
</ul>
<div *ngIf="(user | async)?.uid">
<img src="{{(user | async)?.photoURL}}" style="width:30px;height:30px;">
<br> Email: {{(user | async)?.email}}
<br> Name: {{(user | async)?.displayName}}
</div>
</div>
</nav>
Related
this is my first question on the platform, hope i do it right.
So, i'm working on a social network using MEAN stack and socket.io, and i'm trying to show the number of unviewed notifications and messages next to the icons. All the data is updated in the component by the sockets and i can see that all works fine in the console, data arrives in real time and updates the array which length i'm using to show the numbers on the navbar. Everything works fine and it updates the numbers in the view with no problem, BUT when i change the route (even if i come back to the same url) it stops updating the view, no matter the data still receiving and updating in console.
I've been days stuck with this, doing research, trying but i can't make it work. I've tried:
Using ChangeDetectionStrategy.OnPush combined with ChangeDetectionRef and its methods like markForCheck, detectChanges with async pipe.
Trying to re-render the component on route change without success.
ngZone but honestly i couldnt understand it so well.
So, i'm looking for some short explanation of what is happening and an idea of how can i could fix it. I know there's some very similar questions like this made before, and i'd checked them but couldn't apply them succesfully to my project. I hope someone can help me with this.
This is the navbar component:
import { Component, OnDestroy, OnInit} from '#angular/core';
import { Router, ActivatedRoute, Params } from '#angular/router';
import { UserService } from '../../services/user.service';
import { NotificationService } from '../../services/notification.service';
import { MessageService } from '../../services/message.service';
import { GLOBAL } from '../../services/global';
import { io } from 'socket.io-client';
import { Observable } from 'rxjs';
import { Message } from 'src/app/models/message';
#Component({
selector: 'app-navbar',
templateUrl: './navbar.component.html',
styleUrls: ['./navbar.component.scss'],
providers: [ MessageService ]
})
export class NavbarComponent implements OnInit{
private socket = io("ws://localhost:3000");
public identity;
public token;
public url:string;
public newNotifications$: Observable<boolean>;
public myNotifications;
public unviewedMessages: Message[];
constructor( public user: UserService,
private _notificationService: NotificationService,
private _messageService: MessageService,
private _route: ActivatedRoute,
private _router: Router
) {
this.identity = user.identity;
this.unviewedMessages = [];
this.token = user.getToken();
this.url = GLOBAL.url;
this.checkIfNewNotifications();
this.checkUnviewedMessages();
}
ngOnInit(): void {
this.sockets()
}
sockets(){
this.socket.emit("addUser", this.identity._id);
this.socket.on("newNotification", newNotification =>{
this.checkIfNewNotifications();
console.log("nueva notificacion")
});
this.socket.on("getMessage", msg =>{
this.checkUnviewedMessages();
console.log("nuevo mensaje")
})
}
logout(){
localStorage.clear();
this.identity = null;
console.log();
this._router.navigate(['/register']);
}
toTop(event){
window.scroll(0,0);
}
seeNotifications(){
this.newNotifications$ = new Observable(observer=>observer.next(false));
this.setViewedNotifications(this.token, this.identity._id);
}
checkIfNewNotifications(){
this._notificationService.getNotifications(this.token).subscribe(
response => {
this.myNotifications = response.notifications.filter(notification => notification.viewed == false).length;
console.log(this.myNotifications)
if(this.myNotifications > 0){
this.newNotifications$ = new Observable(observer=>observer.next(true));
}
},
error => {
console.log(<any>error);
}
)
}
setViewedNotifications(token, id){
this._notificationService.setViewedNotifications(token, id).subscribe(
response =>{
console.log(response);
},
error =>{
console.log(<any>error);
}
)
}
checkUnviewedMessages(){
this._messageService.getUnviewedMessages(this.token).subscribe(
response => {
this.unviewedMessages = response.unviewed;
},
error => {
console.log(<any>error);
}
)
}
}
This is the navbar component template:
<div class="navigation col-lg-12">
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<div class="container-fluid">
<div class="navbar-header mx-xl-5 mx-lg-5">
<a [routerLink]="['/timeline']" (click)="toTop($event)" class="navbar-brand">V a p o r b o x</a>
</div>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent"
aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<div class="d-flex">
<li class="nav-item mx-xl-3 mx-lg-3 mobile-avatar">
<!--Imagen de usuario-->
<a [routerLink]="['/profile', identity._id]"><img src="{{ url + 'get-image-user/' + identity.image }}"
alt="Avatar de usuario logueado" *ngIf="identity && identity.image">
<img src="../../../assets/img/default-user.jpg" class="default-img" alt="Imagen de usuario"
*ngIf="!identity.image || identity.image == null">
</a>
</li>
</div>
<li class="nav-item mx-xl-3 mx-lg-3">
<a [routerLink]="['/timeline']" (click)="toTop($event)" class="nav-link">
<i class="fa fa-home fa-lg mx-lg-2"></i>
Inicio
</a>
</li>
<li class="nav-item mx-xl-3 mx-lg-3">
<a [routerLink]="['/users/']" class="nav-link">
<i class="fa fa-users mx-lg-2"></i>
Usuarios
</a>
</li>
<li class="nav-item mx-xl-3 mx-lg-3">
<a [routerLink]="['/chat']" class="nav-link" *ngIf="unviewedMessages">
<i class="fa fa-comments fa-lg mx-lg-2" *ngIf="unviewedMessages.length < 1"></i>
<i class="fa fa-comments fa-lg mx-lg-2 new-unviewed" *ngIf="unviewedMessages.length >= 1">
<small>{{unviewedMessages.length}}</small>
</i>
Chat
</a>
</li>
<li id="log-out" class="nav-item mx-xl-3 mx-lg-3">
<a href="#" (click)="logout()" class="nav-link">
<i class="fa fa-times fa-lg"></i>
Cerrar Sesión
</a>
</li>
</ul>
<ul class="nav navbar navbar-right mx-lg-5" *ngIf="identity">
<li class="avatar">
<!--Imagen de usuario-->
<a [routerLink]="['/profile', identity._id]"><img src="{{ url + 'get-image-user/' + identity.image }}"
alt="Avatar de usuario logueado" *ngIf="identity && identity.image">
<img src="../../../assets/img/default-user.jpg" class="default-img" alt="Imagen de usuario"
*ngIf="!identity.image || identity.image == null"></a>
</li>
<li class="dropdown">
<a class="dropdown-toggle" data-bs-toggle="dropdown" href="#">
{{identity.name}} <span class="caret"></span>
</a>
<ul class="dropdown-menu">
<li>
<a [routerLink]="['/profile/'+identity._id]"><i class="fa fa-user mx-2"></i>Perfil</a>
</li>
<li>
<a [routerLink]="['/user-edit']"><i class="fa fa-cog mx-2"></i>Configuración</a>
</li>
<li>
<i class="fa fa-times mx-2"></i>Cerrar Sesión
</li>
</ul>
</li>
<li class="nav-item">
<a (click)="seeNotifications($event)" [routerLink]="['/notifications']" class="nav-link">
<i class="fa fa-bell fa-lg" *ngIf="!newNotifications$"></i>
<i class="fa fa-bell fa-lg new-unviewed" *ngIf="newNotifications$"><small>{{myNotifications}}</small></i>
</a>
</li>
</ul>
</div>
</div>
</nav>
</div>
Try unsubscribing the api calls made in components, in ngOnDestroy method.
I'd be a happy man if someone could explain why the following is not working as expected please?
The hasCreative is a boolean but regardless of its true/false value, the <li> is always displayed. Any suggestions would be great. Thank you.
<ng-container *ngIf="uiModel$ | async as model">
<ul class="nav" style="padding-bottom: 30px;">
<li *ngIf="model.hasCreative" class="nav-item">
<a class="nav-link active" routerLinkActive="active" [routerLink]="['']">Home</a>
</li>
</ul>
</ng-container>
export class UserInterfaceModel {
hasCreative: boolean;
}
#Injectable({
providedIn: 'root'
})
export class UserInterfaceService {
user: CognitoUser;
userLoggedIn = false;
private userInterfaceModelSubject$: Subject<UserInterfaceModel> = new Subject();
userInterfaceModel$ = this.userInterfaceModelSubject$.asObservable();
constructor(private authService: AuthService) {
combineLatest([this.authService.onUserLoaded$]).subscribe(([currentUser]) => {
this.user = currentUser;
this.userLoggedIn = true;
this.buildUserInterfaceModel();
});
}
buildUserInterfaceModel(){
const model = new UserInterfaceModel();
if (this.userLoggedIn && this.user !== null){
model.hasCreative = this.user.getSignInUserSession().getIdToken().payload.creative;
}
this.userInterfaceModelSubject$.next(model);
}
}
Try using this:
<ng-container *ngIf="uiModel$ | async as model; else loading">
<ul class="nav" style="padding-bottom: 30px;">
<li *ngIf="model.hasCreative === true " class="nav-item">
<a class="nav-link active" routerLinkActive="active" [routerLink]="['']">Home</a>
</li>
</ul>
</ng-container>
<ng-template #loading>
Loading stuff...
</ng-template>
If loading template will render it means your Observable has no value. If it dosnt work too, try render the value of model.hasCreative by adding somethin like this:
<span>{{model.hasCreative}}<span>
out of <ul> tag to see if model.hasCreative has true/false value or not.
I'm using Bootstrap's navbar and I can get it to collapse successfully by using data-toggle and data-target on each li element.
This SO answer shows a way to do this without having to alter each li:
https://stackoverflow.com/a/42401686/279516
This is my navbar with two of the li elements:
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav mr-auto">
<li class="nav-item" data-toggle="collapse" data-target=".navbar-collapse.show">
<a class="nav-link" href="#" routerLink="/servers">Servers</a>
</li>
<li class="nav-item" data-toggle="collapse" data-target=".navbar-collapse.show">
<a class="nav-link" href="#" routerLink="/servers">Variables</a>
</li>
I'm close to getting this done in my Angular 8 Typescript file:
export class AppComponent implements OnInit {
ngOnInit(): void {
const navbarItems = document.querySelectorAll('.navbar-nav>li');
navbarItems.forEach(navbarItem => {
navbarItem.addEventListener('click', () => {
const navbar = document.querySelector('.navbar-collapse').collapse('hide');
})
});
}
}
The issue is the last line:
Property collapse does not exist on type element.
First, what should I do to get this to work?
Second, is there a better way?
I've tried casting navbar as different types of HTML elements, but that doesn't work either.
You can do it more the Angular way. Like this:
<button class="navbar-toggler" type="button" (click)="showMenu=!showMenu">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" [ngClass]="{'show':showMenu}">
...
</div>
I was able to get it to work by removing 'show' from the class list, shown on the last line here.
(I'm still not sure if this is a good approach, but it's working.)
export class AppComponent implements OnInit {
ngOnInit(): void {
const navbarItems = document.querySelectorAll('.navbar-nav>li');
navbarItems.forEach(navbarItem => {
navbarItem.addEventListener('click', () => {
const navbar = document.querySelector('.navbar-collapse');
navbar.classList.remove('show');
})
});
}
}
There was a single dropdown it was working fine Now I want to make this code reusable, or create Directive because now there are few more dropdowns are added in few more pages
I prefer to make directive, but I am really stuck here
<div class="nav-item has-dropdown">
<div class="menu-text" (click)="hasDropdown($event)">
Click me
</div>
<div class="has-dropdown-view">
Dropdown contenthere
</div>
</div>
hasDropdown(event){
let target = event.target || event.srcElement || event.currentTarget;
this.dropownView = !this.dropownView;
if( this.dropownView ){
target.closest('.has-dropdown').classList.add('has-open')
}else{
target.closest('.has-dropdown').classList.remove('has-open')
}
};
stackblitz
How to implement this click function with the directive 'method'?
I think this could be a solution for your problem:
import { Directive,ElementRef, HostListener } from '#angular/core';
#Directive({
selector: '[hasDropdown]'
})
export class HasDropdownDirective {
constructor(private el: ElementRef) {
}
#HostListener('click') onMouseClick() {
//Place your code here
}
}
Well if you are using bootstrap in your project there is a better way of adding the open class in the dropdown.
Solution Bootstrap 3.3
HTML:
<ul class="nav navbar-nav navbar-right">
<li class="dropdown"
appDropDown>
<a href="#"
class="dropdown-toggle">Manage</a>
<ul class="dropdown-menu">
<li>
Save Data
</li>
<li>
Edit Data
</li>
</ul>
</li>
</ul>
Directivefile
import { Directive, ElementRef, HostListener, Renderer2, HostBinding, } from '#angular/core';
#Directive({
selector: "[appDropDown]",
})
export class DropdownDirective {
#HostBinding('class.open') isOpen: boolean = false;
constructor(private elRef: ElementRef, private renderer: Renderer2) {
}
#HostListener('click') click(eventData: Event) {
this.isOpen = !this.isOpen;
}
}
Note: Above solution uses the HostBinding method, you can even use the ElementRef for it.
This you automatically inject the class open on the click and you can keep using the same bootstrap template everywhere.
I'm trying to create a simple reactive navigation based on if a user is authenticated or not. A login method on a login view sets a token in localstorage. If set, I want to display the logout button. I've tried computed, standard methods and props to no avail.
When I login, the navigation does not update. HOWEVER, if I refresh the page (reinitialize the app), it does show the logout button.
What exactly am I doing incorrectly?
I have been trying for hours to grasp the concept of Vue JS and am on the brink of quitting. What would have taken me minutes to do server side has taken me hours client side. WHERE IS THE REACTIVITY?
Nav.vue
<template>
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
<a class="navbar-brand" href="#">App</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarColor01" aria-controls="navbarColor01" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarColor01">
<ul class="navbar-nav">
<li class="nav-item active">
<a class="nav-link" href="#">
<router-link to="/">Home</router-link>
<span class="sr-only">(current)</span>
</a>
</li>
<li class="nav-item">
<router-link to="/about" class="nav-link">About</router-link>
</li>
</ul>
<ul class="navbar-nav ml-auto">
<li class="nav-item" v-if="hasAuth()"><a #click="logout()" class="nav-link">Log Out</a></li>
<template v-else>
<li class="nav-item">
<router-link to="/register" class="nav-link">Register</router-link>
</li>
<li class="nav-item">
<router-link to="/login" class="nav-link">Login</router-link>
</li>
</template>
</ul>
</div>
</nav>
</template>
<script>
export default {
name: 'Nav',
data: () => {
return {
auth: false
}
},
methods: {
logout: function () {
localStorage.removeItem('user-token');
this.$router.push({ path: 'login' });
},
hasAuth: function () {
this.auth = (localStorage.getItem('user-token')) ? true : false;
return this.auth
}
},
};
</script>
App.vue
<template>
<div id="app">
<Nav></Nav>
<router-view/>
</div>
</template>
<script>
import Nav from '#/components/Nav.vue';
export default {
components: {
Nav,
},
}
</script>
While Vue.js is reactive, localStorage is not. Vue cannot possibly know if the localStorage is modified or not. There is no local change event available with local storage.
To solve this problem, use Vuex combined with Local Storage for persistent data. The point where you save the token to local storage, at that time also save a copy inside Vuex store.
Another component like Nav should read data from Vuex store which is reactive. When you refresh the page, initialize Vuex store with the data from localStorage.
This way you get a perfect reactive system.