lit-element passing data from one component to another - polymer-3.x

I am currently learning how to user lit-element v2.0.0-rc.2 I have two components app.js and list-items.js. In app.js I am collecting data from local storage and storing it in this.todoList, Im then calling this.todoList in my list-items.js but the problem I am running into is that it is not passing the data as an array but as an object, I am trying to output that data in list-items all Im getting when I do a console.log of this.todoList is [object] in my tags it is rendering out with dots for the tag but no data. I was wondering If i could get some help in understanding why this is happening . here is my code
app.js
'''
import {LitElement, html} from 'lit-element';
import './add-item';
import './list-items';
class TodoApp extends LitElement{
static get properties(){
return{
todoList: Array
}
}
constructor(){
super();
let list = JSON.parse(localStorage.getItem('todo-list'));
this.todoList = list === null ? [] : list;
}
render(){
return html `
<h1>Hello todo App</h1>
<add-item></add-item>
<list-items todoList=${this.todoList}></list-items>
`;
}
}
customElements.define('todo-app', TodoApp)
list-items.js
import { LitElement, html } from 'lit-element';
import {repeat} from 'lit-html/directives/repeat.js';
import './todo-item';
class ListItems extends LitElement {
static get properties(){
return{
todoList: Array
}
}
constructor(){
super();
this.todoList = [];
}
render(){
console.log(this.todoList)
return html `
<ul>${repeat(this.todoList, (todo) => html`<todo-item
todoItem=${todo.item}></todo-item`)}</ul>
`;
}
}
customElements.define('list-items', ListItems);
'''
the result I am looking for is the for the data stored in local storage to be listed on my rendered page.

Attributes are always text. Because todoList an array, it's a property, not attribute. Try binding as a property: .todoList="${this.todoList}". See https://lit-element.polymer-project.org/guide/templates#bind-properties-to-child-elements (Updated link for Lit, https://lit.dev/docs/templates/expressions/#property-expressions)

Related

Lit-Element - Can't get Id of item from Object

