Angular2 how to empty observable stream - http

I am using Angular 2.0.0-beta.0 and TypeScript 1.7.5
When you type something in the search box and something is found and shown on the screen, then you delete the search input box and you want to show an empty list. It work using this piece of code:
this.searchTermStream.next("makesureyoudontfindanything");
Does anyone has a better cleaner solution without doing a http request?
#Component({
selector: 'contact-search',
template: `
<div class="container">
<div class="form-group">
<label for="inputUser">Search</label>
<input #inputUser (keyup)="search(inputUser.value)">
</div>
<ul>
<li *ngFor="#contact of contactList | async">{{contact.name}}</li>
</ul>
</div>
`
})
export class ContactSearch {
private searchTermStream = new Subject<string>();
private contactList: Observable<Contact[]> = this.searchTermStream
.debounceTime(300)
.distinctUntilChanged()
.switchMap((value: string) => this.contactService.searchContacts(value))
constructor(private contactService: ContactService) {}
search(value: string) {
if (value) {
this.searchTermStream.next(value);
}
else {
this.searchTermStream.next("makesureyoudontfindanything");
}
}
}

You can check if value is empty before calling service:
private contactList: Observable<Contact[]> = this.searchTermStream
.debounceTime(300)
.distinctUntilChanged()
.switchMap((value: string) =>
0 < value.length ? this.contactService.searchContacts(value) : Observable.of([]))
search(value: string) {
this.searchTermStream.next(value);
}

Related

Angular [style] not always working with *ngFor divs

