I am writing an Angular Service to prove a Users permissions. In the constructor I want to get the current logged in user from an API. The current User which is created is used in other methods of this Service. This Methods are called from components to check which things can be shown and so on.
The problem is that the methods in the service are called faster than the current user is available.
Are there any possibilities solving this issue?
permission.service.ts
#Injectable()
export class PermissionService {
currUser = {
'id': "",
'permission': ""
};
apiService: AlfrescoApiService;
authService: AuthenticationService;
constructor(apiService: AlfrescoApiService, authService: AuthenticationService) {
this.apiService = apiService;
this.authService = authService;
this.init();
}
init() {
let userId: string = this.authService.getEcmUsername();
this.currUser.id = userId;
//API call
this.apiService.sitesApi.getSiteMember(SITENAME, userId).then(resp => {
this.currUser.permission = resp.entry.role;
})
}
isSiteManager(): boolean {
console.log(this.currUser.permission, this.currUser);
if(this.currUser.permission === "SiteManager"){
return true;
}else{
return false;
}
}
}
method call
export class AppLayoutComponent {
constructor(permissionService:PermissionService) {
permissionService.isSiteManager();
}
}
output in Google Chrome
{id: "admin", permission: ""}
id: "admin"permission: "SiteManager"
You should use promise in your getEcmUsername() handle this; after that you can code like this
`
this.authService.getEcmUsername().then((userID) => {
this.apiService.sitesApi.getSiteMember(SITENAME, userId).then(resp => {
this.currUser.permission = resp.entry.role;
})
});
`
In my opinion better solution is to use Observable here and rxjs. In service you can create Subject and subscribe it inside your compnent, to be sure data is already there. E.g.:
#Injectable()
export class PermissionService {
public Subject<bool> userFetched= new Subject<bool>();
currUser: IUser = {
'id': "",
'permission': ""
};
apiService: AlfrescoApiService;
authService: AuthenticationService;
constructor(apiService: AlfrescoApiService, authService: AuthenticationService) {
this.apiService = apiService;
this.authService = authService;
this.init();
}
init() {
let userId: string = this.authService.getEcmUsername();
this.currUser.id = userId;
//API call
this.apiService.sitesApi.getSiteMember(SITENAME, userId).subscribe((data:IUser)=>
{
this.user=data;
this.userFetched.next(true);
})
}
isSiteManager(): boolean {
console.log(this.currUser.permission, this.currUser);
if(this.currUser.permission === "SiteManager"){
return true;
}else{
return false;
}
}
}
After that in your component:
export class AppLayoutComponent {
constructor(permissionService:PermissionService) {
permissionService.userFetched.subscribe((data)=>{
permissionService.isSiteManager();
});
}
}
It's better approach. You need to consider if better is Subject or BehaviourSubject.
Thanks to everybody who answered. I have found a solution. I have changed the isSiteManager() method to a method which checks all four permissiontypes. This method is executed in the then() block and affects four variables for each permissiontype. These variables i can reach from other components.
Looks like this:
#Injectable()
export class PermissionService {
isSiteManager: boolean;
isSiteConsumer: boolean;
isSiteContributor: boolean;
isSiteCollaborator: boolean;
userId : string;
constructor(private apiService: AlfrescoApiService, private authService: AuthenticationService) {
this.init();
}
init() {
this.isSiteCollaborator = false;
this.isSiteConsumer = false;
this.isSiteContributor = false;
this.isSiteManager = false;
this.userId = localStorage.USER_PROFILE;
//proof permission of user
this.apiService.sitesApi.getSiteMember(SITENAME, this.userId).then(resp=>{
if(resp.entry.role === "SiteManager"){
this.isSiteManager = true;
}else if(resp.entry.role === "SiteConsumer"){
this.isSiteConsumer = true;
}else if(resp.entry.role === "SiteContributor"){
this.isSiteContributor = true;
}else{
this.isSiteCollaborator = true;
}
});
}
}
Now i can ask for the variables in other components like this:
export class AppLayoutComponent {
constructor(private permissionService : PermissionService) {
if(permissionService.isSiteManager){
console.log("You are Boss!");
}
}
}
You should call your service method synchrony. To do so, you have to map your response from the service:
your component code:
constructor() {
...
permissionService.isSiteManager().map(
response => {
isManager = response;
}
);
}
Something like this.
To call map operator import it before:
import 'rxjs/add/operator/map';
Related
Not an expert at angular far from it. But I've been looking deeply and i cant figure out why my other components that call the function types, runs before the constructor. and to solve it where do i put the " echo " function? everything works likes a charm except for the fact that echo is called before types. what or how do i make echo come first to run before any other function. i cant hook it up to the promise because it takes data from another component. i ran a if statement to check if the global variable exist and obviously doesn't because of the order of processes.
import { Injectable, OnInit, OnDestroy } from '#angular/core';
import { HttpClient, HttpHeaders } from '#angular/common/http'
import { Observable, of } from "rxjs";
import { Router, ActivatedRoute } from '#angular/router';
import { Location } from '#angular/common/'
import { DataService } from './products.service';
import { BehaviorSubject, Subscription } from 'rxjs';
import { map } from 'rxjs/operators';
#Injectable({ providedIn: "root" })
export class CartService implements OnInit, OnDestroy {
public data: any = { "productObjs": [] }
public array: any;
public datap: any;
private sub: Subscription;
//loop up the id if specexist remove thespec if empty remove id
constructor(public dataservice: DataService, private http: HttpClient) {
this.echo()
}
echo() {
let headers = new HttpHeaders();
headers.append('Content-Type', 'application/json');
let prom = new Promise((resolve, reject) => {
this.http.get('../assets/productCategories/products.json', { headers }).toPromise().then((data: any) => {
console.log(data)
var dat = this.datap
resolve(dat)
this.datap = data
return dat
}).then((dat) => { this.nextt(dat) });
})
return this.datap;
}
nextt(datap) {
console.log(datap)
this.datap = datap
}
// types is called from another component and runs before promise finishes
types(type) {
if (this.datap) {
console.log(this.datap)
let that = this
var func = type.func
var rtype = type.type
var arr;
switch (func) {
case "random":
return that.sortByRandom()
break;
case "category":
return that.sortByCategory(rtype)
break;
default: "specific"
return that.sortBySpecific(rtype)
}
console.log(this.array)
console.log(this.array)
console.log(this.array)
console.log(this.datap)
return this.array;
}
}
getArray() {
if (this.datap) {
console.log(this.array)
return this.array
}
}
sortBySpecific(specific) {
let that = this
console.log(that.datap)
var type = that.datap.product.filter(function(objj) {
return objj.type === specific;
})
return type
}
sortByCategory(category) {
let that = this
var type = this.datap.product.filter(function(objj) {
return objj.productCategory === category;
})
return type
}
sortByRandom() {
var cats = []
var picked = []
var sproducts;
let that = this
this.datap.productObjs.forEach((obj) => {
var randomnum2 = Math.floor(Math.random() * this.datap.productObjs.length)
cats.push(obj.category)
})
var randomnum = Math.floor(Math.random() * cats.length)
var selectedCats = this.datap.product.filter(function(objj) {
return objj.productCategory === cats[randomnum];
});
sproducts = selectedCats
var x = sproducts[Math.floor(Math.random() * sproducts.length)]
picked.push(x)
that.array = picked
return picked
}
addToCart(ps, pobj) {
var checkarray = this.data.productObjs.filter(function(obj) {
return obj.productSpec === ps;
});
console.log(checkarray)
if (checkarray.length <= 0) {
this.data.productObjs.push(pobj)
}
}
getItems() {
return this.data.productObjs
}
clearCart() {
this.data.productObjs = []
}
clearProduct(objspec) {
var newarray = this.data.productObjs.filter(function(obj) {
return obj.productSpec !== objspec;
});
this.data.productObjs = newarray;
}
changeInventory() {
//update pricing from inputs
}
checkout() {
this.http.post('http://localhost:4201/api', this.data).subscribe((res) => {
console.log(res)
var json = res
if (json['bool'] === "false") {
//cant check out
// this checks inventory also.
//pop up error problem with pricing.
}
if (json['bool'] === "true") {
//can check out
//neeeds to request paypal to send productinfo and once payment response is succeded send valid, and delete from database.
}
})
}
ngOnInit() {
}
ngOnDestroy() {
this.sub.unsubscribe();
console.log(this.sub)
console.log(this.datap)
}
}
Check this article for how to initialize global data:
https://www.cidean.com/blog/2019/initialize-data-before-angular-app-starts/
BTW, you should never call business logic in a constructor like:
this.echo()
Instead you should call it in the component it need the data, maybe ngOnInit in that component when it is needed.
It is usually recommended to use constructor only for dependency injection mainly. For other initialization consider using Angular life cycle hooks (Difference between constructor and ngOnInit).
Since you want to run your function echo() which gets called from child component you can call it from ngOnInit() in child component.
Then if you have anything that needs to be called from parent to child. You can call child from parent components ngAfterViewInit() method. (In your case types() function)
I am working with Ionic and I want to push an array of an object, when an event is emitted.
I have this
export class PublicationService {
constructor(
private storage: Storage
){}
private addPublicationSubject = new BehaviorSubject<PublicationModel>(new PublicationModel());
data = this.addPublicationSubject.asObservable();
publishData(data: PublicationModel) {
this.addPublicationSubject.next(data);
}
}
Here, event is emitted
savePublication() {
this.newPublication.id_pub = 1;
this.newPublication.textPub = this.f.text.value;
this.newPublication.user_sender = 'Juance';
this.newPublication.datetime = "asd";
this.pubService.publishData(this.newPublication);
}
And on my home page the event is listen (in ngOnInit)
// Variable defined in the component
publications: PublicationModel[] = [];
//ngOnInit
this.pubService.data.subscribe((data) => {
if (data != null) {
console.log(data);
this.publications.push(data);
}
});
Now my problem is: when I try to push the data into the array it tells me it cannot read property of null (this.publications).
When entering the subscribe of the event, it does not take the variable as defined in the component. Any ideas?
EDIT:
My component HomePage
#Component({
selector: 'app-home',
templateUrl: 'home.page.html',
styleUrls: ['home.page.scss']
})
export class HomePage implements OnInit {
viewAddPublication: boolean;
publications: PublicationModel[] = [];
countLikePub: number;
addPublication: any;
constructor(
private storage: Storage,
private navCtrl: NavController,
private pubService: PublicationService) {
this.publications = new Array<PublicationModel>();
}
ngOnInit() {
this.publications = [];
this.pubService.data.subscribe((data) => {
if (data != null) {
console.log(data);
this.setData(data);
}
}
);
this.viewAddPublication = false;
this.countLikePub = 0;
this.storage.get('publications').then((val) => {
this.publications = val;
});
}
setData(data) {
this.publications.push(data);
}
goToAddPub() {
this.navCtrl.navigateForward('/add-publication', {
animated: true,
animationDirection: "forward",
});
}
public likedPost(event) {
console.log();
let like = (document.getElementById(event.target.id) as HTMLElement);
like.style.color = '#0277bd';
this.countLikePub++;
}
debug mode in chrome
I need a way to push an array in real time and this is the only way I could think of, the other is to use Socket.io
I think maybe you are setting publications to null because of this function:
this.storage.get('publications').then((val) => {
this.publications = val;
});
You could change it a little bit to make sure publications are still an array
this.storage.get('publications').then((val) => {
this.publications = val || [];
});
I added this.publications = val || []; which is creating an empty array if val is not defined
I have a drag-and-drop.service that depends on lists.service.
The d&d service looks like this:
#Injectable({
providedIn: "root"
})
export class DragAndDropService {
private lists: any[] = null;
private dropListSubject = new BehaviorSubject<IDropListState>({
Lists: this.lists,
IsOpen: false,
Delay: 0
});
constructor(private ls: ListsService) {
// TODO: Subscription breakes the drag and drop functionallity
this.ls.onListsChanged().subscribe(x => {
this.lists = x;
console.log(x);
this.emitDropListState();
});
}
onDropListStateChange() {
return this.dropListSubject.asObservable();
}
open() {
this.emitDropListState(true);
}
close(delay: number) {
this.emitDropListState(false, delay);
}
add(id: string) {
this.ls.addNewList(id);
this.emitDropListState();
}
remove(listId: string) {
this.ls.deleteList(listId);
this.emitDropListState();
}
emitDropListState(isOpen = false, delay = 0) {
this.dropListSubject.next({
Lists: this.lists,
IsOpen: isOpen,
Delay: delay
});
}
}
the lists.service looks like this:
#Injectable({
providedIn: "root"
})
export class ListsService {
private listsFromStorage: BehaviorSubject<any[]>;
private lists: any[];
private STORAGE_LIST_NAME = "lists";
constructor() {
this.lists = JSON.parse(
window.localStorage.getItem(this.STORAGE_LIST_NAME)
);
this.listsFromStorage.asObservable();
this.listsFromStorage = new BehaviorSubject<any[]>(this.lists);
}
onListsChanged() {
return this.listsFromStorage.asObservable();
}
deleteList(listName: string) {
this.lists = this.lists.filter(x => {
return x.Name !== listName.trim();
});
this.saveAndEmmit();
}
addNewList(id: string) {
//TODO: List adding todo
console.log("TODO: Add List: id-", id);
this.saveAndEmmit();
}
saveAndEmmit() {
window.localStorage.setItem(
this.STORAGE_LIST_NAME,
JSON.stringify(this.lists)
);
this.listsFromStorage.next(this.lists);
}
}
NOW: When i use the angular material d&d stuff, this error is thrown:
When I remove the subscription in the d&d service, it doesn't throw the error.
Is this approach reasonable or how else can I imitate this behaviour ?
Thanks in advance!
I get an error that my this.worldCities is undefined and that I cannot apply find on it with the following code
export class SelectCityModalPage {
worldCities : Array<City>;
chosenCity:City;
myForm;
constructor(public navCtrl: NavController, public navParams: NavParams,
public appCitiesProvider:AppCitiesProvider, public viewCtrl:ViewController,
public formBuilder:FormBuilder) {
this.worldCities = this.appCitiesProvider.worldCities;
this.chosenCity = new City();
this.chosenCity.name = "";
console.log("In modal the world cities are " + this.worldCities);
this.myForm = formBuilder.group({
cityControl: ['Start typing...', this.checkIfValidCity]
//cityControl:['']
});
}
ionViewDidLoad() {
console.log('ionViewDidLoad SelectCityModalPage');
}
closeModal(){
this.chosenCity = this.worldCities.find((element) =>{
return element.name == this.chosenCity.name;
});
this.viewCtrl.dismiss(this.chosenCity);
}
checkIfValidCity(control : FormControl){
let validation = this.worldCities.find((element) => {
return element.name == control.value;
});
return validation != undefined;
}
}
And my provider is like that :
export class AppCitiesProvider {
errorMessage;
worldCities:Array<City>;
constructor(public http: Http, errorHandler:ErrorHandler) {
console.log('Hello Cities Provider');
this.getWorldCities2()
.subscribe(
(cities) => {
this.worldCities = cities;
console.log("in provider the worldCities are " + this.worldCities);
},
(error: any) => this.errorMessage = <any>error);
;
}
getWorldCities2() {
return this.http.get('../assets/city.list.json')
.map(res => res.json());
}
}
What I don't understand is that the appCitiesProvider.worldCities is initialized when called on a previous page, so this should not be the issue.
Also when I don't have the formbuilder in the code, I don't have any issue. The issue is really appearing because of the checkIfValidCity function.
Do you know where that comes from and how to solve it ?
Thanks a lot!
Methods outside your constructor loses the context of the class, therefore using 'this' will mean the method and not necessarily the class. If you want your methods to use the context of the class you will need to bind it in your constructor.
Add
this.checkIfValidCity = this.checkIfValidCity.bind(this)
to your constructor.
I have a a function that returns an Observable<Person[]>, Person is my model:
export interface Person {
id: string;
age: number;
}
Now in my component.ts im calling this function and I want to retrieve that array so I can display it in the html.
the array that comes back example:
[{"id":"13434 1","age":21},{"id":"34334 1","age":27}]
I have 2 buttons that call the same function.
I have three methods in my component.ts that triggered when that buttons where clicked, and this is where I want to set some variable to hold the returned value of that Observable. I'v tried to do something but it didnt work...
this are the functions:
#Injectable()
export class MyCmp implements OnInit {
listOneData: Observable<Person[]>;
listTwoData: Observable<Animal[]>;
showListOne = false;
showListTwo = false;
constructor(private _myService: MyService) {
};
public showListOneData(): void {
this.showListOne = true;
this.showListTwo = false;
this._myService.getListOneData().subscribe(res => {
this.listOneData = res;
})
}
public showListTwo(): void {
this.showListTwo = true;
this.showListOne = false;
this._myService.getListTwoData().subscribe(res => {
this.listTwoData = res;
})
}
}
this.listTwoData = res; this line does not compile, its because im assigning Person[] to listOneData: Observable<Person[]>;, but even if I take of the Observable<> it dosent work.
Can someone please explain to me what woill be an efficiant way to do it? I dont want to do it async cause I want to send an array to the html, and base on the code where should I do unsubscribe?
thanks allot!
Sounds like you want two different things; data from the observable and a subscription object so you can unsubscribe:
#Injectable()
export class MyCmp implements OnInit {
listOneData: Person[];
listTwoData: Animal[];
listOneSubscription: Subscription;
listTwoSubscription: Subscription;
showListOne = false;
showListTwo = false;
constructor(private _myService: MyService) {
};
public showListOneData(): void {
this.listOneSubscription = this._myService.getListOneData().subscribe(res => {
this.showListOne = true;
this.showListTwo = false;
this.listOneData = res;
})
}
public showListTwo(): void {
this.listTwoSubscription = this._myService.getListTwoData().subscribe(res => {
this.showListTwo = true;
this.showListOne = false;
this.listTwoData = res;
})
}
}
Then, wherever you're wanting to unsubscribe:
this.listOneSubscription && this.listOneSubscription.unsubscribe();
this.listTwoSubscription && this.listTwoSubscription.unsubscribe();
You also mentioned you don't want to do it async; that doesn't make sense in JavaScript since this is asynchronous; ask yourself, what do you want to show to the user while the content is loading? You probably want to show some sort of progress indicator, so that should be built into your view.