One Observer not being created, but another identical one is - angularjs

Using rxjs with Angular 2 RC.1 and I'm creating a few Observer's. I have the following in a service:
taxRates$: Observable<TaxRate[]>;
taxTables$: Observable<TaxTable[]>;
private _taxRatesObserver: Observer<TaxRate[]>;
private _taxTablesObserver: Observer<TaxTable[]>;
constructor(private http: HttpService) {
this.taxRates$ = new Observable<TaxRate[]>(observer =>
this._taxRatesObserver = observer
).share();
this.taxTables$ = new Observable<TaxTable[]>(observer =>
this._taxTablesObserver = observer
).share();
// more stuff here
}
The _taxRatesObserver is created correctly, but _taxTablesObserver is not. When I inspect the dev tools, I see this:
_taxRatesObserver:Subscriber
http:HttpService
taxRates$:RefCountObservable
taxTables$:RefCountObservable
I'd expect to also see _taxTablesObserver:Subscriber but if I log it via the console, it always shows as undefined. TaxTable, if it matters, looks like this:
export class TaxTable {
constructor(
public id: number,
public name: string,
public description: string) {}
}

From docs:
subscribe the function that is called when the Observable is initially subscribed to.
_taxRatesObserver and _taxTablesObserver are initialized after subscription to taxRates$ and taxTables$ correspondingly.
Most probably taxTables$ does not have subscribers.

Related

Faked database is missing x properties, however those properties are private

I'm trying to write some tests for my code. I'm using dependancy injection, and I'm trying to create a faked version of my database to be used when running tests.
I'm using the keyword implements to define my faked database, however I'm getting typescript errors due to the fact that this faked DB is missing certain properties, however, those properties are private, and never used outside the class
Here's an example:
class Database {
private client: MongoClient;
public async getData(query: string): Promise<{ name: string }> {
return await this.client.db('db').collection('collection').findOne({ name: query });
}
}
class MockDatabase implements Database {
public async getData(query: string): Promise<{ name: string }> {
return {
name: 'Jo'
}
}
}
function makeApp(database: Database) {
console.log(`Here's your app`);
}
const fakeDB = new MockDatabase();
const app = makeApp(fakeDB)
Typescript will error both when declaring MockDatabase, as well as when using it in the makeApp function.
Property 'client' is missing in type 'MockDatabase' but required in type 'Database'
How should I approach faking a database or another service like this?
A Database needs to have the client property, and because the property is private, that means you can only get a valid Database from the Database constructor. There is no way to "mock" a Database with a different declaration, because private properties need to come from the same declaration in order to be compatible. This restriction is important, because private properties are not completely inaccessible from outside the object; they are accessible from other instances of the same class. See TypeScript class implements class with private functions for more information.
Anyway, instead of trying to mock a Database, you should consider creating a new interface which is just the "public part" of Database. It would look like this:
// manually written out
interface IDatabase {
getData(query: string): Promise<{ name: string }>
}
You can make the compiler compute this for you, because the keyof operator only returns the public property names of an object type:
// computed
interface IDatabase extends Pick<Database, keyof Database> { }
The type Pick<Database, keyof Database> uses the Pick<T, K> utility type to select just the public properties of Database. In this case that's just "getData", and so the computed IDatabase is equivalent to the manual one.
And now we change references to Database to IDatabase anywhere we only care about the public part:
class MockDatabase implements IDatabase {
public async getData(query: string): Promise<{ name: string }> {
return {
name: 'Jo'
}
}
}
function makeApp(database: IDatabase) {
console.log(`Here's your app`);
}
const fakeDB = new MockDatabase();
const app = makeApp(fakeDB)
And everything works as expected.
Playground link to code

RxJS Observables, when to unsubscibe?

