Navmenu items doesn't change unless it's refreshed it. - asp.net

I have an angular app that has authentication system. Inside the navmenu, I have login, logout and a text that displays username. It works just fine, however, when the user logged in, I still see the same navbar. login should be gone and logout along with username text should be there. But it's not. They are there only when user refreshes the page. I don't know what I'm missing.
Here is my navmenu.component.html
<li *ngIf="!currentUser"><a [routerLink]="['/login']"><span class="glyphicon glyphicon-log-in"></span> Login</a></li>
<li *ngIf="currentUser"><a (click)="logout()"><span class="glyphicon glyphicon-log-out"></span> Logout</a></li>
<li *ngIf="currentUser"><a><span class="glyphicon glyphicon-user"></span> {{ currentUser.firstName }} {{ currentUser.lastName }}</a></li>
And here is my navmenu.component.ts code:
import { Observable } from 'rxjs';
import { User } from './../../models/user';
import { Router } from '#angular/router';
import { AuthService } from './../../services/auth.service';
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'nav-menu',
templateUrl: './navmenu.component.html',
styleUrls: ['./navmenu.component.css']
})
export class NavMenuComponent implements OnInit {
currentUser: User;
ngOnInit() {
this.currentUser = JSON.parse(localStorage.getItem('currentUser') || '');
}
constructor(private authService: AuthService, private router: Router) { }
logout() {
this.authService.logout();
this.router.navigate(['/home']);
}
}
Lastly, here is my app.component.html file looks like:
<nav-menu></nav-menu>
<router-outlet></router-outlet>

ngOnInit will be called only once, so I suggest to do the following:
auth.service.ts
// Your code here
authStateChanged: Subject<AuthState> = new Subject<AuthState>();
// Your code here
auth-state.enum.ts
export enum AuthState {
Authorized,
Unauthorized
}
component.ts
Leave everything as is, just change ngOnInit slightly.
ngOnInit() {
this.authService.authStateChanged.subscribe(
authState => { this.refreshCurrentUser(); }
)
}
refreshCurrentUser() {
this.currentUser = JSON.parse(localStorage.getItem('currentUser') || '');
}
You can add more login to check authState, this is just basic example that will work in your case.
Now inside your auth.service when you do logon, on success simply do:
this.authStateChange.next(AuthState.Authorized);

You can use a BehaviorSubject that checks if there is a user in localStorage and also emits changes in the status.
Service:
private user = new BehaviorSubject<any>(JSON.parse(localStorage.getItem('currentUser')))
public user$ = this.user.asObservable();
setUser(user) {
localStorage.setItem('currentUser', JSON.stringify(user))
this.user.next(true)
}
we call setUser() when the status changes. And every subscriber get's the status.
Login:
login() {
this.authService.setUser({user: 'Admin'});
}
and each component that you want to listen to, add this to constructor:
constructor(private authService: Service) {
authService.user$.subscribe(val => {
this.currentUser = JSON.parse(localStorage.getItem('currentUser'))
})
}
and then of course when you want to log out user, do...
logout() {
this.authService.setUser(null)
}

Related

Return firebase values from a service to a component angular 6

I'm creating an application with angular 6 and firebase using angularfire2, I chose to use the firestore where I have a collection called pages like in the image:
basically I created a service - "PagesService" where I have a function that returns the data of the page that I sent. I'm trying to use getPage to return the values to my component, and assign them to the form, nothing else I tried worked, only returns an "observable" that I can not work, does anyone have an idea of what I can do?
Full code, service:
import { Injectable } from '#angular/core';
import { AngularFirestore, AngularFirestoreCollection, AngularFirestoreDocument } from 'angularfire2/firestore';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
#Injectable()
export class PagesService {
private pagesCollection: AngularFirestoreCollection<any>;
private page: AngularFirestoreDocument<any>;
constructor(private afs: AngularFirestore) {
this.pagesCollection = afs.collection('pages');
}
getPage(pageName: string) {
return this.afs.doc<any>('pages/${pageName}').valueChanges();
}
addPages(pageName: string, pageForm: any) {
this.pagesCollection.doc(pageName).set(pageForm.value);
}
}
My component:
import { Component, OnInit } from '#angular/core';
import { FormBuilder, FormGroup } from '#angular/forms';
import { Observable } from 'rxjs';
import { PagesService } from '../../services/pages.service';
#Component({
selector: 'app-quem-somos',
templateUrl: './quem-somos.component.html',
styleUrls: ['./quem-somos.component.scss']
})
export class QuemSomosComponent implements OnInit {
pageForm: FormGroup;
pageName: string = "wo-we-are";
page: any;
constructor(private pagesService: PagesService, private fb: FormBuilder) { }
ngOnInit() {
this.page = this.pagesService.getPage(this.pageName);
console.log(this.page);
this.pageForm = this.fb.group({
title: '',
content: ''
});
}
save() {
this.pagesService.addPages(this.pageName, this.pageForm);
}
}
obs: Sorry my english
If I have understand you right, When you say "Observable that I cannot work" is mean that you cannot access his data when you are trying to assign its values in the form?
In this case (I assume that your service is working as expected), just subscribe to it and populate the form after your values are ready to use. for example:
ngOnInit() {
this.pagesService.getPage(this.pageName).subscribe(v => {
// Here your data is ready, so you can send it to a function and populate the form as you need.
this.populateForm(v);
});
// Here I just construct the FormGroup, so your application can rendered.
this.pageForm = this.fb.group({
title: '',
content: ''
});
}
And add this function to do the task:
populateForm = (data) => {
console.log(data); // Just log it in console, and see if its the data that you seek for
}
Instead of console.log() you can populate your form or do what ever you need to.
Good Luck !
--EDIT--
I just noticed now, In your service:
getPage(pageName: string) {
return this.afs.doc<any>('pages/${pageName}').valueChanges();
}
You call the doc with ' ' instead of ``, so In fact, you are not using Template Strings. So your call is wrong and not fetch with the right path.
Change it to:
return this.afs.doc<any>(`pages/${pageName}`).valueChanges();

