Reload Angular 9 component elements on data change - javascript

I'm using Angular 9, where I want to dynamically change data of a menu item when a person logs in. But instead, since the menu gets loaded along with the home page, when a person logs in, the data change is not reflected in the menu items until I refresh the page manually. I tried using Renderer 2, ChangeDetectorRef and ElementRef but failded to reload the menu automatically. Below I'm adding just the relevant elements since the actual component code is long. Ask me if you need to know anything else:
Html:
<div class="widget-text">
<a mat-button [matMenuTriggerFor]="accountMenu" #accountMenuTrigger="matMenuTrigger" *ngIf="!isLoggedIn">
<mat-icon>person</mat-icon>
<span fxShow="false" fxShow.gt-sm class="flag-menu-title">Account</span>
<mat-icon class="mat-icon-sm caret cur-icon">arrow_drop_down</mat-icon>
</a>
<mat-menu #accountMenu="matMenu" [overlapTrigger]="false" xPosition="before" class="app-dropdown">
<span>
<button mat-menu-item [routerLink]="['/admin/login']" routerLinkActive="router-link-active">
<mat-icon >person</mat-icon>
<span>Login</span>
</button>
<button mat-menu-item [routerLink]="['/admin/login']" routerLinkActive="router-link-active">
<mat-icon>person_add</mat-icon>
<span>Register</span>
</button>
</span>
</mat-menu>
<a mat-button [matMenuTriggerFor]="profileMenu" #profileMenuTrigger="matMenuTrigger" *ngIf="isLoggedIn">
<mat-icon>person</mat-icon>
<span fxShow="false" fxShow.gt-sm class="flag-menu-title">Howdy, {{name}}</span>
<mat-icon class="mat-icon-sm caret cur-icon">arrow_drop_down</mat-icon>
</a>
<mat-menu #profileMenu="matMenu" [overlapTrigger]="false" xPosition="before" class="app-dropdown">
<span>
<button mat-menu-item [routerLink]="['/admin/profile']" routerLinkActive="router-link-active">
<mat-icon >person</mat-icon>
<span>Profile</span>
</button>
<button mat-menu-item (click)="logout()">
<mat-icon>warning</mat-icon>
<span>Logout</span>
</button>
</span>
</mat-menu>
</div>
typescript:
public name;
public isLoggedIn = false;
constructor(public router: Router, private cartService: CartService, public sidenavMenuService:SidebarMenuService) {
this.checkLogin();
this.name = Cookie.get('userName');
}
public checkLogin(): any {
if(Cookie.get('authtoken')) {
this.isLoggedIn = true;
}
}

You don't need to make things complicated, when you logged in your logged in guard (i.e. auth guard).
import { Injectable } from '#angular/core';
import { Router, CanActivate } from '#angular/router';
import { AuthService } from './auth.service';
#Injectable()
export class AuthGuardService implements CanActivate {
constructor(public auth: AuthService, public router: Router , private sideMenuService: SideMenuService) {}
canActivate(): boolean {
if (!this.auth.isAuthenticated()) {
this.sideMenuService.sideMenuData.next({...data}); // so here you can dispatch the side menu service data .
this.router.navigate(['dashboard']); // here after authentication it
will redirect to your dashboard
page
return false;
}
return true;
}
}
}
so after redirect when you land on the Dashboard Page , in the Dashboard component you have also inject the sideMenu Service and subscribe the BehaviourSubject menu data field .
public name;
public isLoggedIn = false; // here you don't need to check login
// because you come here from auth guard
constructor(public router: Router, private cartService: CartService,
public sidenavMenuService: SidebarMenuService) {
this.checkLogin(); // same no need to check login in each
component if you use auth guard
this.name = Cookie.get('userName');
}
public ngOnInit(){
this.sideMenuService.sideMenuData.subscribe((data)=>{
// hered you get the data dynamic , you can assign to any
// component field.
});
}
public checkLogin(): any {
if(Cookie.get('authtoken')) {
this.isLoggedIn = true;
}
}
so that's how whenever you login every time you dispatch some dynamic data and your behaviourSubject will get updated and where ever you subscribe like in Dashboard component you will get the dynamic data.
Hope it will help.

