Picking an item from Firebase on button click - firebase

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">

Related

Perform actions on component initialization in LWC

I recently started learning LWC. I am working on a requirement in LWC where on component initialization I need to use record Id to get value of numeric field from same object/record, check if it is 100 and display a message on component html. Note - this LWC is used as a quick action of type Screen Action. Following is current implementation which works at times and doesn't (not sure if it is a cache issue).
LWCComponent.html
<template>
<!-- modal start -->
<template if:true={isShowModal}>
<lightning-quick-action-panel header="New Record">
<div class="slds-modal__content modalBodySpinner" style="width: 100%;" id="modal-content-id-1">
<template if:true={isLoaded}>
<lightning-spinner alternative-text="Loading" size="medium" class="spinnerClass"></lightning-spinner>
</template>
<div slot>
<lightning-layout>
<lightning-layout-item size="6" padding="around-small">
<lightning-input label="Record Name" type = "text" value={recordName}></lightning-input>
</lightning-layout-item>
</lightning-layout>
</div>
<template if:true={recordId}></template>
<template if:true={showForm}>
<lightning-input label="Record ID" type="text"></lightning-input>
</template>
<template if:false={showForm}>
<p class="warningP">{restrictMessage}</p>
</template>
<br/>
</div>
<div slot="footer">
<lightning-button variant="neutral" label="Cancel" onclick={closeModal}></lightning-button>
</div>
</lightning-quick-action-panel>
</template>
<!-- modal end -->
</template>
LWCComponent.js
import { LightningElement, track, api, wire } from 'lwc';
import { getRecord, getFieldValue } from "lightning/uiRecordApi";
import NAME from "#salesforce/schema/CustomObject__c.Name";
import CAPACITY from "#salesforce/schema/CustomObject__c.Capacity__c";
import { CloseActionScreenEvent } from 'lightning/actions';
import restrictMessage from "#salesforce/label/c.RestrictWarning";
const fields = [NAME, CAPACITY];
export default class NewClass extends LightningElement {
#track isShowModal = true;
#track showForm = true;
#api recordId;
#track isLoaded = true;
#api capacityVal;
#track warningMsg;
#track retrievedRecordId = false;
#wire(getRecord, {
recordId: "$recordId",
fields
})
custObjRec;
get recordName() {
return getFieldValue(this.custObjRec.data, NAME);
}
renderedCallback() {
if(!this.retrievedRecordId && this.recordId) {
this.retrievedRecordId = true;
this.capacityVal = getFieldValue(this.custObjRec.data, CAPACITY)
if(this.capacityVal == 100) {
this.showForm = false;
this.warningMsg = restrictMessage;
this.isLoaded = false;
}
else {
this.isLoaded = false;
}
}
else {
this.isLoaded = false;
}
}
// method to close modal pop up
closeModal() {
this.dispatchEvent(new CloseActionScreenEvent());
}
}
Any suggestions to make this better?

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">

Angular2 how to empty observable stream

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);
}

Nothing showing on screen. Just showing a list of items from a collection

Im holding non persistant data , calling an api (Meteor.method) Its supposed to show a list of items(text)but nothing is showing up on screen
if(Meteor.isClient) {
Searches = new Meteor.Collection('searches');
Meteor.subscribe('allSearches');
console.log(Searches.find());
}
...
renderTasks(){
return this.data.searches.map((searches, i) => {
return <SearchResultItem key={i} searches={searches} />;
});
},
...
<ul>
{this.renderTasks()}
</ul>
...
SearchResultItem = React.createClass({
render(){
return
<li >
{this.props.searches}
</li>
}
});

How to set event handler in React sub-component

I'm having trouble getting menu items connected to an event handler. Here's a mock of the UI showing state changes over time. It's a dropdown menu (via Bootstrap), with the root menu item showing the current selection:
[ANN]<click ... [ANN] ... [BOB]<click ... [BOB]
[Ann] [Ann]
[Bob]<click + ajax [Bob]
[Cal] [Cal]
The end goal is to change the page content asynchronously based on the user's selection. Clicking on Bob should trigger the handleClick, but it's not.
As a side note, I'm not terribly happy with the way componentDidMount calls this.handleClick();, but it works for now as a way to get initial menu content from the server.
/** #jsx React.DOM */
var CurrentSelection = React.createClass({
componentDidMount: function() {
this.handleClick();
},
handleClick: function(event) {
alert('clicked');
// Ajax details ommitted since we never get here via onClick
},
getInitialState: function() {
return {title: "Loading items...", items: []};
},
render: function() {
var itemNodes = this.state.items.map(function (item) {
return <li key={item}><a href='#' onClick={this.handleClick}>{item}</a></li>;
});
return <ul className='nav'>
<li className='dropdown'>
<a href='#' className='dropdown-toggle' data-toggle='dropdown'>{this.state.title}</a>
<ul className='dropdown-menu'>{itemNodes}</ul>
</li>
</ul>;
}
});
$(document).ready(function() {
React.renderComponent(
CurrentSelection(),
document.getElementById('item-selection')
);
});
I'm almost positive that my hazy understanding of javascript scoping is to blame, but everything I've tried so far has failed (including trying to pass the handler down through props).
The problem is that you're creating the item nodes using an anonymous function, and inside that this means the window. The fix is to add .bind(this) to the anonymous function.
var itemNodes = this.state.items.map(function (item) {
return <li key={item}><a href='#' onClick={this.handleClick}>{item}</a></li>;
}.bind(this));
Or create a copy of this and use that instead:
var _this = this, itemNodes = this.state.items.map(function (item) {
return <li key={item}><a href='#' onClick={_this.handleClick}>{item}</a></li>;
})
As I can understand the specification of the task for "Anna", "Bob", "Cal, the solution can be the following (based on a react component and ES6):
Basic live demo is here
import React, { Component } from "react"
export default class CurrentSelection extends Component {
constructor() {
super()
this.state = {
index: 0
}
this.list = ["Anna", "Bob", "Cal"]
}
listLi = list => {
return list.map((item, index) => (
<li key={index}>
<a
name={item}
href="#"
onClick={e => this.onEvent(e, index)}
>
{item}
</a>
</li>
))
}
onEvent = (e, index) => {
console.info("CurrentSelection->onEvent()", { [e.target.name]: index })
this.setState({ index })
}
getCurrentSelection = () => {
const { index } = this.state
return this.list[index]
}
render() {
return (
<div>
<ul>{this.listLi(this.list)}</ul>
<div>{this.getCurrentSelection()}</div>
</div>
)
}
}

Resources