My users can like a list of heroes, so I have this structure in my firebase rules/datas:
"user_flags": {
"$uid": {
".write": "auth.uid == $uid",
".read": "auth.uid == $uid",
"liked": {
"$heroIdx": {
".validate": "newData.isString()"
}
}
}
}
In my code I want to subscribe to the "liked heroes" ref, so that's what I do:
import { Injectable } from '#angular/core';
import { AngularFireDatabase } from 'angularfire2/database';
import { Observable } from 'rxjs/Observable';
import { AngularFireAuth } from 'angularfire2/auth';
#Injectable()
export class UserFlagsService {
likedHeroes$: Observable<string[]>;
constructor(
private afAuth: AngularFireAuth,
private db: AngularFireDatabase
) {
this.likedHeroes$ = afAuth.authState.flatMap(user => {
return user && user.uid
? this.db.list(`user_flags/${user.uid}/liked`)
.map(heroes => heroes.map(hero => <string>hero.$value))
: Observable.of([])
});
}
}
Everything works fine until the user signs out... Even with the check on user and user.uid the query user_flags/MY_ID_HERE/liked seems to be triggered and I get a "permission denied".
I tried to use subscribe and watch for signout to unsubscribe but it didn't work either... The query was still triggered and failed with "permission denied"
How should I handle this ? I want my service to return a reliable observable so I can subscribe to it in my components.
Thanks a lot for your help
I am assuming that you want to ensure that the data you are rendering in the view disappears on sign out?
If this is the case, I would suggest using the switchMap operator from RXJS and the following pattern:
this.userProvider = this.afAuth.authState;
this.likedHeroes$ = this.userProvider.switchMap((auth) => {
if(auth){
return this.af.list('user_flags/' + auth.uid + '/liked');
}
});
You need to add the following to import the switchMap operator:
import 'rxjs/add/operator/switchMap';
Hit me with some comments if you want me to fill this out some more or if my assumption about what you're trying to achieve is incorrect. I've been trying to figure out the best way to do this stuff as well.
You should also check out this video from the Angular Firebase YouTube, it might help you with some of the issues in your question.
I managed to make it work by creating a BehaviorSubject and unsubscribing the event before the "signout" is triggered.
Here is my provider:
import { Injectable } from '#angular/core';
import { AngularFireDatabase } from 'angularfire2/database';
import { Observable } from 'rxjs/Observable';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { Subscription } from 'rxjs/Subscription';
import { UserProvider } from '../../providers/user/user';
#Injectable()
export class UserFlagsProvider {
likedHeroes$: Observable<string[]>;
subHeroesLiked: Subscription;
constructor(
protected userProvider: UserProvider,
protected db: AngularFireDatabase
) {
const heroesLikedSubject: BehaviorSubject<string[]> = new BehaviorSubject([]);
this.likedHeroes$ = heroesLikedSubject.asObservable();
this.userProvider.user$.subscribe(user => {
if (user) {
this.subHeroesLiked = this.db.list(`user_flags/${user.uid}/liked`).subscribe(heroesSlugs => {
heroesLikedSubject.next(heroesSlugs.map(hero => <string>hero.$key));
});
}
});
this.userProvider.signingOut$.subscribe(() => {
this.subHeroesLiked.unsubscribe();
});
}
}
And my userProvider
import { Injectable } from '#angular/core';
import { Observable } from 'rxjs/Observable';
import { Subject } from 'rxjs/Subject';
import { AngularFireAuth } from 'angularfire2/auth';
#Injectable()
export class UserProvider {
user$: Observable<firebase.User>;
user: firebase.User;
signingOut$: Subject<any> = new Subject();
constructor(private afAuth: AngularFireAuth) {
this.user$ = afAuth.authState;
this.user$.subscribe(user => this.user = user);
}
// [...]
signout() {
this.signingOut$.next();
return this.afAuth.auth.signOut();
}
}
Hope it helps someone.
FYI, this doesn't work (for now) with angularfire2-offline for an unknown reason.
If someone sees a better way to do it I'm interested
Related
My app has stopped from receiving push notifications.
I don't know what else I can do to check it.
It was working a few months ago
Stopped working for both iOS and Android
Installed and checked app permissions many times
Firebase is initializing, getting token and registering in my database fine
Tried test messages on Firebase console with generated token and it didn't work as well
Here is my notification.service.ts code:
import { Injectable } from '#angular/core';
import { Platform, Events } from '#ionic/angular';
import { HttpClient } from '#angular/common/http';
import { Storage } from '#ionic/storage';
import { BehaviorSubject } from 'rxjs';
import { tap } from 'rxjs/operators';
import { FirebaseX } from "#ionic-native/firebase-x/ngx";
import { environment } from '../../environments/environment';
import { AppService } from './app.service';
import { AuthService } from './auth.service';
#Injectable({
providedIn: 'root'
})
export class NotificationService {
fcm_token:any = null;
constructor(
private platform: Platform,
private http:HttpClient,
private appService:AppService,
private firebase: FirebaseX,
public events: Events,
public authService:AuthService,
) { }
async initialize(){
console.log('notificationService.initialize()');
let self = this;
let platforms = this.platform.platforms();
if(platforms.includes('ios') || platforms.includes('android')){
this.firebase.getToken().then(token => {
if(platforms.includes('ios')){ this.firebase.grantPermission(); }
if(platforms.includes('android')){ }
self.saveToken(token);
}).catch(error => console.error('FIREBASE Error getting token', error));
this.firebase.onTokenRefresh().subscribe((token: string) => { self.saveToken(token); });
this.firebase.onMessageReceived().subscribe(data => { });
}else{
// using browser, do nothing
}
}
async saveToken(token){
console.log('notificationService.saveToken()', token);
if(this.authService.is_logged == true){
this.fcm_token = token;
return this.http.post(environment.api_url+'/user/save-fcm-token', {fcm_token:this.fcm_token}, {headers:{'Authorization':'Bearer '+this.authService.auth_token}}).toPromise();
}
}
}
What else can I try?
Is there some place when I can see sending/receiving push errors?
Following Google's official Angular 4.3.2 doc here, I was able to do a simple get request from a local json file. I wanted to practice hitting a real endpoint from JSON placeholder site, but I'm having trouble figuring out what to put in the .subscribe() operator. I made an IUser interface to capture the fields of the payload, but the line with .subscribe(data => {this.users = data}) throws the error Type 'Object' is not assignable to type 'IUser[]'. What's the proper way to handle this? Seems pretty basic but I'm a noob.
My code is below:
import { Component, OnInit } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { IUsers } from './users';
#Component({
selector: 'pm-http',
templateUrl: './http.component.html',
styleUrls: ['./http.component.css']
})
export class HttpComponent implements OnInit {
productUrl = 'https://jsonplaceholder.typicode.com/users';
users: IUsers[];
constructor(private _http: HttpClient) { }
ngOnInit(): void {
this._http.get(this.productUrl).subscribe(data => {this.users = data});
}
}
You actually have a few options here, but use generics to cast it to the type you're expecting.
// Notice the Generic of IUsers[] casting the Type for resulting "data"
this.http.get<IUsers[]>(this.productUrl).subscribe(data => ...
// or in the subscribe
.subscribe((data: IUsers[]) => ...
Also I'd recommend using async pipes in your template that auto subscribe / unsubscribe, especially if you don't need any fancy logic, and you're just mapping the value.
users: Observable<IUsers[]>; // different type now
this.users = this.http.get<IUsers[]>(this.productUrl);
// template:
*ngFor="let user of users | async"
I'm on the Angular doc team and one open todo item is to change these docs to show the "best practice" way to access Http ... which is through a service.
Here is an example:
import { Injectable } from '#angular/core';
import { HttpClient, HttpErrorResponse } from '#angular/common/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/throw';
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/do';
import 'rxjs/add/operator/map';
import { IProduct } from './product';
#Injectable()
export class ProductService {
private _productUrl = './api/products/products.json';
constructor(private _http: HttpClient) { }
getProducts(): Observable<IProduct[]> {
return this._http.get<IProduct[]>(this._productUrl)
.do(data => console.log('All: ' + JSON.stringify(data)))
.catch(this.handleError);
}
private handleError(err: HttpErrorResponse) {
// in a real world app, we may send the server to some remote logging infrastructure
// instead of just logging it to the console
let errorMessage = '';
if (err.error instanceof Error) {
// A client-side or network error occurred. Handle it accordingly.
errorMessage = `An error occurred: ${err.error.message}`;
} else {
// The backend returned an unsuccessful response code.
// The response body may contain clues as to what went wrong,
errorMessage = `Server returned code: ${err.status}, error message is: ${err.message}`;
}
console.error(errorMessage);
return Observable.throw(errorMessage);
}
}
The component would then look like this:
ngOnInit(): void {
this._productService.getProducts()
.subscribe(products => this.products = products,
error => this.errorMessage = <any>error);
}
I am integrating Nestjs with firebase admin, the controller is not updating the view. With the service there is not problem, update in real time.
Someone will have some suggestion, what is my wrong in the code?
The Service that inject the controller is:
import { Injectable } from '#nestjs/common';
import * as admin from 'firebase-admin';
export interface Customer {
direction: string,
codLegal: string,
phone: string,
name: string
}
export interface CustomerId extends Customer{
id: string;
}
#Injectable()
export class CustomerService {
constructor() {}
findCustomers(): Promise<any>{
return new Promise((resolve, reject) => {
admin.firestore().collection('/data/LYvBew5FDpjLqcQjA2Ra/info')
.onSnapshot((querySnapshot) => {
const promises: any = [];
querySnapshot.forEach((doc: any) => {
promises.push({
id: doc.id,
data: doc.data() as Customer,
});
});
console.log(promises);
resolve(promises);
})
});
}
}
**The basic controller is: **
import { Controller, Get } from '#nestjs/common';
import { CustomerService } from './services/customer.service';
#Controller('customers') export class CustomerController {
constructor(private readonly customerService: CustomerService) {
}
#Get()
async findAll() {
try {
return await this.customerService.findCustomers();
}catch(err) {
console.log(err);
}
}
}
Talking in terms of HTTP, the controller will not update the view. The view is rendered once you call the findAll route and send to the client.
If you want to show updates to the view in realtime, you should include firebase into your frontend.
New to Angular 2, still trying to get my head around certain things. Where I am stuck is I have login service and login component. I send a login request from the login component to the login service to post the username and password to a login API. If successful it posts the token to the localstorage. Where I am stuck is after the token is sent storage I want to return a boolean response back to the login component. Based on the boolean response it will perform execute a function in the component.
I can do everything until I get the response. I don't know how to handle a response back to the login component. Appreciate if someone could point me in the right direction. My code as follows:
LOGIN SERVICE
import { Injectable } from '#angular/core';
import { Token } from './login';
import { APIDOMAIN } from '../../../shared/api';
import { Http, Response, Headers, RequestOptions } from '#angular/http';
import { Observable } from 'rxjs/Rx';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/toPromise';
#Injectable()
export class LoginService {
url: string = APIDOMAIN;
constructor(private http: Http) { }
login(username: string, password: string) {
console.log('Login API');
let headers = new Headers();
let data = null;
headers.append("Authorization", "Basic " + btoa(username + ":" + password));
headers.append("Content-Type", "application/x-www-form-urlencoded");
this.http.post(this.url+ '/login', data, {headers: headers})
.map(res => res.json())
.subscribe(
token => { console.log(token); localStorage.setItem('id_token',token.token); },
err => { console.log(err);},
() => console.log('Request Complete')
);
}
logout(): void {
localStorage.removeItem('id_token');
}
}
LOGIN COMPONENT
import { Component, OnInit } from '#angular/core';
import { LoginService } from './shared/login.service';
import { Http, Response } from '#angular/http';
import { Observable } from 'rxjs/Rx';
#Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit {
username: string;
password: string;
constructor(private loginService: LoginService) { }
ngOnInit() {}
login(): void {
this.loginService.login(this.username,this.password)
// PEFORM SOME FUNCTION BASED BOOLEAN RESPONSE
}
}
Here's one solution:
export class LoginService {
status: EventEmitter<boolean> = new EventEmitter();
login(username: string, password: string) {
this.http.post(...)
.map(res => res.json())
.subscribe(token => {
console.log(token);
localStorage.setItem('id_token',token.token);
this.status.emit(true);
});
logout() {
localStorage.removeItem('id_token');
this.status.emit(false);
}
}
export class LoginComponent implements OnInit {
constructor(private loginService: LoginService) { }
ngOnInit() {
this.loginService.status.subscribe(console.info);
}
}
I don't know what I'm doing wrong but somehow i'm not able to read data, though the data is coming from server in response and even the data is getting showed inside service extractData method when I'm putting the console but in component inside subscribe function it is giving me undefined. Help me what I'm doing wrong, what I'm assuming is that this is the problem of async but, I have no idea how correct it.
Any help will be appreciable.
Thanx in advance
Component.ts
import { Component, Input, OnInit } from '#angular/core';
import {AdminService} from './admin.service';
import {logistics} from '../shared/model/logistics';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/distinctUntilChanged';
import 'rxjs/add/operator/switchMap';
import 'rxjs/add/operator/toPromise';
import { Observable } from 'rxjs/Observable';
import {Response } from '#angular/http';
#Component({
moduleId:module.id,
selector: 'admin',
templateUrl: 'admin.component.html',
styleUrls:['admin.component.css'],
providers:[AdminService]
})
export class AdminComponent implements OnInit{
#Input() public allocatedAssetsList: logistics[];
mode = 'Observable';
public errorMsg = '';
constructor(private adminService: AdminService) {
}
ngOnInit(){
this.listByEmpId("123");
}
listByEmpId(empId:string){
this.adminService.getAllocatedAssets(empId).subscribe(
res => this.allocatedAssetsList = res,
error => this.errorMessage = <any>error);
}
}
Service.ts
import { Injectable } from '#angular/core';
import { Http, Response } from '#angular/http';
import { Hero } from './hero';
import { Observable } from 'rxjs/Observable';
import { Headers, RequestOptions } from '#angular/http';
import {logistics} from '../shared/model/logistics';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/distinctUntilChanged';
import 'rxjs/add/operator/switchMap';
import 'rxjs/add/operator/toPromise';
#Injectable()
export class AdminService {
constructor (private http: Http) {}
private listAssetsURL = '/api/logistics/list/'; // URL to web API
private extractData(res: Response) {
let body = res.json();
return body || { };
}
private handleError (error: any) {
// In a real world app, we might use a remote logging infrastructure
// We'd also dig deeper into the error to get a better message
let errMsg = (error.message) ? error.message :
error.status ? `${error.status} - ${error.statusText}` : 'Server error';
console.error(errMsg); // log to console instead
return Observable.throw(errMsg);
}
getAllocatedAssets (empId: string): Observable<logistics[]> {
this.listAssetsURL+= empId;
//let body = JSON.stringify({ empId });
let headers = new Headers({ 'Content-Type': 'application/json' });
let options = new RequestOptions({ headers: headers });
return this.http.get(this.listAssetsURL)
.map(this.extractData)
.catch(this.handleError);
}
}
listByEmpId(empId:string){
this.adminService.getAllocatedAssets(empId).subscribe(
res => {
this.allocatedAssetsList = res;
console.log(this.allocatedAssetsList);
},
error => this.errorMessage = <any>error);
}
This is probably because you are trying to access your allocatedAssetsLists before the data is actually returned from the service.
If you are accessing it in your template you can use a simple ngIf to make sure you only try to display it once the data is available.
If this is not it, we need more information on your problem to help.