Callback for FirebaseObservable - javascript

I have a simple setup with some code that counts the number of items in my FireBase database.
This is working but the button is empty until the data is loaded.
Is there a way to hide the button until this is done? Or set a loading state until this is done?
export class ButtonComponent implements OnInit {
likes: FirebaseListObservable<any[]>;
constructor(private angularFire: AngularFire) {}
ngOnInit() {
this.getLikes();
}
getLikes() {
this.likes = this.angularFire.database.list('/likes');
}
}
This is my button template
<button (click)="onClicked()" class="button">
{{(likes | async)?.length}}
</button>
I tried subscribing() to it
export class ButtonComponent {
likes: FirebaseListObservable<any[]>;
constructor(private angularFire: AngularFire) {
this.angularFire.database.list('/likes').subscribe(
response => {this.likes = response}
);
}
onClicked() {
this.likes.push({'like': new Date().toISOString()})
}
}
But that returns the following error:
Type 'any[]' is not assignable to type 'FirebaseListObservable<any[]>

Related

Dialog box appear in bottom of page in angular

I want to show a dialog box in angular to confirm a delete action but the box does not appear and it appends to the end of the page
here is my dialog component ts file:
import { MatDialogRef, MAT_DIALOG_DATA } from '#angular/material/dialog';
export class DeleteBoxComponent implements OnInit {
constructor(private dialogRef: MatDialogRef<DeleteBoxComponent>,
#Inject(MAT_DIALOG_DATA) public data: CBookModel,
private service: BookService) { }
onNoClick(){
this.dialogRef.close();
}
onYesClick(){
this.service.delete(this.data.id).subscribe(response => {
this.dialogRef.close();
})
}
}
Here is html file of dialog component:
<div class="mat-container position-relative">
<h1 mat-dialog-title>DELETE</h1>
<mat-dialog-content class="mat-typography">
<p>Are you Sure you want to delete "{{data.title}}"</p>
</mat-dialog-content>
<mat-dialog-actions>
<button mat-button (click)="onNoClick()">No</button>
<button mat-button (click)="onYesClick()">Yes</button>
</mat-dialog-actions>
</div>
And here is another component that calls the dialog component:
import { DeleteBoxComponent } from './../delete-box/delete-box.component';
import { MatDialog } from '#angular/material/dialog';
export class BooksComponent implements OnInit {
constructor(private service: BookService,public router:Router, public dialog: MatDialog) { }
onClickDelete(book: CBookModel) {
const dialogRef = this.dialog.open(DeleteBoxComponent,
{width: '250px',backdropClass: 'backdropBackground', data: book});
dialogRef.afterClosed().subscribe( Response =>{
this.service.getAll()
.subscribe(data => this.books = data);
})
}
}
Based on your screenshot I think you have not configured Material styles.
Follow Getting Started and it will be set up.

How to assign global variable from subscribe() function and update the view

I am doing the task and I have a problem I can't assign value to global to be seen in the view in angular8. I know what is asynchronous but how to assign the value and update the view.
post-details.component.ts
export class PostDetailsComponent implements OnInit {
indexOfPost;
titles=[];
subscription: Subscription;
renderPost:boolean=false;
constructor(private activatedRoute:ActivatedRoute,private getSharedTitles:ShareInfoTitleService,private detectChanges: ChangeDetectorRef) {
this.subscription = this.getSharedTitles.getMessage().subscribe((title)=>{
this.titles=title.titles;
this.renderPost=true;
this.detectChanges.markForCheck();
});
}
ngOnInit() {
this.indexOfPost=this.activatedRoute.snapshot.paramMap.get("id");
}
}
post-details.component.html
<p *ngIf="renderPost">works</p>
It does not work. My aim is to show the titles in post-details.component.html. I'll be grateful for the tips.Regards
There is no need to write too much for a simple task. Just explore async pipe and it will automatically subscribe and unsubscribe.
export class PostDetailsComponent implements OnInit {
message$ = this.getSharedTitles.getMessage();
constructor(private activatedRoute:ActivatedRoute,private getSharedTitles:ShareInfoTitleService) {
}
ngOnInit() {
this.indexOfPost=this.activatedRoute.snapshot.paramMap.get("id");
}
}
In template
<p *ngIf="( message$ | async) as message">{{message.title}}</p>
You can use async pipe which unsubscribes auto-magically.
export class PostDetailsComponent implements OnInit {
indexOfPost;
titles: Observable<any> = this.getSharedTitles.getMessage();
constructor(private activatedRoute:ActivatedRoute,private getSharedTitles:ShareInfoTitleService,private detectChanges: ChangeDetectorRef) {
}
ngOnInit() {
this.indexOfPost=this.activatedRoute.snapshot.paramMap.get("id");
}
}
in template
<p *ngIf="(titles | async)">works</p>