I have a couple of questions about Angular. I recently started to experiment with Angular and really cant get a grip of when I should unsubscribe, there is obviously recommended to use the AsyncPipe but in some cases there is no way to use it.
If I subscribe to a HTTP request in a service, does the Observable unsubscribe itself or is it persistent through out the application lifetime?
When I subscribe (without the AsyncPipe) to a HTTP request in a component I could manually unsubscribe in the ngOnDestroy lifecycle hook which is fine but in my case I have a submit method to create account
account.component.html
<account-item>
*ngFor="let account of collection$"
[data]="account"
</account-item>
account.component.ts
public collection$: Observable<Account[]>;
private subscription: Subscription;
constructor(
private service: AccountService
) {}
ngOnInit() {
this.collection$ = this.service.getAll()
}
createAccount(obj) {
this.subscription = this.service.create(obj)
.subscribe(
success => this.collection$ = this.service.getAll(),
error => Observable.throw(error)
);
}
ngOnDestroy() {
this.subscription.unsubscribe();
}
From what I know the subscription is now persistent until my AccountComponent is destroyed, but is there a way to use AsyncPipe here instead or is it better for me to subscribe in the service itself?
I've read something about finite and infinite Observables but haven't really understand when a Observable is finite or not.
Another problem I'm facing is that in success => this.collection$ = this.service.getAll() my UI doesn't update with the new account list when I use ChangeDetectionStrategy.OnPush but works just fine with ChangeDetectionStrategy.Default
This is the service method that fetches the account data
getAll() {
return this.http.get(ENDPOINT_ACCOUNT)
.map((response: Response) => response.json().data)
}
What you need to think when dealing with Observables and functionnal programming more globaly is that you don't describe how things are done but describe what things are.
In your example, you collection is the combination of on the one hand the initial fetch from the service and on the other hand, all updates that may occur, so if you want to avoid subscribing in the component, you can do such a thing:
class Foo {
public collection$: Observable < Account[] > ;
private createAccount$ = new Subject<Account>();
constructor(
private service: AccountService
) {}
ngOnInit() {
let initialAccounts = this.service.getAll().share();
let accountUpdate = initialAccounts.switchMap(()=>this.createAccount$.switchMap(account=>{
return this.service.create(account).switchMap(()=>this.service.getAll())
}))
this.collection$ = Observable.merge(initialAccounts,accountUpdate);
}
createAccount(obj:Account) {
this.createAccount$.next(obj);
}
}
We are using here the merge operator to take the data from either initialAccounts or createAccount$. It is always a good thing to combine your observables together and subscribe once, because this way you don't have to imperatively manage your subscription.
The fact is most of the time, you don't need to subscribe() at all.

How do I use the factory pattern if I'm migrating from Angular 1.5.8 to Angular 2?

