Angular component property inexplicably remains "undefined" after being set in subscribe() - javascript

Let me just preface this by saying yes, i'm using arrow functions to retain the scope of "this" (as far as I can tell anyway).
I have two properties on my component:
IsAdmin (boolean)
currentRole (string)
I make an api call to fetch user roles from my backend via Angular's HttpClient, and I have a callback subscribe-method which updates above mentioned properties with the result.
However, while I can assign the role value to currentRole, the other property IsAdmin remains undefined even as I assign it, and I get no error in my f12 debugger or visual studio code via the chrome plugin.
import { Component, OnInit } from "#angular/core";
import { AuthorizeService, IUser } from "../authorize.service";
import { Observable } from "rxjs";
import { map, tap } from "rxjs/operators";
import { HttpClient } from "#angular/common/http";
#Component({
selector: "app-login-menu",
templateUrl: "./login-menu.component.html",
styleUrls: ["./login-menu.component.scss"]
})
export class LoginMenuComponent implements OnInit {
isAuthenticated: Observable<boolean>;
public userName: Observable<string>;
IsAdmin : boolean;
currentRole : string;
constructor(private authorizeService: AuthorizeService, private http : HttpClient) {
}
ngOnInit() {
this.isAuthenticated = this.authorizeService.isAuthenticated();
this.userName = this.authorizeService.getUser().pipe(map(u => u && u.name));
const endpoint = '.../api/User/User/GetRoles';
this.authorizeService.getUser()
.subscribe(data => {
this.userNameSignedIn = data.name;
});
this.http.get<string[]>(endpoint).
subscribe(result => {
this.currentRole = result[0];
console.log("this.currentRole ", this.currentRole); //prints "admin"
this.IsAdmin == result.includes("admin");
console.log("this.IsAdmin", this.IsAdmin); //prints "undefined"
}, error => console.error(error));
}
}
Console output is as following:
logon-menu.component.ts:37 this.currentRole admin
logon-menu.component.ts:39 this.IsAdmin undefined
What on earth is going on here? What am I doing wrong?