Welcome, I have Angular application which should look and work like messenger writing all messaages to MySQL.
The problem is that it looks like this:
But should look like this:
component.html fragment:
<div *ngFor="let m of messages" [style.background-color]="m.to===conversationPartnerId ? 'lightgrey' : 'blueviolet'"
[style.margin-left]="m.to!=conversationPartnerId ? '5%' : '15%'"
[style.margin-right]="m.to===conversationPartnerId ? '5%' : '15%'"
style="width: 80%; height: auto; float: left; margin-top: 5%">
<h3 style="color: white">{{m.text}}</h3>
</div>
component.ts fragment:
export class BrowseComponent implements OnInit {
userKey = null;
profileToDecide: Profiles;
matches: Profiles[];
currentImage: any;
background: any;
decisionResponse: any;
conversationPartnerId: number;
conversationPartnerName: string;
messages: Messages[];
LINES OF OTHER METHODS
...
...
...
getMessages(id: number, name: string): void {
this.setConversationPartnerIdAndName(id, name);
let registerObject: object;
registerObject = {
"key": this.userKey,
"target": this.conversationPartnerId,
};
this.restService.getMessages(registerObject).subscribe(messages => {
this.messages = messages;
})
}
setConversationPartnerIdAndName(id: number, name: string): void { // Set key and name used in html
this.conversationPartnerId = id; // ngStyle formatting
this.conversationPartnerName = name;
}
JSON from API:
I thought this just set the wrong key, to should be to_id, this is my demo, you can follow this.
stackblitz

Picking an item from Firebase on button click

I'm working with Angular 6 and Firebase and what I need is to get all items from the firebase and then display only one at a time till next button click
/*this is the .ts file*/
constructor( public af: AngularFire, public db: AngularFireDatabase) {
this.items = db.list('items').valueChanges();
db.list('/sentences').valueChanges().subscribe(sentences => {
this.sentences = sentences;
console.log(this.sentences);
});
}
<!--this in the html-->
<div>
<ul class="sent">
<li *ngFor="let sentence of sentences"
[class.selected]="sentence === selectedsentence"
(click)="onSelect(sentence)">{{sentence}}
<!-- this is displaying all the items in the firebase; i need only one at a time-->
</li>
</ul>
</div>
//in the ts file
constructor( public db: AngularFireDatabase) {
this.items = db.list('items').valueChanges();
db.list('/sentences').valueChanges().subscribe(sentences => {
this.sentences = sentences;
console.log(this.sentences);
this.labels = this.sentences; //in this.labels we have all the elements *declaration : labels=[]
console.log(this.labels);
});
onSkip($i) {
if (this.i > 13) { //if there are 13 elements in the database
this.i = 0;
}
this.i = this.i + 1;
console.log(this.i);
}
// in the html code
{{labels[i]}}
<button class="centered" (click)="onSkip(i)" > SKIP <a routerLink="skip">

Problems with onQueryEvent observable failing upon routing in Nativescript with Angular

I am using Nativescript with Angular and have code written that succesfully calls an onQueryEvent from the nativescript-firebase-plugin for data set upon first building the application. However after following a route to a second component containing the exact same onQueryEvent the data succeeds to retreive a complete list but skips the onQueryEvent.
In all honesty I don't know best practices for queries in any situation let alone this one, so I hope it is just a matter of manipulating how I call the onQueryEvent.
I believe the problem to be in the firebase.query inside the getMyTransactionList() function of the firebase.service.ts file.
the overview.component.html page has a transaction | async RadListView that successfully filters upon running tns run android. Then clicking any link directing to the deal-summary.component.html page where the function is re-iterated refuses to query by the static storage variable set in the firebase.service
Here is my code:
firebase.service.ts
export class FirebaseService {
private _allItems: Array<any> = [];
items: BehaviorSubject<Array<any>> = new BehaviorSubject([]);
public storage: any = '-KomUSGcX-j6qQmY4Wrh'; // set statically to test different routes
constructor(
private ngZone: NgZone,
){}
// fetch data
getMyDealList(): Observable<any> {
return new Observable((observer: any) => {
let path = `deals/${BackendService.token}`;
let onValueEvent = (snapshot: any) => {
this.ngZone.run(() => {
let results = this.handleSnapshot(snapshot.value);
observer.next(results);
});
};
firebase.addValueEventListener(onValueEvent, `/${path}`);
}).share();
}
getMyTransactionList(): Observable<any> {
return new Observable((observer: any) => {
let path = `transactions/${BackendService.token}`;
// this is a merge of jen loopers giftler code combined with nativescrip-firebase-plugins standard onQueryEvent. It works on the first load but routing to a second instance of the same function retrieves all the data without queryEvent
let onQueryEvent = (snapshot: any) => {
this.ngZone.run(() => {
let results = this.handleSnapshot(snapshot.value);
observer.next(results);
});
};
firebase.query(
onQueryEvent,
`/transactions/${BackendService.token}`,
{
singleEvent: true,
orderBy: {
type: firebase.QueryOrderByType.CHILD,
value: 'dealId' // mandatory when type is 'child'
},
range:
{
type: firebase.QueryRangeType.EQUAL_TO,
value: `${this.storage}` // this calls a static variable for testing consistency
}
,
}
);
firebase.addValueEventListener(onQueryEvent, `/${path}`);
console.log("transaction Listener added");
}).share();
}
handleSnapshot(data: any) {
//empty array, then refill and filter
this._allItems = [];
if (data) {
for (let id in data) {
let result = (<any>Object).assign({id: id}, data[id]);
this._allItems.push(result);
}
this.publishUpdates();
}
return this._allItems;
}
publishUpdates() {
// here, we sort must emit a *new* value (immutability!)
this._allItems.sort(function(a, b){
if(a.date < b.date) return -1;
if(a.date > b.date) return 1;
return 0;
})
this.items.next([...this._allItems]);
}
}
app.component.ts
<page-router-outlet></page-router-outlet>
overview.component.ts
export class OverviewComponent implements OnInit {
public deals: Observable<any>;
public transactions: Observable<any>;
constructor(private router: Router,
private firebaseS: FirebaseService,
){ }
ngOnInit() {
this.deals = <any>this.firebaseS.getMyDealList();
this.transactions = <any>this.firebaseS.getMyTransactionList();
}
viewDealSumm(id){
this.router.navigate(['dashboard/deal-summary', id]);
}
}
overview.component.html
<RadListView [items]="deals | async ">
<ng-template tkListItemTemplate let-item="item">
<StackLayout (tap)="viewDealSumm(item.id)">
<Label [text]="item.dealName"></Label>
</StackLayout>
</ng-template>
</ListViewGridLayout>
</RadListView>
<RadListView [items]="transactions | async " >
<ng-template tkListItemTemplate let-item="item">
<GridLayout>
<Label [text]="item.transName"></Label>
</GridLayout>
</ng-template>
</RadListView>
deal-summary.component.ts
export class DealSummaryComponent implements OnInit {
public transactions: Observable<any>;
constructor(
private firebaseS: FirebaseService,
){ }
ngOnInit() {
this.transactions = <any>this.firebaseS.getMyTransactionList();
}
deal-summary.component.html
<RadListView [items]="transactions | async " >
<ng-template tkListItemTemplate let-item="item">
<GridLayout >
<Label col="1" [text]="item.transName"></Label>
</GridLayout>
</ng-template>
</RadListView>

Meteor Angular 2 - autobind not working in the tutorial

I am following the Meteor - Angular2 tutorial and things work fine.
The only point not working is the automatic binding with Angular2 UI for the 'details view'. For instance, if I navigate to the details view of Party1 the data of Party1 is correctly loaded and made visible on the Angular2 'details view'. If, afterwards, the data of Party1 is changed (e.g. via Mongo shell) such change is sent to the browser (via WebSockets) where 'details view' is displayed, but the new data is not shown on the view.
Here is the code of the PartyDetailsComponent class.
export class PartyDetailsComponent extends MeteorComponent implements OnInit, CanActivate {
partyId: string;
party: Party;
constructor(private route: ActivatedRoute, private ngZone: NgZone) {
super();
}
ngOnInit() {
this.route.params
.map(params => params['partyId'])
.subscribe(partyId => {
this.partyId = partyId;
this.subscribe('party', this.partyId, () => {
this.party = Parties.findOne(this.partyId);
}, true);
});
}
saveParty() {
Parties.update(this.party._id, {
$set: {
name: this.party.name,
description: this.party.description,
location: this.party.location
}
});
}
canActivate() {
const party = Parties.findOne(this.partyId);
console.log(party);
return (party && party.owner == Meteor.userId());
}
}
Here is the template of of PartyDetailsComponent
<form *ngIf="party" (submit)="saveParty()">
<label>Name</label>
<input type="text" [(ngModel)]="party.name" name="name">
<label>Description</label>
<input type="text" [(ngModel)]="party.description" name="description">
<label>Location</label>
<input type="text" [(ngModel)]="party.location" name="location">
<button type="submit">Save</button>
<a [routerLink]="['/']">Cancel</a>
</form>
Thanks in advance for any help
I actually found the answer to my questions just reading more of the Tutorial.
I can get automatic update ofthe UI once the underlying Mongo doc changes just adding Meteo autorun() method appropriately in the subscription code.
Here is the code that works
ngOnInit() {
this.route.params
.map(params => params['partyId'])
.subscribe(partyId => {
this.partyId = partyId;
this.subscribe('party', this.partyId, () => {
this.autorun(() => {
this.party = Parties.findOne(this.partyId);
}, true);
}, true);
});
}
What is not totally clear to me is why if you use directly the Meteo Mongo cursors (e.g. via *ngFor in the template) autorun is not needed.

HTTP and nested objects (Angular2 RC1 + TS)

I'm having some trouble displaying my data in the browser. To explain my problem I'm using some dummy code. I have some nested objects that are causing my problem. Here I'll display one nested object to showcase my problem.
First of all, I only make http calls for the Car-object. So saveCar acts like updating the car as well, depending on what the user does in the app. All the methods in the service works as they should.
So my service looks something like this:
#Injectable()
export class Service {
constructor(private http: Http) { }
saveCar(car: Car) {
return this.http.post ....
}
getCars(){
return this.http.get...
}
getById(id: string) {
return this.http.get...
}
}
Then I have a Car-class, where the nested object "Brand" comes in to play, Brand then has it's own class, but I'll leave it out.
export class Car {
private brands: Array<Brand>;
constructor(public id: string, public name: string) {
this.brands = new Array<Brand>();
}
public getBrands(): Array<Brand> {
return this.brands;
}
public addBrand(value: Brand): void {
this.brands.push(value);
}
//some other methods.
}
Then I have a list-component that lists all cars, this works as it should!
#Component({
selector: 'car-list',
template: `
<h1>Add Car</h1>
<form (submit)="saveCar()">
<input required [(ngModel)]="name" placeholder="Add car">
</form>
<br>
<table>
<tr *ngFor="let car of cars" >
<td>{{car.name}}</td>
<td><button (click)="goToDetail(car)">Detail</button></td>
</tr>
</table>
`,
})
export class ListComponent implements OnActivate {
id: string
name: string;
cars: Array<Car>
constructor(public _service: Service, public _router: Router) { }
routerOnActivate(): void {
this._service.getCars()
.subscribe(cars => this.cars = cars);
}
saveCar() {
let car = new Car(this.id, this.name)
this._service.saveCar(Car)
.subscribe(car => this.cars.push(car));
this._service.getCars()//
.subscribe(cars => this.cars = cars);
}
goToDetail(car:Car) {
this._router.navigate(['/cardetail', car.id]);
}
}
The problem I have is in the detail-component, where the user gets navigated after clicking a specific car. The routing and retrieving the Car from the db works as it should. That I know, because if I remove all the template except <h1>Car: {{car?.name}}</h1> the name gets printed out fine with the elvis operator.
But my detail-component looks something like this:
#Component({
selector: 'car-detail',
template: `
<h1>Car: {{car?.name}}</h1>
<hr>
<button (click)="addBrand()">Add Brand</button>
<div *ngFor="let brand of car.getBrands(); let i=index">
<h2>Brand {{i+1}}</h2>
</div>
`,
})
export class DetailComponent implements OnActivate {
#Input() car: Car;
constructor(public _service: Service, public _router: Router) { }
routerOnActivate(curr: RouteSegment): void {
let id = curr.getParam('id');
this._service.getById(id)
.subscribe(car => {
this.car = car;
});
}
addBrand() {
this.car.getBrands().push(new Brand());
}
//some other methods
}
So in my detail component I call all methods like: car.someMethod() and further on the nested Brand object like: brand.someMethod() in the template. So the error comes at the call of the method e.g in the template 'cannot get getBrands of undefined' I've tried putting the elvis operator like this: car?.getBrands() It doesn't work. I've tried to wrap the whole thing in a div, both with elvis operator and a <div *ngIf = "car"></div>, doesn't work. Even tried with <template *ngIf="car"></template>, well that doesn't work either....
Edit: my mess-up, wrapping like below, it does "kind of" work, meaning, it gives a new error....
Template:
#Component({
selector: 'car-detail',
template: `
<h1>Car: {{car?.name}}</h1>
<hr>
<button (click)="addBrand()">Add Brand</button>
<div *ngIf="car">
<div *ngFor="let brand of car.getBrands(); let i=index">
<h2>Brand {{i+1}}</h2>
</div>
</div>
You mention <h1>Car: {{car?.name}}</h1> with ? but the full code example has <td>{{car.name}}</td> without ? which will cause an error.
<div *ngFor="let brand of car.getBrands(); let i=index">
also needs a ? to avoid errors when Angular tries to render the view and car is not yet set
<div *ngFor="let brand of car?.getBrands(); let i=index">

Resources