Angular 2 Opening a different form with a passed in parameter

Hope someone can advise the best way to achieve what I want.
I have a user grid where you can click edit it then goes to the manage user form.
What I want to do is pass the selected user id over to the manage user form and display this user, how best would I go about achieving this?
Thanks
Andy
In your grid.component.html, when user clicks on a grid edit button, call a function and pass the corresponding grid data id as shown below.
<button (click)="goToManageForm(gridId)"> Edit</button>
In your grid.component.ts, pass the id as a param to the manageForm component.
private goToManageForm(id:any){
this.router.navigate([`/manageForm/${id}`]);
}
In your routing component, add a route to manageForm component which expects a parameter.
const routes: Routes = [
{
path: '',
component: MyMainComponent,
canActivate: [AuthGuard],
children: [
{ path: 'grid', component: GridComponent },
{ path: 'manageForm/:gridId', component: ManageFormComponent },
]
}
You can access the passed grid id in the manageForm.component.ts as shown below.
import { Component, OnInit } from '#angular/core';
import { Router,ActivatedRoute } from '#angular/router';
import { Subscription } from 'rxjs';
export class manageFormComponent {
private subscription:Subscription;
private gridId:any;
constructor(private activatedRoute:ActivatedRoute){}
ngOnInit(): void {
// subscribe to router event
this.subscription = this.activatedRoute.params.subscribe(
(param: any) => {
if(typeof param['gridId'] !== 'undefined' && param['gridId'].trim !='' ){
this.gridId = param['gridId'];
}
});
}
}
Now the passed gridId will be accessible in this.gridId

ExpressionChangedAfterItHasBeenCheckedError in Directive with Angular

After I get all the hate, I know there's a thread about this problem but I haven't managed to find a solution for my problem. I'm a rookie.
What I wanted to do was to change the nav header background only when the user is in a particular route, so I created a directive in which I retrieve the current url and then I styled the nav header with setElementStyle. For that I'm comparing if the current url matches a particular url that I store in a variable.
The app is working fine but I still get that error.
This is my directive:
import {Directive, ElementRef, Renderer, OnInit, ChangeDetectorRef} from '#angular/core';
import { Router, NavigationStart, NavigationEnd, NavigationError, NavigationCancel, RoutesRecognized } from '#angular/router';
import 'rxjs/add/operator/filter';
#Directive({
selector: '[styled]',
})
export class StyledDirective implements OnInit {
constructor(public el: ElementRef, public renderer: Renderer, public _router: Router) {
renderer.setElementStyle(el.nativeElement, 'color', '#212121');
renderer.setElementStyle(el.nativeElement, 'backgroundColor', 'rgb(247, 247, 247)');
}
ngOnInit(){
const profileUrl = "/app/userprofile";
this._router.events
.filter(event => event instanceof NavigationStart)
.subscribe((event:NavigationStart) => {
if (event.url == profileUrl) {
return this.el.nativeElement.style.backgroundColor = '#ffffff';
}
else {
return this.el.nativeElement.style.backgroundColor = 'rgb(247, 247, 247)';
}
});
this._router.events
.filter(event => event instanceof NavigationStart)
.subscribe((event:NavigationStart) => {
if (event.url == profileUrl) {
return this.el.nativeElement.style.color = "#03A9F4";
}
else {
return this.el.nativeElement.style.color = '#212121';
}
});
}
}
Probably its not the best code ever but that's how I tried to resolve my problem, and probably there's a more elegant solution for this. Thanks for your help guys!
I prefer this way
First inject the Router in constructor, then return a function according to route
constructor(private router: Router) {}
getRoute(){
if (this.router.url === '/client'){
return "client";
}
}
in your html
<header [ngClass]="getRoute()">
and in css
header.client{background-color:yellow}