the problem in your subscribe is that you are using ==(comparison) instead of = (assignation)
subscribe(result => {
this.currentRole = result[0];
console.log("this.currentRole ", this.currentRole); //prints "admin"
this.IsAdmin == result.includes("admin"); //<-- here is an error
console.log("this.IsAdmin", this.IsAdmin); //prints "undefined"
},
your code should be:
subscribe(result => {
this.currentRole = result[0];
this.IsAdmin = result.includes("admin");
},

Related

Shared service with BehaviorSubject not emitting correct value

I am trying to implement a shared service for managing Roles on my app, with an Observable so that, from other components, you can either change the current role and be notified when it changes. The problem I have is that when I publish a new value through the service, the components that subscribe to the observable always recieve the same value (the initial one). Then, I never receive the new role number and I can't update the component state.
Apparently
I have the following set of components:
RolesService: The shared Service, which manages role change, gets the available roles from the user token, manages persistence of the current role for the logged in user. It uses localStorage to persist the role index. It receives changes
HeaderComponent: This is an example of a component receiving changes for the role change, because it needs to update the title of the user. It subscribes to the observable and changes the title accordingly
EscullRolComponent: And this is an example of a component that changes the role the user is currently using (by action of the user, of course). It has some buttons and sends to the service the new index.
Here is the relevant code for this issue:
// RolesService file
#Injectable()
export class RolesService {
private _currentRole: BehaviorSubject<Rol> = new BehaviorSubject(null);
currentRole = this._currentRole.asObservable();
private get currentIndex(): number {
const ras = localStorage.getItem('current_role');
// console.log("Guardat aixo: ", ras);
if (ras === '' || ras === null || ras === 'NaN' || ras === '-1' || parseInt(ras, 10) === NaN) {
return 0;
} else {
return parseInt(ras, 10);
}
}
private set currentIndex(val) {
localStorage.setItem('current_role', val.toString());
}
currentToken: NbAuthJWTToken;
constructor(private authService: NbAuthService,
private http: HttpClient) {
// console.log(this.currentRole);
this.authService.getToken().subscribe((token: NbAuthJWTToken) => {
if (token.isValid()) {
this.currentToken = token;
console.log("Executing token change");
this.setRolActual(0);
}
});
}
protected publishCurrentRol(i: number): void {
console.log("Publishing rol id: ", i); // LOG 1
this._currentRole.next(this.getUserRoles()[i]);
}
setRolActual(i: number) {
this.publishCurrentRol(i);
this.currentIndex = i;
}
}
The following is the component the user has to change the role, and that calls the service with the new role.
#Component({
templateUrl: 'escull-rol.component.html',
styleUrls: ['escull-rol.component.scss'],
})
export class EscullRolComponent {
rols: Array<Rol> = [];
actual: number;
constructor( private rolesService: RolesService,
private route: ActivatedRoute,
private router: Router,
private location: Location ) {
this.rols = this.rolesService.getUserRoles();
this.actual = this.rolesService.getRolActualIndex();
}
buttonRolClicked(index: number) {
this.rolesService.setRolActual(index);
this.router.navigate(['inici']);
// console.log('Boto del rol ' + index + ' clicat');
}
}
And here the header, which changes its state depending on the role:
#Component({
selector: 'ngx-header',
styleUrls: ['./header.component.scss'],
templateUrl: './header.component.html',
})
export class HeaderComponent implements OnInit {
#Input() position = 'normal';
user: any = {};
picture: string;
client: stream.Client;
logoPath = '';
logoEra = '';
rol: string;
ids: Array<string>;
constructor(private sidebarService: NbSidebarService,
/* ... more injections .. */
private imatgesService: ImatgesService,
private notificacionsService: NotificacionsService) {
this.logoEra = 'assets/images/logoEra.png';
this.authService.onTokenChange()
.subscribe((token: NbAuthJWTToken) => {
if (token.isValid()) {
if (token.getValue()) {
this.user = token.getPayload(); // Posem les dades del token a l'objecte user
// console.log('token changed, changing user in header', token);
}
}
}, error => {
console.error('Error en carregar el token en el header');
throw error;
});
this.rolesService.currentRole.subscribe((rol: Rol) => {
// This subscription here should receive the change from the service
if(rol) {
console.log("Changing rol on header to ", rol.getIndex()); // LOG 2
this.rol = rol.getTitle();
this.ids = rol.getRolIDs();
}
});
this.imatgesService.getProfileImagePath().subscribe((path: string) => {
this.picture = path;
}, (err) => {
this.picture = '';
});
}
}
The behaviour that I'm seeing is, the EscullRol component calling the setRolActual(id) method with the new id, and then the service calling its internal method publishCurrentRole(id) with the same id, so at LOG 1 I can see the expected outoput. But then immediately next I can see the output form LOG 2 at the Header Component with the wrong id, which is always the number that we had initially saved at the localStorage when the app started up.
I don't really know if the issue is with how I use the observables, with the service-component communication model or with how components and observables are initailsed and treated in angular.
Few thing to try
First make your service as a singleton using
#Injectable({ providedIn: "root" })
Improvement
Also, make sure that the service is not provided on child modules, as that would create their own instance and it wouldn't be a singleton anymore. If there are more than one instance of the service, the Subject-Observer pattern will not work across all the app.
Then this code
currentRole = this._currentRole.asObservable();
You should create a function to return the data not defined as an variable like
getCurrentRole() {
return this._currentRole.asObservable();
}

how to subscribe the Id of the created object after POST operation

I have an component where i am adding a new object called customer by calling the api like this:
public onAdd(): void {
this.myCustomer = this.customerForm.value;
this.myService.addCustomer(this.myCustome).subscribe(
() => { // If POST is success
this.callSuccessMethod();
},
(error) => { // If POST is failed
this.callFailureMethod();
},
);
}
Service file:
import { HttpClient } from '#angular/common/http';
import { Injectable } from '#angular/core';
import { Observable, Subject } from 'rxjs';
import {ICustomer } from 'src/app/models/app.models';
#Injectable({
providedIn: 'root',
})
export class MyService {
private baseUrl : string = '....URL....';
constructor(private http: HttpClient) {}
public addCustomer(customer: ICustomer): Observable<object> {
const apiUrl: string = `${this.baseUrl}/customers`;
return this.http.post(apiUrl, customer);
}
}
As shown in component code, i have already subscribed the api call like this:
this.myService.addCustomer(this.myCustome).subscribe(
() => { // If POST is success
.....
},
(error) => { // If POST is failed
...
},
);
But,I want to subscribe the results in another component, I have tried like this:
public getAddedCustomer() {
this.myService.addCustomer().subscribe(
(data:ICustomer) => {
this.addedCustomer.id = data.id; <======
}
);
}
I am getting this lint error: Expected 1 arguments, but got 0 since i am not passing any parameter.
What is the right approach to subscribe the api call in other components? after POST operation.
Because i want to get added object id for other functionality.
Well it totally depends on the design of your application and the relation between components. You can use Subjects for multicasting the data to multiple subscribers.
import { HttpClient } from '#angular/common/http';
import { Injectable } from '#angular/core';
import { Observable, Subject } from 'rxjs';
import { ICustomer } from 'src/app/models/app.models';
#Injectable({
providedIn: 'root',
})
export class MyService {
private baseUrl : string = '....URL....';
private latestAddedCustomer = new Subject();
public latestAddedCustomer$ = this.latestAddedCustomer.asObservable()
constructor(private http: HttpClient) {}
public addCustomer(customer: ICustomer): Observable<object> {
const apiUrl: string = `${this.baseUrl}/customers`;
return this.http.post(apiUrl, customer).pipe(map((data) => this.latestAddedCustomer.next(data)));
}
}
and subscribing to the subject as follows
this.latestAddedCustomer$.subscribe()
should get you the latest added customer details. Even though i would not do this the way its written. I would basically write a seperate service to share the data between the components or would write a cache service if its used across the application. But the idea here is to use the concept of Subjects. You can read more about it Here