Trigger cloned component when click on original in Angular

I have a loop on a component which represents a list of graph cards on my real app.
I have copied this component ( and loop it ) as the original
Hello Component
export class HelloComponent {
message:string;
printedMessage:string
#Input() elm:string;
constructor(private data: DataService, private router : Router) { }
ngOnInit() {
this.message = this.data.messageSource.value;
this.data.messageSource.subscribe(message => this.message = message)
}
updateService(){
this.data.changeMessage(this.message);
this.printedMessage=this.data.messageSource.value
}
navigateToSibling(){
this.router.navigate(['/sibling']);
}
}
app component
<div *ngFor="let elm of [1,2,3,4]">
<hello [elm]= "elm"></hello>
</div>
<h1>Copy </h1>
<div *ngFor="let elm of [1,2,3,4]">
<hello [elm]= "elm"></hello>
</div>
DataService component
export class DataService {
messageSource = new BehaviorSubject<string>("default message");
constructor() { }
changeMessage(message: string) {
this.messageSource.next(message)
}
}
Expected behaviour
What I would is when change the input value on the component 1 for example , only the value on the input of the copied component 1 changes.
Actual behaviour
Actually when I change a value inside an input all the other inputs are changings.
Here's a stackblitz example
Below is a solution that will solve you issue. This may not be a perfect solution but you need something similar.
hello.html
<h1>App component {{elm}}</h1>
<input type="text" [(ngModel)]="message">
<button (click)="updateService()" type="button">Save</button> {{printedMessage}}
Data Service
import {
Injectable
} from '#angular/core';
import {
BehaviorSubject
} from 'rxjs/BehaviorSubject';
#Injectable()
export class DataService {
messageSource = new BehaviorSubject < any > ("default message");
constructor() {}
changeMessage(message: string, elem: any) {
this.messageSource.next({
message: message,
elem: elem
});
}
}
HelloComponent
import {
Component,
Input
} from '#angular/core';
import {
DataService
} from "./dataService";
import {
Router
} from '#angular/router';
#Component({
selector: 'hello',
templateUrl: './hello.html',
styles: [`h1 { font-family: Lato; }`]
})
export class HelloComponent {
message: string;
printedMessage: string
#Input() elm: string;
constructor(private data: DataService, private router: Router) {}
ngOnInit() {
this.message = this.data.messageSource.value;
this.data.messageSource.subscribe(message => this.message = message.elem === this.elm ? message.message : this.message);
}
updateService() {
debugger
this.data.changeMessage(this.message, this.elm);
this.printedMessage = this.data.messageSource.value.message;
}
navigateToSibling() {
this.router.navigate(['/sibling']);
}
}
Have also updated the Stackblitz Demo. Hope this helps :)

Angular4 - let multiple unrelated components notify each other of the problem of updating data, and whether there is a cleaner coding method?

