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();
}
}
}
Related
I am busy learning Angular + Firebase and I came across a problem. I have a collection of users and one of roles. I am using the ID generated for the users to store my role data. Here is a screenshot:
I want to use the canActivate function to manage who can edit what data. How do you check if a user has the admin field set to true? I can get the roles document for the user with this:
this.afs.collection('roles').doc(id).valueChanges()
I can display the data in HTML like this:
*ngIf="roles$ | async as roles"
However, I want to use it to verify if the user has admin rights.
You will have to use the take(1) or first() operator to only get the first emit from the firebase stream. The CanActivate guard expects a completing Observable:
export interface UserRoles {
admin: boolean;
editor: boolean;
subscriber: boolean;
}
#Injectable({
providedIn: 'root'
})
export class IsAdminGuard implements CanActivate {
constructor(private auth: AngularFireAuth, private afs: AngularFirestore) {}
canActivate(): Observable<boolean> {
return this.auth.user.pipe(
switchMap((user) => !user?.uid
? of(false)
: this.afs.doc<UserRoles>(`roles/${user.uid}`).valueChanges().pipe(
map((roles) => !!(roles?.admin))
)
),
take(1)
);
}
}
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.
I know this is an extremely simple question but I have yet to find a resource solution that will work or explain in a way that makes complete sense. I'm trying to get back into Angular after many years and never used TypeScript before. Currently struggling a lot with errors and what TypeScript is actually expecting me to do.
I have an app that connects to the Open Brewery DB. I'm trying to make a details page that fetches data based on an :id URL param.
app.com/breweries, give me a list of breweries
app.com/breweries/:id, give me specific details on that brewery
I have a list component that grabs a list of Breweries. So whatever comes back gets displayed in a list.
http.service.ts
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { IBrewery } from './brewery/brewery';
#Injectable({
providedIn: 'root'
})
export class HttpService {
constructor(private http: HttpClient) { }
getBreweries() {
return this.http.get('https://api.openbrewerydb.org/breweries');
}
getBrewery(id) {
return this.http.get<IBrewery[]>(`https://api.openbrewerydb.org/breweries/${id}`)
}
}
list.component.ts
import { Component, OnInit } from '#angular/core';
import { HttpService } from '../http.service';
#Component({
selector: 'app-list',
templateUrl: './list.component.html',
styleUrls: ['./list.component.scss']
})
export class ListComponent implements OnInit {
breweries: Object;
constructor(private _http: HttpService) { }
ngOnInit(): void {
this._http.getBreweries().subscribe(data => {
this.breweries = data;
});
}
}
list.component.html
<h1>Breweries</h1>
<ul *ngIf="breweries">
<li *ngFor="let brewery of breweries">
<p class="name">{{ brewery.name }}</p>
<p class="country">{{ brewery.country}}</p>
Visit Website
</li>
</ul>
So all this works no errors everything seems fine...then comes the profile and where things break down.
brewery.component.ts
import { Component, OnInit } from '#angular/core';
import {ActivatedRoute} from '#angular/router';
import { HttpService } from '../http.service';
#Component({
selector: 'app-brewery',
templateUrl: './brewery.component.html',
styleUrls: ['./brewery.component.scss']
})
export class BreweryComponent implements OnInit {
brewery: object = {};
breweryId: string;
constructor(private _http: HttpService, private activatedRoute: ActivatedRoute) { }
ngOnInit(): void {
this.breweryId = this.activatedRoute.snapshot.params.id;
this._http.getBrewery(this.breweryId).subscribe(data => {
this.brewery = data;
})
}
}
brewery.component.html
<ul *ngIf="brewery">
<li>
{{brewery.name}}
</li>
<li>
{{brewery.city}}, {{brewery.state}}
</li>
</ul>
brewery.ts
export interface IBrewery {
name: string,
city: string,
state: string
};
The errors I'm getting are:
- ERROR in src/app/brewery/brewery.component.html:7:13 - error TS2339: Property 'name' does not exist on type 'object'.
- Error occurs in the template of component BreweryComponent.
src/app/brewery/brewery.component.html:10:13 - error TS2339: Property 'city' does not exist on type 'object'.
- Error occurs in the template of component BreweryComponent.
src/app/brewery/brewery.component.html:10:31 - error TS2339: Property 'state' does not exist on type 'object'.
So the problem I believe is that brewery needs to have assigned properties and types associated to those properties before I can declare them in the component template. If that is true, for the life of me I cannot figure out how or where I'm supposed to take the IBrewery and properly use it. I've seen examples where it gets used in the service as well as the mycomponent.component.ts file. In either instance it's about as clear as mud on how to fix the problem.
Short Answer: use Safe Navigation Operator
Update your html as below.
<ul *ngIf="brewery">
<li>
{{brewery?.name}}
</li>
<li>
{{brewery?.city}}, {{brewery?.state}}
</li>
</ul>
Better approach: use a loading spinner.
<div *ngIf="loading">
some loading spinner
</div>
<div *ngIf="!loading">
<li>
{{brewery?.name}}
</li>
<li>
{{brewery?.city}}, {{brewery?.state}}
</li>
</ul>
export class BreweryComponent implements OnInit {
brewery; // be default type will be any.
breweryId: string;
loading = false; // loading spinner.
constructor(private _http: HttpService,
private activatedRoute: ActivatedRoute) { }
ngOnInit(): void {
this.breweryId = this.activatedRoute.snapshot.params.id;
this.get();
}
get() {
this.loading = true;
this._http.getBrewery(this.breweryId)
.subscribe(data => {
this.brewery = data;
this.loading = false; // loading spinner hidden.
}, (error) => {
// handle error;
});
}
}
First of all, you should get the correct typing in your service. It should look like this:
getBreweries() {
return this.http.get<IBrewery[]>('https://api.openbrewerydb.org/breweries');
}
getBrewery(id) {
return this.http.get<IBrewery>(`https://api.openbrewerydb.org/breweries/${id}`)
}
As you can see, I added the expected type to getBreweries and changed the expected type in getBrewery(id). I'm not sure why it was set to IBrewery[] before, since you told us it should only give one specific detail of a brewery.
Now, when you subscribe to these, the parameter inside the subscibe function will be inferred to be the types you have set in the get type parameter. Therefore, it's a good idea to set the type of the component instance variable to that type too, like this:
export class ListComponent implements OnInit {
breweries: IBrewery[];
...
}
and
export class BreweryComponent implements OnInit {
brewery: IBrewery;
...
}
In general, you don't want to use the type object or Object, because it tells you nothing about the structure of the type. If you don't know the exact structure of your type or are too lazy to create an interface, you should use any.
And btw, the reason why the ListComponent worked to begin with was kinda lucky. the let x of y syntax is allowed for y of type object for some reason, and it seems like x is inferred to as any, so you could write whatever you wanted without getting an error. It's important to understand that typescript won't change anything in runtime, so the runtime types will be whatever they are no matter what your typescript types say.
I have an angular2 application and i have implemented the Registration and Login Modules. User role and other details are received when login in. Have no idea on how to properly manage access, based on the role of the user.
At the moment i'm hoping to use a Angular2 service to share the user role and other details through out the application and use "if" conditions to manage access, based on the Role.
Please provide me any information on how to properly do this.
I would approach this by building out an object to read from when the user is successfully logged in.
// when user logs in build out permissions object
permissions = {
dashboardOne: true,
dashboardTwo: true
}
then within your auth service, have a function that returns a boolean based on the user's permissions
userHasAccess = (() =>{
return {
toDashboardOne: () => {
return this.permissions.hasOwnProperty('dashboardOne');
},
toDashboardTwo: () => {
return this.permissions.hasOwnProperty('dashboardTwo');
}
}
})();
now throughout the app you can call the above function
if(this._authService.userHasAccess.toDashboardOne()){
// do something
}
I hope this helps get you started. cheers
You can try to use ngx-permissions library for controlling of permissions and roles in your angular application. The benefits it will remove elements from DOM, lazy loading and isolated modules supported(then, else syntax is supported).
Example of loading roles
import { Component, OnInit } from '#angular/core';
import { NgxPermissionsService } from 'ngx-permissions';
import { HttpClient } from '#angular/common/http';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
title = 'app';
constructor(private permissionsService: NgxPermissionsService,
private http: HttpClient) {}
ngOnInit(): void {
NgxRolesService
.addRole('ROLE_NAME', ['permissionNameA', 'permissionNameB'])
NgxRolesService.addRole('Guest', () => {
return this.sessionService.checkSession().toPromise();
});
NgxRolesService.addRole('Guest', () => {
return true;
});
}
}
Usage in templates
<ng-template [ngxPermissionsOnly]="['ADMIN']" (permissionsAuthorized)="yourCustomAuthorizedFunction()" (permissionsUnauthorized)="yourCustomAuthorizedFunction()">
<div>You can see this text congrats</div>
</ng-template>
<div *ngxPermissionsOnly="['ADMIN', 'GUEST']">
<div>You can see this text congrats</div>
</div>
<div *ngxPermissionsExcept="['ADMIN', 'JOHNY']">
<div>All will see it except admin and Johny</div>
</div>
for more information see wiki page
I am trying to pass user info object to all low level component,
the issue is what is the best way to pass it to lover component even if they are grandchildren?
If the #input will work or have anther way to pass it?
my code for root component is:
constructor(private _state: GlobalState,
private _imageLoader: BaImageLoaderService,
private _spinner: BaThemeSpinner, public af: AngularFire) {
this._loadImages();
this._state.subscribe('menu.isCollapsed', (isCollapsed) => {
this.isMenuCollapsed = isCollapsed;
});
// this.af.auth.subscribe(
// user => this._changeState(user),
this.af.auth.subscribe( user => this._changeState(user));
}
Have you considered creating a service class? They're singletons, so the same instance of that class gets injected into each and every component that asks for it.
https://angular.io/docs/ts/latest/guide/dependency-injection.html
A simple one would look like this
import { Injectable } from '#angular/core';
#Injectable()
export class DummyService {
public userInfo: UserInfo = {
//User stuff goes here
};
}
And you would add it to a component like this.
import {DummyService} from 'dummy.service';
#Component({
selector: 'my-component',
templateUrl: 'my.component.html'
})
export class MyComponent{
constructor(private myDummyService: DummyService){}
}
At runtime, this would inject the same instance of the class into every component you inject it into. So it's a super handy way of syncronizing data across multiple components.