TypeError: Cannot read property 'markForCheck' of undefined Angular6 Impure Pipe Testing

I am trying to test an impure pipe using jasmine. The pipe works fine on ng serve and does its intended job of animating text.
When I am creating an instance of it in my test cases and trying to get the tranform method run I get error.
NaturalTypePipe > transforms "abc" to "abc"
TypeError:
Cannot read property 'markForCheck' of undefined
at
NaturalType../src/app/shared/pipes/natural-type.pipe.ts.NaturalType.typeNextCharacter
My test case file is below:-
import { ChangeDetectorRef, NgZone } from '#angular/core';
import { NaturalType } from './natural-type.pipe';
describe('NaturalTypePipe', () => {
let changeDetector: ChangeDetectorRef;
let ngZone: NgZone;
let pipe: NaturalType;
beforeEach(() => {
pipe = new NaturalType(changeDetector, ngZone);
});
it('should create an instance of natural pipe', () => {
expect(pipe).toBeTruthy();
});
it('transforms "abc" to "abc"', () => {
expect(pipe.transform('abc')).toBe('abc');
});
});
My pipe code is as follows:-
import { Pipe, PipeTransform, ChangeDetectorRef, NgZone } from '#angular/core';
/*
* Animating text as if it was being typed by a user
*/
#Pipe({name: 'naturalType', pure: false})
export class NaturalType implements PipeTransform {
private typed: string = '';
private target: string = '';
private currentIndex: number = -1;
private timeoutHandle: number = -1;
constructor( private changeDetector: ChangeDetectorRef, private ngZone: NgZone ) { }
transform(value: string, mintypingSpeed: number = 30): any {
if (this.target !== value) {
clearTimeout(this.timeoutHandle);
this.typed = '';
this.currentIndex = -1;
this.target = value;
this.typeNextCharacter(mintypingSpeed);
}
return this.typed;
}
private typeNextCharacter(mintypingSpeed: number) {
this.currentIndex++;
this.typed = this.target.substr(0, this.currentIndex);
this.changeDetector.markForCheck();
if (this.typed !== this.target) {
const time = Math.round(Math.random() * 70) + mintypingSpeed;
this.ngZone.runOutsideAngular(() => {
this.timeoutHandle = <any> setTimeout(()=> {
this.ngZone.run(() => this.typeNextCharacter(mintypingSpeed));
},time);
});
}
}
}
My initial thought was this may be due to private constructor variables and private typeNextCharacter method in pipe file and I tried a few things but was not successful.
Any help would be appreciated. Thanks in advance.
The changeDetector variable is never being initialized. So, the line:
pipe = new NaturalType(changeDetector, ngZone);
in the beforeEach block is creating a new NaturalType with an undefined changeDetector.

How to solve time raise problems in Angular?

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';

Angular 2 Facebook Login