The constructor is executed only one time during the creation of the page.
constructor(public router: Router, private cartService: CartService, public sidenavMenuService:SidebarMenuService) {
this.checkLogin();
this.name = Cookie.get('userName');
}
Now, according to the code, if the cookie authtoken is not found during the construction, there is no way your app to know if that was created by another (login) process.
You should call the checkLogin function and the name assignment right after your login cocmpletes.

Related

Is any standard way to role based access file in angular?

I have developed 4 roles access projects in angular. The dashboard have different content pages. Whenever logged in the portal intially called dashboard page.
This dashboard content will shows based on logged user role. I have used ngSwitch. Anyone knows a different way implementation instead of using ngSwitch. Kindly share your answer. It's working but I want different solution
I have explained what i did,
defined 4 role
SuperAdmin, Admin, AdminUser, User
I have created 4 component files. follow this code component.ts file
export class DashboardComponent implements OnInit {
userRole: string;
constructor(private authService: AuthService) {
this.userRole = this.authService.userRole();
}
html file:
<div [ngSwitch]="userRole">
<app-header-component title="Dashboard" *ngSwitchCase="'SuperAdmin'">
</app-header-component>
<app-system-integrator-dashboard *ngSwitchCase="'Admin'">
</app-system-integrator-dashboard>
<app-organization-admin-dashboard *ngSwitchCase="'AdminUser'">
</app-organization-admin-dashboard>
<app-organization-user-dashboard *ngSwitchCase="'User'">
</app-organization-user-dashboard>
</div>
create directive like
/* Usage : *roleIsOneOf="[userType.ADMIN, userType.ANALYST, userType.SUPER_ANALYST]" */
#Directive({
selector: '[roleIsOneOf]',
})
export class RoleIsOneOfDirective {
constructor(private authService: AuthService,
private templateRef: TemplateRef<any>,
private viewContainer: ViewContainerRef) {
}
#Input() set roleIsOneOf(allowedRoles: Role[]) {
const userRole: Role = this.authService.userRole();
if (allowedRoles.includes(userRole)) {
this.viewContainer.createEmbeddedView(this.templateRef);
} else {
this.viewContainer.clear();
}
}
}

The event is not subscribed by method in Angular (modal window)

I have modal window and component where this modal window called. When i try to close this modal window, method that need to close it - don't invoked, because of this method starts when event is emited. But method subscription doesn't happen.
export class TakeOrderFormComponent implements OnInit
{
#ViewChild('orderSucModal') orderSuccessModal: OrderSuccessComponent;
orderSuccessRef: BsModalRef;
constructor(private orderService: OrderService, private cartService: CartService,
private modalService: BsModalService, private router: Router)
{
}
openCartModal(orderId: number): void
{
this.orderSuccessModal.loadOrder(orderId);
this.orderSuccessRef = this.modalService.show(this.orderSuccessModal.template, {ignoreBackdropClick: true});
}
hideSucModal(): void
{
console.log('in parent start')
this.orderSuccessRef.hide();
console.log('in parent stop')
} ...
This is code of modal window in main block. And here method hideSucModal() don't invoked.
<app-order-success
#orderSucModal
(hideModal)="hideSucModal()">
</app-order-success>
All modal window code
<ng-template #orderSuccess class="modal-md">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">Ваше замовлення <b>№{{order.orderId}}</b> успішно оброблено!</h4>
</div>
<div class="modal-body">
<div class="modal-footer">
<button (click)="hideModalClick()" class="btn btn-outline-success"
type="button">Зрозуміло!
</button>
</div>
</div>
</div>
</ng-template>
And here is logic of modal-component
export class OrderSuccessComponent
{
#ViewChild('orderSuccess') template: TemplateRef<any>;
#Output() hideModal: EventEmitter<void> = new EventEmitter<void>();
#Output() confirm: EventEmitter<void> = new EventEmitter<void>();
public order: Order = new Order();
public dateAndTime: string[] = [];
constructor(private orderService: OrderService, private router: Router)
{
}
hideModalClick(): void
{
console.log('in child start')
this.hideModal.emit();
this.router.navigate(['/home']);
console.log('in child stop');
}...
Actually as I done some research, I think that method hideSucModal() can't subscribe to hideModal event. Any thoughts about this?
Maybe you can make small refactoring:
1) in core component
import { MatDialog } from '#angular/material/dialog';
add in constructor
constructor( ... ,private matDialog: MatDialog )
to open modal window use
this.matDialog.open(OrderSuccessComponent, {data},width:'100%', height:'90%')
2) in OrderSuccessComponent
add in constructor
constructor( ... ,private dialogRef: MatDialogRef )
and now you can call this.dialogRef.close() inside hideModalClick()
Pay attention different entities (MatDialog vs MatDialogRef) used in core and child components!