I am learning lit-element and have run into a small problem I am trying to setup the ability to remove an Item from my list but I am unable to get the id of my Item it is coming across as undefined when I test it with console.log. I have three components the add-item.js which adds items to the list that is working fine. app.js is the main component that handles the auto refresh of the page aswell as the main rendering of the page this is where I have the event listeners for the addItem and the removeItem. Then I have todo-item component which is where I have the object that I am trying to get the ID for the remove functionality. Im at a loss at what I am doing wrong here and was hoping some one could take a look and point me in the right direction
here is the code so far .
add-item.js
```
import {LitElement, html} from 'lit-element';
class AddItem extends LitElement{
static get properties(){
return{
todoList: Array,
todoItem: String
}
}
constructor(){
super();
this.todoItem = '';
}
inputKeypress(e){
if(e.keyCode == 13){
e.target.value="";
this.onAddItem();
}else{
this.todoItem = e.target.value;
}
}
onAddItem(){
if(this.todoItem.length > 0){
let storedTodoList = JSON.parse(localStorage.getItem('todo-
list'));
storedTodoList = storedTodoList === null ? [] : storedTodoList;
storedTodoList.push({
id: new Date().valueOf(),
item: this.todoItem,
done: false
});
localStorage.setItem('todo-list',
JSON.stringify(storedTodoList));
this.dispatchEvent(new CustomEvent('addItem',{
bubbles: true,
composed: true,
detail: {
todoList: storedTodoList
}
}));
this.todoItem = '';
}
}
render(){
return html `
<div>
<input value=${this.todoItem}
#keyup="${(e) => this.inputKeypress(e)}">
</input>
<button #click="${() => this.onAddItem()}">Add Item</button>
</div>
`;
}
}
customElements.define('add-item', AddItem)
```
app.js
```
import {LitElement, html} from 'lit-element';
import './add-item';
import './list-items';
class TodoApp extends LitElement{
static get properties(){
return{
todoList: Array
}
}
constructor(){
super();
let list = JSON.parse(localStorage.getItem('todo-list'));
this.todoList = list === null ? [] : list;
}
firstUpdated(){
this.addEventListener('addItem', (e) => {
this.todoList = e.detail.todoList;
});
this.addEventListener('removeItem', (e) => {
let index = this.todoList.map(function(item) {return
item.id}).indexOf(e.detail.itemId);
this.todoList.splice(index, 1);
this.todoList = _.clone(this.todoList);
localStorage.setItem('todo-list', JSON.stringify(this.todoList));
})
}
render(){
return html `
<h1>Hello todo App</h1>
<add-item></add-item>
<list-items .todoList=${this.todoList}></list-items>
`;
}
}
customElements.define('todo-app', TodoApp)
```
todo-item.js
```
import {LitElement, html} from 'lit-element';
class TodoItem extends LitElement{
static get properties(){
return{
todoItem: Object
}
}
constructor(){
super();
this.todoItem = {};
}
onRemove(id){
this.dispatchEvent(new CustomEvent('removeItem',{
bubbles: true,
composed: true,
detail:{
itemId: id
}
}));
}
render(){
console.log(this.todoItem.id);
return html `<li>${this.todoItem}</li>
<button #click="${() =>
this.onRemove(this.todoItem.id)}">Remove</button>`;
}
}
customElements.define('todo-item', TodoItem);
```
I am looking to get the Id of the item so that I can remove it from the list for example if i have 5 items, one, two , three , four, five and I click the button to remove the third Item it should be removed and the list updated with the remaining items .. right now it is deleting the items but it is the last one on the list which is what I do not want to happen.
Looking forward to some help on this so that I can move forward with the project
thank you .
The issues has been resolved,
I was not supplying the entire array, just one element. After fixing the code I was able to get the Id of the object and move forward with my project as expected.

#ngrx 4 how to filter current loaded data

I am working on a new angular 4 plus #ngrx 4 project.
I wish to have a searching function on the loaded data.
For example, all the contacts info have been loaded in the component.
The contacts list will be filtered which contact name matched with the search text.
Please see screenshot
As the data is existed in store and I do not wish to call web api service again.
Any idea or demo code would be appreciated.
You can follow this flow to search what you need on already fetched content:
Use something like '(input)'='searchInputChange$.next(search)' in your input. So, each time the user changes the input, it will trigger our research.
Then, on your component, on the constructor, each time searchInputChange$ changes, we trigger a new SearchAction. Then, we will change our filtered contents on the reducers and the result will be inserted into contents$. On ngOnInit we just load the data from api the first time.
I'm using a model called Content, just an example, that has a string parameter title. We will use this field to filter our contents based on the search input.
import { Component, OnInit } from '#angular/core';
import { Store } from '#ngrx/store';
import { Subject } from 'rxjs/Subject';
import {of} from 'rxjs/observable/of';
/** ngrx **/
import {AppState} from '../../app-state.interface';
import * as searchActions from './actions/search.actions';
/** App Models **/
import { Content } from './models/content.model';
export class SearchComponent implements OnInit {
searchInputChange$ = new Subject<string>();
contents$: Observable<Array<Content>>;
constructor(private _store: Store<AppState>) {
this.searchInputChange$
.switchMap((text: string) => of(text))
.subscribe((text: string) => this._store.dispatch(new searchActions.SearchAction(text)));
this.contents$ = this._store.select(getSearchedContents);
}
ngOnInit() {
this._store.dispatch(new searchActions.LoadAction());
}
}
Then, we'll have our SearchActions. Load is triggered on the init of our component, fetches some contents from api. LoadSuccess is emitted on the effect of the load action in order to populate our reducer with fetched data and show it in our first component, this has a payload of an array of contents. Search will be triggered on change of our input field, this will have a string payload containing the search string.
import { Action } from '#ngrx/store';
/** App Models **/
import { Content } from '../models/content.model';
export const LOAD = '[Search] Load';
export const LOAD_SUCCESS = '[Search] Load Success';
export const SEARCH = '[Search] Search';
export class LoadAction implements Action {
readonly type = LOAD;
constructor() { }
}
export class LoadActionSuccess implements Action {
readonly type = LOAD_SUCCESS;
constructor(public payload: Content[]) { }
}
export class SearchAction implements Action {
readonly type = SEARCH;
constructor(public payload: string) {}
}
export type All
= LoadAction
| LoadActionSuccess
| SearchAction;
SearchEffect that will just fetch contents from api:
import { Injectable } from '#angular/core';
import { Actions, Effect } from '#ngrx/effects';
/** rxjs **/
import {of} from 'rxjs/observable/of';
import {map} from 'rxjs/operators/map';
import {mergeMap} from 'rxjs/operators/mergeMap';
import {catchError} from 'rxjs/operators/catchError';
/** ngrx **/
import * as searchActions from '../actions/search.actions';
/** App Services **/
import { SomeService } from '../services/some.service';
/** App Model **/
import {Content} from '../models/content.model';
#Injectable()
export class SearchEffects {
#Effect() load$ = this.actions$
.ofType(searchActions.LOAD)
.pipe(
mergeMap(() => {
return this.someService.getContentsFromApi()
.pipe(
map((contents: Content[]) => {
return new searchActions.LoadActionSuccess(contents);
}),
catchError(() => {
// do something
})
);
})
)
;
constructor(private someService: SomeService, private actions$: Actions) { }
}
SearchReducer will handle LoadSuccess when we successfully fetch contents from api and Search action that will filter our fetched contents to return only the ones containing our search string inside content's title parameter. We save first fetched contents in both of contents and searchedContents. Then, on search, we will update searchedContents to contain only contents having content.title including the searched string.
import { isEmpty } from 'lodash';
/** ngrx **/
import {createFeatureSelector} from '#ngrx/store';
import {createSelector} from '#ngrx/store';
/** App Models **/
import { Content } from '../models/content.model';
/** ngrx **/
import * as searchActions from '../actions/search.actions';
export type Action = searchActions.All;
export interface SearchsState {
contents: Content[];
searchedContents: Content[];
}
export const initialState: SearchsState = {
contents: [],
searchedContents: []
};
/ -------------------------------------------------------------------
// Selectors
// -------------------------------------------------------------------
export const selectContents = createFeatureSelector<SearchsState>('search');
export const getSearchedContents = createSelector(selectContents, (state: searchedContents) => {
return state.searchedContents;
});
export function contentsReducer(state: searchedContents = initialState, action: Action): searchedContents {
switch (action.type) {
case contentsActions.LOAD_SUCCESS:
const loadContents = action.payload.map(content => new Content(content));
return {
contents: loadContents,
searchedContents: loadContents
};
case contentsActions.SEARCH:
const keywordContents = isEmpty(action.payload) ? state.contents :
state.contents.filter(content => content.title.includes(action.payload));
return {
contents : state.contents,
searchedContents : keywordContents
};
default: {
return state;
}
}
}
So, updating searchedContents will automatically update the contents in our component.
ngrx store is the part of how you store the data. ngrx store is observable so your application flow is
Container -> Components
Container - wrapper component that will select data from store.
example:
const contacts$: Observable<contact> = this.store.pluck('contacts');
//*contacts$ - the dollar since is convention for Observable *//
Component - data visualization component, the data will be as Input(). example:
Input() contacts: Array<contact>;
this convention is called sometime SmartComponent(Container) and
DumbComponent(component)
now for a data transform/mapping you can use reactive approach(Rxjs) or functional programming or whatever you want but it not related for ngrx because in your contacts component the data as exist.
DEMO FOR YOUR SCENARIO:
contacts.container.ts
#Component({
selector: 'contacts-container',
template: `
<contacts-list [contacts]="contacts$ | async"></contacts-list>
`
})
export class ContactsContainer {
contacts$: Observable<[]contact> = this.store.pluck('contacts');
constructor(
private store: Store<applicationState>
) { }
}
contact-list.component.ts
#Component({
selector: 'contacts-list',
template: `
<input type="text" placeholder="write query" #query>
<ul>
<li *ngFor="contact of contacts | searchPipe: query.target.value">
</li>
</ul
`
})
export class ContactsListComponent {
contcats: Array<contact> = [];
constructor() { }
}
i use searchPipe for data transform ( custom pipe ) but is only example for data transform you can do it else.
Good Luck!

react-native navigating between screens from non component class

I'm trying to navigate between react native screens from my Backend class like this:
var self = this;
firebase.auth().onAuthStateChanged((user) => {
if (user) {
self.setState({
userID: user.uid,
})
} else{
self.props.navigation.navigate("Login");
}
});
My backend class is not a component and therefore is not imported into the stack navigator I am using. I am getting an error saying 'self.props.navigation is not an object'.
Does anyone know I can fix this? Thanks
One not-so-good practice is to define your Navigator as a static/class variable of your App instance:
const MyNavigator = StackNavigator(...);
export default class MyApp extends Component {
render() {
return <MyNavigator ref={(ref) => MyApp.Navigator = ref}/>
}
}
then you can access your navigator and it's props and functions anywhere you want! (for example dispatch a back event):
import MyApp from '...';
MyApp.Navigator.dispatch(NavigationActions.back());
I am personally not a fan of navigation actions happening at that level however, sometimes it's necessary. Expanding on the answer from #Dusk a pattern was made known to me that helps with this very solution. You can find it here
https://github.com/react-community/react-navigation/issues/1439#issuecomment-303661539
The idea is that you create a service that holds a ref to your navigator. Now from anywhere in your app you can import that service and have access to your navigator. It keeps it clean and concise.
If you are using react-navigation then you can achieve this via Navigation Service
Create a file named NavigationService and add the below code there
import { NavigationActions, StackActions } from 'react-navigation';
let navigator;
function setTopLevelNavigator(navigatorRef) {
navigator = navigatorRef;
}
function navigate(routeName, params) {
navigator.dispatch(
NavigationActions.navigate({
routeName,
params
})
);
}
function goBack(routeName, params) {
navigator.dispatch(
StackActions.reset({
index: 0,
actions: [
NavigationActions.navigate({
routeName,
params
})
]
})
);
}
function replace(routeName, params) {
navigator.dispatch(
StackActions.replace({
index: 0,
actions: [
NavigationActions.navigate({
routeName,
params
})
]
})
);
}
function pop() {
navigator.dispatch(StackActions.pop());
}
function popToTop() {
navigator.dispatch(StackActions.popToTop());
}
// add other navigation functions that you need and export them
export default {
navigate,
goBack,
replace,
pop,
popToTop,
setTopLevelNavigator
};
Now import this file in your app.js and set the TopLevelNavigator, your app.js will look something like this
import React, { Component } from 'react';
import NavigationService from './routes/NavigationService';
export default class App extends Component {
constructor() {
super();
}
render() {
return (
<View style={{ flex: 1, backgroundColor: '#fff' }}>
<AppNavigator
ref={navigatorRef => {
NavigationService.setTopLevelNavigator(navigatorRef);
}}
/>
</View>
);
}
}
Now you are good to go, you can import your NavigationService where ever you want, you can use it like this in any of the components and non-component files
import NavigationService from 'path to the NavigationService file';
/* you can use any screen name you have defined in your StackNavigators
* just replace the LogInScreen with your screen name and it will work like a
* charm
*/
NavigationService.navigate('LogInScreen');
/*
* you can also pass params or extra data into the ongoing screen like this
*/
NavigationService.navigate('LogInScreen',{
orderId: this.state.data.orderId
});

React Native & Firebase: How do I get JSX components to render from a fetched Firebase database of items?

I am new to React Native and Firebase, this is probably easy but I can't figure out what's wrong. I'm trying to:
(1) Fetch a list of items from my Firebase database, convert the snapshot.val() that Firebase returns into an array (DONE)
(2) Filter that array for when each object has a specific color (DONE)
(3) Send that filtered array of objects to a function that renders a JSX component to the screen (NOT WORKING)
PROBLEM - The console.log above the return() statement in renderItems() tells me that I am getting the data that I need to be there correctly, but for whatever reason, the JSX components are not rendering to the screen. I feel like there is something simple I am missing, but I just can't figure out what. Please help!
import React, { Component } from 'react';
import { ScrollView } from 'react-native';
import _ from 'lodash';
import firebase from 'firebase';
import Item from './Item';
class ItemList extends Component {
getItemsByColor(color) {
const itemsRef = firebase.database().ref('/items/');
itemsRef.once('value').then((snapshot) => {
const filteredItems = _.filter(snapshot.val(), item => {
return item.color === color;
});
this.renderItems(filteredItems);
}, (error) => {
// The Promise was rejected.
console.error(error);
});
}
renderItems(filteredItems) {
filteredItems.map((item) => {
console.log(item.name);
return <Item name={item.name} color={item.color} />;
});
}
render() {
return (
<ScrollView style={{ backgroundColor: '#333', flex: 1 }}>
{this.getItemsByColor('blue')}
</ScrollView>
);
}
}
export default ItemList;
Within renderItems() you are returning each <Item/> to the map function, but are not then returning the result of the function afterwards. Try including another return like so:
renderItems(filteredItems) {
return filteredItems.map((item) => {
console.log(item.name);
return <Item name={item.name} color={item.color} />;
});
}
You may need to then put in a couple more return statements in getItemsByColor() as well so that the array of <Item/>'s is returned to the function call within render().

meteorjs reactjs action after authentification

I have the following react component that passed data to another component:
export default class App extends TrackerReact(Component){
getUserFrameData(){
return (FrameCollection.find().fetch());
}
render(){
return(
<div className="main-container">
<Frames
data={this.getUserFrameData()}
/>
</div>
);
}
}
Now I want my frames component to do an action when the component initialises.
export default class Frames extends Component{
componentDidMount(){
console.log(this.props.data);
}
render() {...}
}
But on I only get empty data at on loadup. I think it's because I'm using subscriptions and a login system. So how can I tell my Frames component to wait until everything is "loaded up"?
Use the ready method of the subscription object.
constructor() {
super();
this.state = {
sub: Meteor.subscribe('myPublication')
}
}
render() {
if (!this.state.sub.ready()) return <p>Loading...</p>;
return ...
}
http://docs.meteor.com/api/pubsub.html#Subscription-ready

Resources