I've been using Subject from rxjs library to make an event emitter in my angular applications but I'm not able to use it in Angular 6 as
This module isn't present there
Cannot find module 'rxjs/Subject'
Is being thrown.
Is there a new way for what I've been doing like this.
import { Subject } from 'rxjs/Subject';
private Login = new Subject<boolean>();
Then doing this this.Login.next(true)
I was able to change state in my project of the user:
loginService.Login.subscribe(state => {
console.log('Login State')
console.log(state)
})
How to do the above stuff in Angular 6
In Angular 6, you can do it like this:
import { Subject } from 'rxjs'; // or import { Subject } from 'rxjs/index';
// ....
private Login = new Subject<boolean>();
// ...
loginService.Login.subscribe(state => {
// ... your code
})
Related
I'm using NestJS as the framework for a client API. Within the framework we are using a pretty standard Passport/JWT auth infrastructure that is working fine. Our AuthGuard is firing when the bearer token is found and, in secure API endpoints, I can inject the HTTP context via '#Res() request' and get access to the 'request.user' property which contains the payload of my Jwt token.
On top of this we are attempting to implement a 'RolesGuard' in a very similar fashion to the sample code provided in the documentation and some of the sample projects on GitHub (none of which actually use this guard but they include it as a sample guard).
Our issue is that our AuthGuard fires and validates the Jwt token and THEN our RolesGuard fires but the request object it is passed does not have the user meta-data attached to the request.
The key code in our RolesGuard is:
const request = context.switchToHttp().getRequest();
const user = request.user;
if (!user) {
return false;
}
In the above snipped the user is always false. Has anyone written a role/permission based guard in Nest that successfully gets access to the scope of the current user? All the code is firing and everything appears registered correctly.
-Kevin
Ultimately this appears to be an ordering issue with the guards and it doesn't look like it can be easily resolved (without the framework allowing some control over the ordering).
My hope was to register the RolesGuard globally but that causes it to be registered first and fire first.
#UseGuards(AuthGuard('jwt'), RolesGuard)
#Roles('admin')
If I register it at the endpoint level and put it after the AuthGuard then it fires second and I get the user context I am expecting within the guard itself. It isn't perfect but it works.
-Kevin
register RoleGuard at the endpoint level and put it after the AuthGuard then it fires second and I get the user context I am expecting within the guard itself.
don't register RoleGuard at module causes it'll be registered first and fire first.
*.module.ts
imports: [],
providers: [{provide: APP_GUARD, useClass: RolesGuard} ,], // remove guard
controllers: [],
exports: [],
Make your RolesGuard extend AuthGuard('StrategyName') and then call super.canActivate for example:
#Injectable()
export class RolesGuard extends AuthGuard('jwt') {
async canActivate(context: ExecutionContext): Promise<boolean> {
// call AuthGuard in order to ensure user is injected in request
const baseGuardResult = await super.canActivate(context);
if(!baseGuardResult){
// unsuccessful authentication return false
return false;
}
// successfull authentication, user is injected
const {user} = context.switchToHttp().getRequest();
}
}
In other words you have to Authenticate first then Authorize
If anyone else stumbles across this question: putting multiple guards into one #UseGuards decorator works, but if you want to keep them separated (say, if you use a custom decorator), you can give the 2nd guard access to req.user by placing it before the #UseGuards call that puts the user on the request object, as in this example:
#RestrictTo(UserAuthorities.admin)
#UseGuards(JwtAuthGuard)
#Get("/your-route")
It seems that this is a consequence of how decorators work in TypeScript.
You can also use multiple roles for role-based Authentication.
In UserResolver
import { Args, Mutation, Query, Resolver } from '#nestjs/graphql';
import { UseGuards } from '#nestjs/common';
import { RolesGuard } from 'src/guards/auth.guard';
#UseGuards(new RolesGuard(['admin']))
#Resolver()
export class UserResolver { ... }
In RolesGuard
import { ExecutionContext, Injectable, UnauthorizedException } from '#nestjs/common';
import { ExecutionContextHost } from '#nestjs/core/helpers/execution-context-host';
import { GqlExecutionContext } from '#nestjs/graphql';
import { AuthGuard } from '#nestjs/passport';
#Injectable()
export class RolesGuard extends AuthGuard('jwt') {
constructor(private roles: string[] | null) {
super();
}
canActivate(context: ExecutionContext) {
const ctx = GqlExecutionContext.create(context);
const { req } = ctx.getContext();
return super.canActivate(new ExecutionContextHost([req]));
}
handleRequest(err: any, user: any, info: string) {
if (!this.roles) {
return user || null;
}
if (!user) {
throw new UnauthorizedException('Not Valid User.');
}
const role = user.role;
const doesRoleMatch = this.roles.some(r => r === role);
if (!doesRoleMatch) {
throw new UnauthorizedException('Not Valid User.');
}
return user;
}
}
I have some data that I want to be shared with my entire app so I have created a service like so..
user.service
userDataSource = BehaviorSubject<Array<any>>([]);
userData = this.userDataSource.asObservable();
updateUserData(data) {
this.userDataSource.next(data);
}
then in my component Im getting some data from an api and then sending that data to userDataSource like so..
constructor(
private: userService: UserService,
private: api: Api
){
}
ngOnInit() {
this.api.getData()
.subscribe((data) => {
this.userService.updateUserData(data);
})
}
now that all works but.. I want to be able to add data to the end of the array inside the userDataSource so basically the equivalent of a .push am I able to just call the updateUserData() function and add more data or will doing that overwrite what is currently in there?
Any help would be appreciated
You can add a new method to your service like addData in which you can combine your previous data with new data like.
import {Injectable} from '#angular/core';
import {BehaviorSubject} from 'rxjs/BehaviorSubject';
#Injectable()
export class UserService {
userDataSource: BehaviorSubject<Array<any>> = new BehaviorSubject([]);
userData = this.userDataSource.asObservable();
updateUserData(data) {
this.userDataSource.next(data);
}
addData(dataObj) {
const currentValue = this.userDataSource.value;
const updatedValue = [...currentValue, dataObj];
this.userDataSource.next(updatedValue);
}
}
For someone that may come accross this issue with a BehaviorSubject<YourObject[]>.
I found in this article a way to properly add the new array of YourObject
import { Observable, BehaviorSubject } from 'rxjs';
import { YourObject} from './location';
import { Injectable } from '#angular/core';
#Injectable({
providedIn: 'root'
})
export class ObjService {
private theObjData: BehaviorSubject<YourObject[]> = new BehaviorSubject<YourObject[]>(null);
constructor() {
}
public SetObjData(newValue: YourObject[]): void {
this.theObjData.next(Object.assign([], newValue));
}
}
How to update data:
// inside some component
this.api.userData().subscribe((results:YourObject) =>
this.objService.SetObjData(results);
)
How to observe changes on other component
// inside another component
ngOnInit() {
this.objService.GetAccountStatements().subscribe((results) =>
...
)
}
Normally Observables and Subjects are meant to be streams of data, not an assignment of data. BehaviorSubjects are different because they hold their last emitted value.
Normally Subjects or BehaviorSubjects inside of a contained class (like a Service) do not want to expose themselves publicly to any other classes, so it's best practice to access their properties with getters or methods. This keeps the data stream cold to all subscribers.
However, since the BehaviorSubject holds the last emitted value, there's a few options here. If all subscribers need a concatenated stream of data from every emission, you could access the last emitted value and append to it:
userDataSource = BehaviorSubject<any[]>([]);
userData = this.userDataSource.asObservable();
updateUserData(data) {
this.userDataSource.next(this.userDataSource.value.push(data));
}
...or, in what might be considered better practice, Subscribers to this Subject could do their own transformation on the stream:
this.api.userData()
.scan((prev, current) => prev.push(current). [])
.subscribe((data) => {
this.concatenatedUserData = data;
});
Use concat to add object
userDataSource = BehaviorSubject<Array<any>>([]);
updateUserData(data) {
this.userDataSource.next(this.userDataSource.value.concat(data));
}
Use filter to remove object
removeUserData(data) {
this.userDataSource.next(this.userDataSource.value.filter(obj => obj !== data));
}
I have an Angular 2/4 service which uses observables to communicate with other components.
Service:
let EVENTS = [
{
event: 'foo',
timestamp: 1512205360
},
{
event: 'bar',
timestamp: 1511208360
}
];
#Injectable()
export class EventsService {
subject = new BehaviorSubject<any>(EVENTS);
getEvents(): Observable<any> {
return this.subject.asObservable();
}
deleteEvent(deletedEvent) {
EVENTS = EVENTS.filter((event) => event.timestamp != deletedEvent.timestamp);
this.subject.next(EVENTS);
}
search(searchTerm) {
const newEvents = EVENTS.filter((obj) => obj.event.includes(searchTerm));
this.subject.next(newEvents);
}
}
My home component is able to subscribe to this service and correctly updates when an event is deleted:
export class HomeComponent {
events;
subscription: Subscription;
constructor(private eventsService: EventsService) {
this.subscription = this.eventsService.getEvents().subscribe(events => this.events = events);
}
deleteEvent = (event) => {
this.eventsService.deleteEvent(event);
}
}
I also have a root component which displays a search form. When the form is submitted it calls the service, which performs the search and calls this.subject.next with the result (see above). However, these results are not reflected in the home component. Where am I going wrong? For full code please see plnkr.co/edit/V5AndArFWy7erX2WIL7N.
If you provide a service multiple times, you will get multiple instances and this doesn't work for communication, because the sender and receiver are not using the same instance.
To get a single instance for your whole application provide the service in AppModule and nowhere else.
Plunker example
Make sure your Component is loaded through or using its selector. I made a separate component and forgot to load it in the application.
This is just me trying to get an understanding of the lifecycle and try and perform some logic before loading the component so I found the CanActivate Annotation.
I have a function in the Component that I want to call so I need the Component; I have injected it... it seems overly complex.
// HomeComponent Component
#Component({
selector: 'HomeComponent',
template: '<h2>HomeComponent Us</h2>'
})
#CanActivate((next,prev) => {
let injector: any = Injector.resolveAndCreate([HomeComponent]);
let theComponent: HomeComponent = injector.get(HomeComponent);
return theComponent.canActivate(next,prev)
})
class HomeComponent {
data = {}
myresolver: any
constructor() {
console.log("in constructor", this.data)
}
setData(data: any) {
this.data = data
}
canActivate(nextInstr, currInstr) {
console.log("in my resolver");
var that = this;
return new Promise(function(resolve, reject) {
setTimeout(function() {
var data = { data: "Aaron" };
that.setData(data)
resolve(data);
}, 2000)
});
}
}
In fact you can't since processing within the annotation is called right before instantiating (or not) the component.
I guess that you want to use the resolve feature of Angular1 router into Angular2. In fact, such feature isn't supported yet. For more details you can have a look at this issue in the Angular github:
https://github.com/angular/angular/issues/4015
The two following links give you some workarounds for your use case:
Angular 2 - equivalent to router resolve data for new router
Using Resolve In Angular2 Routes
Hope it helps you,
Thierry
Does anyone know how to do a basic unit test using angular 2 to test a basic firebase add item.
I'm using typescript instead of basic JavaScript for my code
This is what I'm testing:
export class AppComponent {
ref: Firebase;
refUsers: Firebase;
refProfiles: Firebase;
constructor(){
this.ref = new Firebase("https://markng2.firebaseio.com");
this.refUsers = new Firebase("https://markng2.firebaseio.com/users");
this.refProfiles = new Firebase("https://markng2.firebaseio.com/profiles");
}
public addUser(newUser: Object): void{
this.refUsers.push(newUser, ()=>{
});
}
}
This is my current test:
import {it, iit, describe, expect, inject, injectAsync, beforeEachProviders, fakeAsync, tick } from 'angular2/testing';
import { AppComponent } from '../app/app';
describe('AppComponent', () => {
it('saves an item to Firebase', () => {
let refUsers = new Firebase('');
let service = new AppComponent();
spyOn(service.refUsers, 'push');
service.addUser({ item: true });
expect(service.refUsers.push).toHaveBeenCalled();
})
});
This is the error I'm getting when I run that test:
Three steps to begin testing.
Setup your testing environment. The Angular 2 docs have a great guide on doing so.
Write your code.
Write the test.
Let's say you create a class called DataService:
/// <reference path="typings/firebase/firebase.d.ts" />
export class DataService {
ref: Firebase;
constructor(theRef: Firebase) {
this.ref = theRef;
}
add(object: any) {
this.ref.push(object);
}
}
To test it, you can import DataService and use Jasmine methods to test that the add method.
import {DataService} from './data-service';
describe('DataService', () => {
it('saves an item to Firebase', () => {
let ref = new Firebase('');
let service = new DataService(ref);
// create a spy to use if push is called
spyOn(service.ref, 'push');
service.add({ item: true });
// expect that push was called
expect(service.ref.push).toHaveBeenCalled();
})
});
The key to testing Firebase methods is just to spy on them. You don't need to test that Firebase works, just that your code calls Firebase properly.
The problem here is that you're using the full Firebase SDK in your unit tests. Ideally you'd want to use a mocked library, so you can create a mock for whatever functionality you need from the Firebase SDK.