Angular. How to switch component depending on service's actions

Lets say I have 2 components, aComponent and bComponent. I have them redered inside the AppComponent
<app-a>
<app-b>
And I have service myService that has method .trigger().
What I want is to show only aComponent, but whenever I call myService.trigger() from another part of code, it would switch and show bComponent. That's perfect implementation that I can't reach.
Question is: Is it possible to do so? And if not what is the best closest solution.
The only working solution I got:
I added .trigger() inside AppComponent
export class AppComponent {
title = 'spa';
show: boolean = false;
trigger() {
this.show = true;
}
}
And rendered components like so:
<div *ngIf="!show; else show">
<app-a></app-a>
</div>
<ng-template #show>
<app-b></app-b>
</ng-template>
Then whenever I want to trigger switching, I add instance of the app to the constructor and call it's method:
export class AnotherComponent implements OnInit {
constructor(
private app: AppComponent
) {}
ngOnInit(): void {
this.app.trigger();
}
}
Even though it's working pretty good, I myself see that it's a dirty solution. Components are not intended to be used inside another components, but Services are.
You can use Subject from rxjs library for that.
In your service file:
// a-service.service.ts
import { Injectable } from '#angular/core';
import { Subject } from 'rxjs';
#Injectable({ providedIn: 'root' })
export class AService {
private subject = new Subject<any>();
trigger(state: boolean) {
this.subject.next(state);
}
getTrigger(): Subject<any> {
return this.subject;
}
}
and in your AppComponent:
// app.component.ts
...
private show = false;
constructor (private aService: AService) { }
ngOnInit() {
this.aService.getTrigger().subscribe(state => {
this.show = state;
});
}
the template can be as you provided - it's fine:
<div *ngIf="!show; else show">
<app-a></app-a>
</div>
<ng-template #show>
<app-b></app-b>
</ng-template>
And if you want to trigger from another component, you do it like this:
// another.component.ts
...
constructor (private aService: AService) { }
ngOnInit() {
this.aService.trigger(true);
}
One way to communicate between different components and services which aren't directly related, is via 'Subjects'.
You can try to create a subject and pass in values to it from myService.trigger(). And you can subscribe to that subject from whichever component you want to access that trigger data.

How to create a mat-progress bar after showing a dialog box