I am developing an website that needs to be logged in with Facebook account. I am using Angular 2 and, of course, TypeScript. It works But not exactly what I wanted. I can't take back the user's information.
Let's go to the code:
import {Component} from 'angular2/core';
import {Main} from './pages/main/main';
declare const FB: any;
#Component({
selector: 'my-app',
templateUrl: 'app/app.html',
directives: [Main]
})
export class AppComponent implements OnInit {
token: any;
loged: boolean = false;
user = { name: 'Hello' };
constructor() { }
statusChangeCallback(response: any) {
if (response.status === 'connected') {
console.log('connected');
} else {
this.login();
}
}
login() {
FB.login(function(result) {
this.loged = true;
this.token = result;
}, { scope: 'user_friends' });
}
me() {
FB.api('/me?fields=id,name,first_name,gender,picture.width(150).height(150),age_range,friends',
function(result) {
if (result && !result.error) {
this.user = result;
console.log(this.user);
} else {
console.log(result.error);
}
});
}
ngOnInit() {
FB.getLoginStatus(response => {
this.statusChangeCallback(response);
});
}
}
Basically, When the page loads I check if the user is logged in to Facebook, if not, I call the login method. The me method is used to fetch the users information, like its name, first name etc. When I logged in condition browser console print the following line:
Object {id: "666", name: "Paulo Henrique Tokarski Glinski", first_name: "Paulo", gender: "male", picture: Object…}
Everything ok! But I want to get that Object and put into a User object! Something like that:
me method:
this.user = result;
console.log(this.user);
But the user just exists inside the method. If I print it outside, its returns nothing.
I just want to print the users name etc. at the website page. I did almost the same thing with Angular JS and worked well.
Please! Help me!
you can use fat arrow functions to use the same context ...
login() {
FB.login((result: any) => {
this.loged = true;
this.token = result;
}, { scope: 'user_friends' });
}
For the facebook javascript SDK, You just add the following line in your index.html
<script src="//connect.facebook.net/en_US/sdk.js"></script>
and in ngOnInit() :
`FB.init({
appId : 'your-app-id',
cookie : false,
xfbml : true, // parse social plugins on this page
version : 'v2.5' // use graph api version 2.5
});`
Angular 2 Service level implementation
import {Injectable} from '#angular/core';
import { Location } from '#angular/common';
import { Http, Response, Headers, RequestOptions,URLSearchParams } from '#angular/http';
import { Observable } from 'rxjs/Observable';
import { ConfigService } from "app/core/services/config.service";
import 'rxjs/add/observable/throw';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';
#Injectable()
export class AuthService {
constructor(private http: Http,
private configProvider:ConfigService) {
}
authenticateFacebook(){
window.location.href = 'https://www.facebook.com/v2.9/dialog/oauth?client_id='+
this.configProvider.config.facebook.clientId +
'&redirect_uri='+ this.configProvider.config.facebook.redirectURI + '&scope=public_profile';
}
getAccessToken(authenticationCode: string){
var authProviderUrl = 'https://graph.facebook.com/v2.9/oauth/access_token';
var authParameters = {
client_id: this.configProvider.config.facebook.clientId,
redirect_uri: this.configProvider.config.facebook.redirectURI,
client_secret: this.configProvider.config.facebook.clientSecret,
code: authenticationCode
};
var params = [];
for (var k in authParameters) {
params.push(k + '=' + authParameters[k]);
}
var authOpenURI = authProviderUrl + '?' + params.join('&');
return this.http.get(authOpenURI)
.map(res => res.json())
.catch(err => Observable.throw(err));
}
getUserFacebookProfile(accessToken:string):Observable<any>{
var fields = ['id', 'email', 'first_name', 'last_name', 'link', 'name','picture.type(small)'];
var graphApiUrl = 'https://graph.facebook.com/v2.5/me?fields=' + fields.join(',');
return this.http.get(graphApiUrl+'&access_token='+accessToken+'')
.map(res => res.json())
.catch(err => Observable.throw(err));
}
Caller level function, this code will be in the component of your redirect URI
//Facebook authentication check
if (window.location.href.indexOf("code") > -1){
var code = window.location.href.substring(window.location.href.indexOf("?") + 1).split('&')[0].split('=')[1];
this.getFaceBookProfile(code);
}
//Get profile from facebook
getFaceBookProfile(code:string){
this.authService.getAccessToken(code).subscribe(oathAccessData => {
this.authService.getUserFacebookProfile(oathAccessData.access_token).subscribe(profile => {
this.userProfile = new UserProfile(profile.name,profile.email, profile.picture.data.url,"facebook",
profile.id);},err => { console.log(err); });},err => { console.log(err);});
this.router.navigate(['/dashboard']);
}
this has a bunch of magic involved. Does it help if you capture the class's this in a variable and use that in the callbacks (so as to avoid caring what their this is)?
e.g.
login() {
var self = this;
FB.login(function(result) {
self.loged = true;
self.token = result;
}, { scope: 'user_friends' });
}

Categories