I have encountered a project in progress, let multiple unrelated components notify each other of the update data, is there a cleaner coding method?
There are 3 components (more likely later) and a common-data component. They have no parent-child relationship with each other and only show on the same screen.
The desired effect is to press the button of any component, update the contents of common-data, and notify yourself and other components to fetch new messages from common-data.
At present, my approach is to use Rx's Observable and Subscription, but they must be imported in the component.ts and service.ts files of each component, and a lot of duplicate code appears, it is very messy, I don't know what is better. practice?
Thanks!
My code :
The sample name is test-a-comp (a.b.c and so on, the code is the same)
test-a-comp.html
<p>
{{ownMessage}}
</p>
<button (click)="sendChange()">update</button>
test-a-comp.component
import { Component, OnInit } from '#angular/core';
import { Subscription } from 'rxjs/Subscription';
import { CommonData } from '../common-data/common-data';
import { TestACompService } from './test-a-comp.service';
import { TestBCompService } from '../test-b-comp/test-b-comp.service';
import { TestCCompService } from '../test-c-comp/test-c-comp.service';
#Component({
selector: 'app-test-a-comp',
templateUrl: './test-a-comp.component.html',
styleUrls: ['./test-a-comp.component.css']
})
export class TestACompComponent implements OnInit {
subscription: Subscription;
ownMessage;
constructor(
private testAService: TestACompService,
private testBService: TestBCompService,
private testCService: TestCCompService,
) {
this.subscription = this.testAService.getMessage()
.subscribe((test) => {
CommonData.message = test;
});
this.subscription = this.testBService.getMessage()
.subscribe(() => {
this.ownMessage = CommonData.message;
});
this.subscription = this.testCService.getMessage()
.subscribe(() => {
this.ownMessage = CommonData.message;
});
}
ngOnInit() {
}
sendChange() {
this.testAService.sendMessage();
}
}
test-a-comp.service:
import { Injectable } from '#angular/core';
import {Subject} from 'rxjs/Subject';
import {Observable} from 'rxjs/Observable';
import {Subscription} from 'rxjs/Subscription';
#Injectable()
export class TestACompService {
subscription: Subscription;
private subject = new Subject<any>();
constructor() {
}
getMessage(): Observable<any> {
return this.subject.asObservable();
}
sendMessage(): void {
this.subject.next('update message from A');
}
}
As far as i understand & you've mentioned in the above, there is a button in one of the component (test-a-component.html). If you update the button, you need to send message to other components which are subscribed.
The Components which have no Parent-Child relationship can communicate via a service:
Create a single service file (In your case: test-a-comp.service)
Create a Subject on what data you need to communicate via this service:
export class testMessageService {
constructor() {}
// Observable string sources
private message = new Subject<string>();
//Observable string streams
testMessage$ = this.message.asObservable();
constructor() {}
// Method to send message when a button is clicked
sendMessage(message: string) {
this.message.next(message);
}
/* You don't need "getMessage()" method as you've already subscribed to
the observables. There subscribed Observable string streams are
injected in your components (As below point 3) to display / do other
operation on the message. */
}
In your other Components, where you want to receive messages, do the following:
export class TestComponent 1 {
myMessage1: string;
constructor(private TestMessageService: testMessageService) {}
TestMessageService.testMessage$.subscribe(message => {
this.myMessage1 = message;
});
}
export class TestComponent 2 {
myMessage2: string;
constructor(private TestMessageService: testMessageService) {}
TestMessageService.testMessage$.subscribe(message => {
this.myMessage2 = message;
});
}
export class TestComponent 3 {
myMessage3: string;
constructor(private TestMessageService: testMessageService) {}
TestMessageService.testMessage$.subscribe(message => {
this.myMessage3 = message;
});
}
For more information/guidance refer Component interaction via a common
service: https://angular.io/guide/component-interaction
Hope this helps!

Why does the value not change to false in AuthGuard?