How to get Accounts.onLogin to impact my app in Meteor React ES6?

I want my meteor app to call setState in App on login and logout. How can I have one section of code (ie: Accounts.onLogon) affect inside another component (ie App{})? Also, what to do to detect logouts?
Accounts.onLogin(function(user){
console.log('hi');
//App.showPrivate();
});
class App extends Component {
constructor(props) {
super(props);
this.state = {
showPublic: false,
};
}
toggleShowPublic() {
this.setState({
showPublic: !this.state.showPublic,
});
}
showPublic() {
this.setState({
showPublic: true,
});
}
showPrivate() {
this.setState({
showPublic: false,
});
}
render() {
return (
<div className="container">
<div className="show-public" onClick={this.toggleShowPublic.bind(this)}>
{this.state.showPublic ?
<span className="private-public"> View private</span> :
<span className="private-public"> View public </span>
}
</div>
</div>
);
}
}
Instead of Accounts.onLogin you should use Meteor's in-built reactive data sources to determine the user's logged-in status:
class App extends Component {
constructor(props) {
super(props);
this.state = { showPublic: false };
}
toggleShowPublic() {
this.setState({ showPublic: !this.state.showPublic });
}
render() {
return (
<div className="container">
{this.props.isLoggedIn ?
<div className="show-public" onClick={this.toggleShowPublic.bind(this)}>
{showPrivate ?
<span className="private-public"> View private</span> :
<span className="private-public"> View public </span>
}
</div> :
Show something else if the user is not logged in here...
}
</div>
);
}
}
export default createContainer(() => {
return {
isLoggedIn: !!Meteor.user()
}
}, App);
Now Meteor will take care of reactively updating this.props.isLoggedIn for you. Note that you need to install meteor/react-meteor-data and import createContainer for this to work:
import { createContainer } from 'meteor/react-meteor-data';
If you still need to do something when the user logs in, you can place Accounts.onLogin basically anywhere you want in your app, as long as you consider whether you want it to run server-side or client-side or both. For best practices regarding application structure, check out Meteor Guide.
It turns out Accounts.onLogin is a distraction. To have the app update when the user logs in or out, we need to see when the logged in user changes, and react accordingly. Seeing when something changes in React is done using componentWillReceiveProps, as seen below:
componentWillReceiveProps(nextProps) {
// user just logged in/out
if (!this.props.currentUser && nextProps.currentUser) {
this.setState({ showPublic: false });
}
}
oh, and current users comes from:
export default createContainer(() => {
return {
currentUser: Meteor.user(),
};
}, App);

How to change the behavior of Meteor accounts-ui so when a user logs in, the log out form automatically appears and vice versa

When a user logs in, I've tried adding a class to the 'logout form' that has a 'display: block' even with the '!important' tag which would override any display property on the logout form. I've tried reloading the page because that does bring up the logout form once a user logs in but it gets stuck in an infinite loop.
import React from 'react';
import ReactDOM from 'react-dom';
export default class AccountsUI extends React.Component {
componentDidMount() {
Accounts._loginButtonsSession.set('dropdownVisible', true);
this.view = Blaze.render(Template.loginButtons,
ReactDOM.findDOMNode(this.refs.container));
setTimeout(function () {
window.requestAnimationFrame(function() {
var node = ReactDOM.findDOMNode();
if (node !== undefined){
Accounts.onLogin(function(user){
document.getElementById('login-dropdown-list').className = "accounts-dialog hide-div"
console.log(document.getElementById('login-dropdown-list').className)
})
}
});
}, 250)
}
componentWillUnmount() {
Blaze.remove(this.view);
}
render() {
return <span ref="container" />
}
}
I'm also going to change how the class additions are triggered. I know that waiting 1/4 a second is very primitive and won't always work.
The Meteor.userId() function is reactive, which means if you call it in getMeteorData() it will be called again each time the userId changes. Save it to this.data, and use it in render().
I'd also suggest you create a React wrapper called LogoutUIWrapper for the Blaze component that only does wrapping and nothing else, just to make your life easier. See here: https://www.meteor.com/tutorials/react/adding-user-accounts
So you'll need to do something like this:
export default class AccountsUI extends React.Component {
getMeteorData() {
return {
userId: Meteor.userId(),
};
}
render() {
return (
<div>
{ this.data.userId ? null : <LogoutUIWrapper /> }
</div>
);
}
}
This way the LogoutUIWrapper component will only appear when the user is logged in.

Resources