If I'm migrating to Angular 2 and I want to use the factory pattern to create a transient dependency (a dependency that doesn't share state between components in which it gets injected), what is the best way to register a service in angular 1.5.8 with plans to migrate to ng2's way of registering services
I know in angular 2, components can reinstantiate services by passing them to the providers array, but that option doesn't exist in angular 1, and it seems like .factory is all but deprecated in angular 2 since the docs don't give it much love
An example would be a TabulationService that manages the state of which tab a user is viewing on a dashboard, which would obviously not share state between components. I would want to reinstantiate the service in each component it gets injected into. But I also want to avoid using .factory if Angular 2 best practices seem to shy away from using it.
Here is the "hack" I've resorted to, but I don't like it because even though it gets me type hinting and statelessness for my service, I can't use dependency injection in the object that gets created and i have to manage the state of my service when it gets injected and when the component in which it gets injected is destroyed (by manually clearing the service's state):
tab-manager.service.ts:
import { TabManager } from './tab-manager.class';
export class TabService {
manager;
public initialize(tabs: string[], defaultTab: string) {
this.manager = new TabManager(tabs, defaultTab);
}
}
tab-manager.class.ts:
import { includes } from 'lodash';
const mandatory = (param) => { throw new Error(`${ param } is a required field in Tab Manager!`) };
export class TabManager {
tab: string;
constructor(public tabs: string[] = mandatory(`tabs`), public defaultTab: string = mandatory('defaultTab')) {
this.checkTab(defaultTab);
this.tab = defaultTab;
}
public switchTab(tab) {
const self = this;
self.checkTab(tab);
self.tab = tab;
}
private checkTab(tab: string) {
const self = this;
if (!includes(self.tabs, tab)) {
throw new Error(`{ tab } is not a valid tab. Available tabs are ${ self.tabs.join(',') }`);
}
}
}
The service then gets initialized by importing the TabManager service from tab-manager.service.ts and calling `angular.service('TabService', TabManagerService)
There's nothing to blame Angular 2 documentation on. Angular 1 factory service is implemented in Angular 2 DI as useFactory provider.
It doesn't really matter here if it is factory or service in this case. Both serve the same purpose and share the same behaviour - they are singletons in Angular 1. Similarly, useFactory and useClass providers are singletons within the same injector in Angular 2.
To achieve the desired behaviour uniformly in both frameworks a dependency should be instantiated after injection. It doesn't really matter if this is done with factory or constructor function - they should be defined as value service in Angular 1 or useValue provider in Angular 2.
For Angular 1 it will be:
export class TabulationService { ... }
export type TTabulationService = typeof TabulationService;
...
app.value('TabulationService', TabulationService);
app.component('some', {
controller: class {
tabulationService: TabulationService;
static $inject = ['TabulationService'];
constructor (TabulationService: TTabulationService) {
this.tabulationService = new TabulationService();
}
}
});
And for Angular 2:
providers: [{ provide: TabulationService, useValue: TabulationService }]
...
#Component(...)
export class SomeComponent {
tabulationService: TabulationService;
constructor (#Inject(TabulationService) TabulationService: TTabulationService) {
this.tabulationService = new TabulationService();
}
}

How to store the result of a Firebase query to a variable from a promise?

I'm new to angular and still trying to get the hang of promises.
I have an ItemDetail class:
#Component({
templateUrl: 'build/pages/item-detail/item-detail.html',
providers: [Auth]
})
export class ItemDetailPage {
private title;
private description;
private author;
constructor(private navParams: NavParams, private _auth: Auth) {
this.title = this.navParams.get('item').title;
this.description = this.navParams.get('item').description;
_auth.getUser(this.navParams.get('item').author).then(function(snapshot){
console.log("ayyy lmao = " + JSON.stringify(snapshot.child(navParams.get('item').author).val().email));
this.author = JSON.stringify(snapshot.child(navParams.get('item').author).val().email);
});
console.log("thisAuthor = " + this.author);
}
}
I'm trying to store the e-mail retrieved from the database as the author variable. Even though it is output correctly in the console, its value does not actually get assigned to the variable outside of the promise. However I think where I'm encountering an issue is to do with the promise, which is a concept I'm still trying to get my head around.
The getUser method in the Auth class is as follows:
getUser(uid: string): any {
return firebase.database().ref('/userProfile').orderByKey().equalTo(uid).once("value", function(snapshot){});
}
If there is a better way to do this (without promises), that'd be a solid alternative too.
Thanks!
seem like the promise works fine, but this point to wrong instance, use Function.prototype.bind to bind this to ItemDetailPage:
function successHandler(snapshot){ ... };
_auth.getUser(this.navParams.get('item').author).then(successHandler.bind(this));
A few things...
A better way to get the user profile information is to use the uid as the key when you insert the object
firebase.database().ref('/userProfile/' + uid).once("value", function(snapshot){});
If you use => fat arrows, the binding is done for you in ionic2
_auth.getUser(this.navParams.get('item').author)
.then((snapshot) => {
// THIS WILL BE WHAT YOU EXPECT...
});

How to access the user Token in an injected service to reencode passwords?

I have the below code where I am trying to re-encode passwords as users log in (the database has bee migrated form a legacy website). However, I'm not sure what I'm doing wrong as I keep getting errors:
Attempted to call an undefined method named "forward" of class "AppBundle\Service\HubAuthenticator".
I have set things up as follows:
security.yml
security:
encoders:
AppBundle\Entity\Member:
id: club.hub_authenticator
services.yml
services:
//This should be central service than then calls the second
club.hub_authenticator:
class: AppBundle\Service\HubAuthenticator
club.password_rehash:
class: AppBundle\Service\PasswordRehash
Hubauthenticator.php
namespace AppBundle\Service;
use Symfony\Component\Security\Core\Encoder\PasswordEncoderInterface;
class HubAuthenticator extends \Symfony\Component\Security\Core\Encoder\BCryptPasswordEncoder implements PasswordEncoderInterface
{
function __construct($cost=13)
{
parent::__construct($cost);
}
function isPasswordValid($encoded, $raw, $salt)
{
// Test for legacy authentication (and conditionally rehash the password stored in the database if true)
if ($this->comparePasswords($encoded, sha1("saltA".$raw."saltB"))) {
$this->forward('club.password_rehash:rehash');
}
// Test for Symfony's Bcrypt authentication (any passwords just rehashed in previous step should work here)
if (parent::isPasswordValid($cost=13, $encoded,$raw,$salt)) return true ;
}
}
PasswordRehash.php
namespace AppBundle\Service;
use Symfony\Component\Security\Core\Encoder\PasswordEncoderInterface;
class PasswordRehash extends \Symfony\Component\Security\Core\Encoder\BCryptPasswordEncoder
{
// Customises BCryptPasswordEncoder class to use legacy SHA method
function rehash($member, $raw, $salt)
{
//Salt is null as Symfony documentation says it is better to generate a new one
parent::encodePassword($member->getPlainPassword, $salt=null ) ;
}
}
Some other previous attempts for completeness:
My guess is that the problem is that I am misunderstanding what objects are available to me. My understanding is that the user hasn't been authenticated at this point so have tried and removed the below attempts:
Trying to inject the $member into the HubAuthenticator service:
function __construct($cost=13)
{
parent::__construct($cost, \Member $member);
}
When trying to get the plainpassword to rehash:
$this->get('security.context')->getToken()->getUser()->getPlainPassword();
In your services, you can only access what dependencies you've injected.
So, to access the current user object, you need to pass it as argument:
service:
club.password_rehash:
class: AppBundle\Service\PasswordRehash
arguments: [ "#security.token_storage" ]
Constructor:
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
class HubAuthenticator extends \Symfony\Component\Security\Core\Encoder\BCryptPasswordEncoder implements PasswordEncoderInterface
{
private $storage;
function __construct($cost = 13, TokenStorageInterface $storage)
{
parent::__construct($cost);
$this->storage = $storage;
// Now you can use:
// $user = $this->storage->getToken()->getUser();
}
}
Then, to access the second service, same way, inject it.
Add it to the service arguments:
club.password_rehash:
class: AppBundle\Service\PasswordRehash
arguments: [ "#security.token_storage", "#club.password_rehash" ]
Add it to your constructor:
private $storage;
private $passwordRehash
function __construct($cost = 13, TokenStorageInterface $storage, PasswordRehash $passwordRehash)
{
parent::__construct($cost);
$this->storage = $storage;
$this->passwordRehash = $passwordRehash;
// Now you can use:
// $this->passwordRehash->rehash(...);
}
Hope this helps you.

Resources