In the template component AppComponent, depending on the value, the variable this.loggedInService.isLoggedIn switches between the logIn() and logout() methods, which in the application component AppComponent are subscribed to these methods in the service LoggedinServiceand depending on the method, change the value of the variable to true or false.
Also in the Guard's method checkLogin (url: string) I return true or false depending on the value of the variable this.loggedInService.isLoggedIn.
When I start the application, I cannot enter the module, when I click on the button, I can, but when I repeat click on the button "exit", I can still go to the module.
How to make the switch to checkLogin work so that the authentication works correctly and save the value of switching the state between input and output when the page is restarted?
**AppComponent.html: **
<li class="nav-item">
<a class="btn btn-outline-success"
[class.btn-outline-success]="!this.loggedInService.isLoggedIn$"
[class.btn-outline-danger]="this.loggedInService.isLoggedIn$"
(click)="this.loggedInService.isLoggedIn$ ? logout() : logIn()">
{{this.loggedInService.isLoggedIn$ ? 'Exit' : 'Enter'}}
</a>
</li>
**AppComponent.ts **
export class AppComponent implements OnInit {
message: string;
constructor(public loggedInService: LoggedinService,
public router: Router) {
this.setMessage();
}
ngOnInit() {}
logIn(): void {
this.loggedInService.login().subscribe(() => {
if (this.loggedInService.isLoggedIn$) {
let redirect = this.loggedInService.redirectUrl ? this.loggedInService.redirectUrl :
'/gallery';
this.router.navigate([redirect]);
}
});
}
logout(): void {
this.loggedInService.logout();
}
}
LoggedinService:
export class LoggedinService {
isLoggedIn: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
isLoggedIn$: Observable<boolean> = this.isLoggedIn.asObservable();
redirectUrl: string;
constructor() {}
login(): Observable < boolean > {
return of(true).pipe(
delay(100),
tap(val => this.isLoggedIn.next(true))
);
}
logout(): void {
this.isLoggedIn.next(false);
}
}
AuthGuard:
export class AuthGuard implements CanActivate {
constructor(
private loggedInService: LoggedinService,
private router: Router
) {}
canActivate(next: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable<boolean> {
let url: string = state.url;
return this.loggedInService.isLoggedIn$;
}
checkLogin(url: string): boolean {
if (this.loggedInService.isLoggedIn) {
return true;
} else {
this.loggedInService.redirectUrl = url;
return false;
}
}
}
isLoggedIn in your LoggedinService is a Primitive Data type. So it is not passed by reference. It's passed by value. So if there is a change in it at one place, the same change won't reflect at other places where it is used.
This behavior is only exhibited by Objects as they are passed by reference and NOT value.
You could use a BehaviorSubject to fix this issue.
import { Injectable } from '#angular/core';
import { BehaviorSubject, Observable, of } from 'rxjs';
import { Router } from '#angular/router';
import { delay, tap } from 'rxjs/operators';
#Injectable()
export class LoggedinService {
isLoggedIn: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
isLoggedIn$: Observable<boolean> = this.isLoggedIn.asObservable();
redirectUrl: string;
constructor(private router: Router) { }
login(): Observable<boolean> {
this.isLoggedIn.next(true);
return this.isLoggedIn$;
}
logout(): Observable<boolean> {
this.isLoggedIn.next(false);
return this.isLoggedIn$;
}
}
Now, instead of isLoggedIn of type boolean, you'll get isLoggedIn$ of type Observable which you'll have to subscribe to, to get the logged in status of the user.
You'll have to .subscribe to this.loggedInService.login() and this.loggedInService.login() in your AppComponent as both of them return isLoggedIn$. You'll have to create a local isLoggedIn property and assign it whatever is returned in your .subscribe. You can then set the button text and click handler based on the template based on this isLoggedIn property.
In the case, of AuthGuard, since a guard can return Observable<boolean> or Promise<boolean> or boolean, you can simply return this.loggedInService.isLoggedIn$
Here's a Sample StackBlitz for your ref.

Categories

Resources