I have a page in which I have a Run button.If I click on Run button a dialog box appears with two options Yes and No.If a user clicks Yes I want to display a mat-progress bar.
I am confused as to where to write the html code of mat-progress bar and where to call it from.
HTML Code:
<mat-toolbar>
<div class="col-md-offset-11">
<button
mat-raised-button
mat-hint="Execute Query on Whole DataSet"
color="primary"
(click)="executeOnFullData()"
>
Run
</button>
</div>
</mat-toolbar>
TypeScript Code:
executeOnFullData() {
const dialogRef = this.dialog.open(ConfirmJobRunComponent, {
});
dialogRef.afterClosed()
}
HTML Code for dialogBox:
<div class="card">
<div class="card-header"><h5 class="title">Confirm</h5></div>
<div class="content">
<h3 mat-dialog-title>
Are you sure you want to run Recommendation Settings on the entire
Dataset?
</h3>
<div mat-dialog-actions>
<button
mat-button
[mat-dialog-close]="true"
(click)="confirmSelection()"
>
Yes
</button>
<button mat-button (click)="onNoClick()">
Cancel
</button>
</div>
</div>
Typescript Code for DialogComponent
import { MAT_DIALOG_DATA, MatDialogRef } from "#angular/material";
import { Component, Inject } from "#angular/core";
import { RecommendationService } from "../../recommendation-
service.service";
#Component({
selector: "app-confirm-job-run",
templateUrl: "./confirm-job-run.component.html",
styleUrls: ["./confirm-job-run.component.scss"]
})
export class ConfirmJobRunComponent {
constructor(
public dialogRef: MatDialogRef<ConfirmJobRunComponent>,
#Inject(MAT_DIALOG_DATA) public data: any,
public dataService: RecommendationService
) {}
onNoClick(): void {
this.dialogRef.close();
}
confirmSelection(): void {}
}
You can just subscribe to afterClosed of your dialogRef and based on the result you get back from your dialog (clicking Yes returns true, clicking No returns false) you can then show a mat-progress and execute your business logic.
Here
is a stackblitz showing how this could look like. The mat-progress
is currently indeterminate and not waiting for something to complete,
that is up to you.
Template (in your component where the button is located)
<mat-progress-bar *ngIf="showMatProgress" mode="indeterminate"></mat-progress-bar>
Component for above template
showMatProgress: boolean = false;
executeOnFullData() {
const dialogRef = this.dialog.open(ConfirmJobRunComponent, {});
dialogRef.afterClosed().subscribe((result) => {
this.showMatProgress = result;
});
}
An in your dialog component
onNoClick(): void {
this.dialogRef.close(false);
}
confirmSelection(): void {
this.dialogRef.close(true);
}

Why is my user ID undefined when passing it into my URL?

I am building a profile page and trying to get the authenticated user data to display there. My API call works with their id, and it works on the front end if I manually enter the id into the url.
But when I try to navigate to the profile from the navbar, I receive a
400 Bad Request for URL: http://localhost:3000/users/undefined
What I can assume right now is that it's an asynchrony issue. My profile page calls the user data, but that user data isn't available in my nav component. And it seems as though I need to pass in my id param into my profile [routerLink] if I want to navigate correctly. Since my user data isn't available in my nav component, it has nothing to pass.
Is there a better approach to this? Should I be using an event emitter?
Fairly new to Angular - help much appreciated!
Profile Component
import { Component, OnInit, Input } from '#angular/core';
import { AuthService } from '.././services/auth.service';
import { UserService } from '.././services/user.service'
import { Router, ActivatedRoute } from '#angular/router';
#Component({
selector: 'app-profile',
templateUrl: './profile.component.html',
styleUrls: ['./profile.component.css'],
providers: [UserService]
})
export class ProfileComponent implements OnInit {
currentUser;
isAuth: boolean;
constructor(
private session: AuthService,
private router: Router,
private userService: UserService,
private route: ActivatedRoute
) {
this.session.isAuth
.subscribe((isAuth: boolean) => {
// user will be false if logged out
// or user object if logged in.
this.isAuth = isAuth;
});
if (this.session.token) {
this.isAuth = true;
console.log(this.session);
} else {
this.isAuth = false;
}
}
ngOnInit() {
this.route.params.subscribe(params => {
this.getUserDetails(params['id']);
});
}
getUserDetails(id) {
this.userService.get(id)
.subscribe((user) => {
this.currentUser = user;
console.log(this.currentUser);
});
}
}
Nav Template
Where I'm navigating to my profile page.
<nav class="navbar navbar-default">
<div class="container-fluid">
<!-- Brand and toggle get grouped for better mobile display -->
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#">bnb</a>
</div>
<!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav navbar-right">
<li *ngIf="!isAuth"><a [routerLink]="['login']">Login</a></li>
<li *ngIf="isAuth"><a [routerLink]="['profile']"><span class="glyphicon glyphicon-user" aria-hidden="true"></span> Profile</a></li>
<li *ngIf="isAuth"><a (click)="logout()">Logout</a></li>
<li *ngIf="!isAuth"><a [routerLink]="['signup']">Signup</a></li>
</ul>
</div>
</div>
</nav>
Nav Component
import { Component, OnInit, Input } from '#angular/core';
import { AuthService } from '.././services/auth.service';
import { UserService } from '.././services/user.service';
import { Router, ActivatedRoute } from '#angular/router';
#Component({
selector: 'app-navbar',
templateUrl: './navbar.component.html',
styleUrls: ['./navbar.component.css']
})
export class NavbarComponent implements OnInit {
isAuth: boolean;
currentUser: any;
constructor(
private session: AuthService,
private userService: UserService,
private router: Router,
private route: ActivatedRoute
) {
this.currentUser = JSON.parse(localStorage.getItem("User"))
console.log("USER",this.currentUser) //Currently returns Null
console.log(this.session)
this.session.isAuth
.subscribe((isAuth: boolean) => {
// user will be false if logged out
// or user object if logged in.
this.isAuth = isAuth;
});
if (this.session.token) {
this.isAuth = true;
} else {
this.isAuth = false;
}
}
ngOnInit() {
}
logout() {
this.session.logout();
}
}
Router
import { Routes } from '#angular/router';
import { LoginComponent } from '../login/login.component';
import { SignupComponent } from '../signup/signup.component';
import { HomeComponent } from '../home/home.component';
import { RentalListingsComponent } from '../rental-listings/rental-listings.component';
import { SingleRentalComponent } from '../rental-listings/single-rental/single-rental.component';
import { ProfileComponent } from '../profile/profile.component'
import { AuthService } from '../services/auth.service';
export const routes: Routes = [
{ path: '', component: HomeComponent },
{ path: 'login', component: LoginComponent },
{ path: 'signup', component: SignupComponent },
{ path: 'rentals', component: RentalListingsComponent },
{ path: 'listing', component: SingleRentalComponent },
{ path: 'profile/:id', component: ProfileComponent, canActivate: [AuthService] } <--profile path. I know I have to match my url paths, but don't know how to do this from the navbar.
// { path: 'home', component: HomeComponent, canActivate: [AuthService] },
{ path: '**', redirectTo: '' }
];
Thanks for providing the detail. Somewhere you need to subscribe to 'after login' or 'authentication' event, grab the user profile JSON, and save it to localstorage so you can use it anywhere you want. If you can't hook in or subscribe to one of these, then do it imperatively somewhere convenient in your code. Find out what call you can make to fetch the entire user JSON and save it as follows...
Check out my AuthService init() below. First line is this.authProvider.on('authenticated', this.onAuth);. Whatever authentication service API you are using should provide a way for you to specify a callback (providing the login token) whenever someone logs in. The onAuth callback function saves the token in localstorage and then fetchProfile(...){...} makes another call to the authentication service API to get the whole JSON user profile using the token just received this.user.getProfile(idToken, this.onProfile);. For example, I use Auth0 in projects, and my call to Auth0 API looks like this.lock.getProfile(idToken, this.onProfile); but I replaced that with an example of what your call might look like this.user.getProfile(idToken, this.onProfile); So use whatever your API uses replace in fetchProfile. Then the onProfile callback saves the entire JSON profile in a single key in local storage using this.localStorage.set('profile', profile); Then you can get it any time by calling this.localStorage.get('profile').
Do not provide UserService through the lazy-loaded ProfileComponent. That creates a separate branch on the dependency injection tree you might not want. See https://angular-2-training-book.rangle.io/handout/modules/shared-modules-di.html Import the UserService in a top-level module like AppModule or SharedModule and provide it there. No need to export it if it's in AppModule.
app.module.ts
...
#NgModule({
imports: [
...
UserService,
...
]
providers: [
...
UserService,
...
]
Handle Auth related stuff in Auth, not Profile. Profile seems visual/implementation-specific (e.g. it has a template). Here is a code snippet example.
auth.service.ts
#Injectable()
export class Auth {
userProfile: UserProfile;
constructor(
...
private localStorage: LocalStorageService,
private router: Router,
private user: UserService,
private authProvider: ...
...
) {
}
init() {
this.authProvider.on('authenticated', this.onAuth);
// Set userProfile attribute if already saved profile
this.userProfile = this.localStorage.get('profile');
setTimeout(() => { // let AppComponent listener initialize
this.localStorage.set('profile', this.userProfile);
}, 0);
}
}
onAuth = (authResult: AuthResult) => {
this.localStorage.set('id_token', authResult.idToken);
this.fetchProfile(authResult.idToken);
}
// Save current route for redirect url
login() {
this.localStorage.set('redirect_url', this.router.url);
this.authProvider.show({initialScreen: 'login'});
};
// Check if user is logged in.
authenticated() {
// Check if unexpired token.
// Searches for item in localStorage with key == 'id_token'
return this.authProvider.tokenNotExpired();
};
logout() {
this.router.navigateByUrl('');
this.userProfile = undefined; // do before localstorage
this.localStorage.remove('id_token');
this.localStorage.remove('profile');
};
fetchProfile(idToken: string) {
this.user.getProfile(idToken, this.onProfile);
}
/**
* On profile event callback.
* Save profile to LocalStorage.
* Redirect to url if present in LocalStorage.
*/
onProfile = (error: any, profile: UserProfile) => {
if (error) {
console.log(error);
return;
}
this.userProfile = profile;
this.localStorage.set('profile', profile);
// Redirect if there is a saved url to do so.
const redirectUrl: string = this.localStorage.get('redirect_url');
if (redirectUrl !== undefined) {
this.router.navigateByUrl(redirectUrl);
this.localStorage.remove('redirect_url');
}
}
Interact with localStorage through a LocalStorageService and subscribe to changes as follows.
localstorage.service.ts
import { Injectable } from '#angular/core';
import { Subject } from 'rxjs/Subject';
#Injectable()
export class LocalStorageService {
[key:string]: any;
/**
* define your localstorage variables here as observables
*/
id_token$ = new Subject();
redirect_url$ = new Subject();
profile$ = new Subject();
customer$ = new Subject();
set(key: string, value: any) {
this[key + '$'].next(value); // this will make sure to tell every subscriber about the change.
localStorage.setItem(key, JSON.stringify(value));
}
get(key: string) {
const value = localStorage.getItem(key);
return value && JSON.parse(value);
}
remove(key: string) {
this[key + '$'].next(undefined);
localStorage.removeItem(key);
}
}
Don't do so much in constructor. Example:
app.component.ts
export class AppComponent implements OnDestroy, OnInit {
webRobot: boolean = false;
private profileSub: any;
private customerSub: any;
private subscriptionSub: any;
constructor(
private analyticsService: AnalyticsService,
private auth: Auth,
private localStorage: LocalStorageService,
) {
}
ngOnInit(): void {
this.init();
}
init() {
this.auth.init(this.webRobot);
this.analytics.init(this.webRobot);
if (!this.webRobot) {
// subscribe to authed profile changes
this.profileSub =
this.localStorage.profile$.subscribe(this.onProfile);
// Subscribe to changes to Stripe customer
this.customerSub =
this.localStorage.customer$.subscribe(this.onCustomer);
}
// always delete active subscribes on destroy
ngOnDestroy() {
this.profileSub.unsubscribe();
this.customerSub.unsubscribe();
}
onProfile = (profile: UserProfile) => {
// ...do stuff
}
onCustomer= (profile: Customer) => {
// ...do stuff
}
In your profile route configuration, it is expecting the id query param
{ path: 'profile/:id', component: ProfileComponent, canActivate: [AuthService] }
<--profile path.
I know I have to match my url paths,
but don't know how to do this from the navbar.
but your navbar link is not passing the id value
<li *ngIf="isAuth"><a [routerLink]="['profile']"><span class="glyphic
you need to do something like this in your navbar
<li *ngIf="isAuth"><a [routerLink]="['profile/user.id']">

Categories