Passing data from service to angular components - angularjs

I am reading data from a firebase database and creating some objects based on the data received and pushing the data into a list. But the control goes back to the component before the objects are created or pushed into the list. I am confused to use any life cycle hooks in this approach.
Class Service(){
questions: QuestionsData<any>[]=[];
getQuestions(FormKey: string) {
var dbQuestions = this.af.list('/elements', {
query: {
limitToLast: 200,
orderByChild: 'formid',
equalTo: FormKey
}
})
dbQuestions.subscribe(snapshots=>{
snapshots.forEach(elementData => {
this.questions.push(new TextboxQuestion({
key: elementData.elementname,
label: elementData.displaytext,
value: elementData.elementvalue,
required: false,
order: elementData.sortorder
}))
}
}
}
Can anyone suggest how to consume this data in my component.

As JB Nizet mentioned in the comments, you should not subscribe to the observable and unwrap it in your template as you are currently doing. Angular provides the async pipe to handle that subscription for you. You simply want to map your data to TextBoxQuestion's. You can do that with the following code.
class MyComponent {
questions$: QuestionsData<any>[]=[];
getQuestions(FormKey: string) {
const dbQuestions$ = this.af.list('/elements', {
query: {
limitToLast: 200,
orderByChild: 'formid',
equalTo: FormKey
}
});
this.questions$ = dbQuestions$.map(snapshots =>
snapshots.map(data => new TextBoxQuestion({
key: data.elementname,
// and so on...
});
}
}
If you want to run that when your component initializes, use the OnInit lifecycle hook:
ngOnInit() {
this.getQuestions(/* form key */);
}
And then use the async pipe in your template like this:
<ul>
<li *ngFor="let question of questions$ | async">
{{ question.key }}
</li>
</ul>

Your service should be more or less like this:
import 'rxjs/add/operator/map'
Class Service() {
getQuestions(FormKey: string): Observable<QuestionsData<any>[]> {
return dbQuestions = this.af.list('/elements', {
query: {
limitToLast: 200,
orderByChild: 'formid',
equalTo: FormKey
}
}).map(snapshots=>{
conts questions: QuestionsData<any>[]=[];
snapshots.forEach(elementData => {
questions.push(new TextboxQuestion({
key: elementData.elementname,
label: elementData.displaytext,
value: elementData.elementvalue,
required: false,
order: elementData.sortorder
}))
})
return questions;
})
}
}
And in the component:
serviceInstance.getQuestions(FormKey).subscribe(questions => {
// your code here
})

Related

Problem with Mobx (TS) using with React Functional Components

Here is the problem.
I have simple todo store:
import { makeAutoObservable } from "mobx";
import { Todo } from "./../types";
class Todos {
todoList: Todo[] = [
{ id: 0, description: "Погулять с собакой", completed: false },
{ id: 1, description: "Полить цветы", completed: false },
{ id: 2, description: "Покормить кота", completed: false },
{ id: 3, description: "Помыть посуду", completed: true },
];
// Input: Add Task
taskInput: string = "";
// Filter: query
query: string = "";
// Filter: showOnlyCompletedTasks
showOnlyCompleted: boolean = false;
constructor() {
makeAutoObservable(this);
}
setShowOnlyCompletedState(value: boolean) {
this.showOnlyCompleted = value;
}
changeCompletionState(id: number) {
const task = this.todoList.find((todo) => todo.id === id);
if (task) task.completed = !task.completed;
}
addTask(text: string) {
if (text !== "") {
const newTodo: Todo = {
id: Number(new Date()),
description: text,
completed: false,
};
this.todoList.push(newTodo);
}
}
taskChangeInput(value: string) {
this.taskInput = value;
}
queryChangeInput(value: string) {
this.query = value;
}
}
export default new Todos();
In the app I have some tasks, which I can make completed or not-completed (by clicking on it) and also I do have some filters to filter my todo_list.
Here is the code:
of posts:
import { Todo } from "../types";
import { useMemo } from "react";
function useFilterByQuery (list: Todo[], query: string):Todo[] {
const filteredList = useMemo(()=>{
if (!query) return list
return list.filter(todo => todo.description.toLowerCase().includes(query.toLowerCase()))
}, [list, query])
return filteredList
}
export function useFilterByAllFilters (list:Todo[], query: string, showOnlyCompleted: boolean):Todo[] {
const filteredByQuery = useFilterByQuery(list, query)
const filteredList = useMemo(()=>{
if(!showOnlyCompleted) return filteredByQuery
return filteredByQuery.filter(todo => todo.completed)
}, [filteredByQuery, showOnlyCompleted])
return filteredList
}
So the description of the problem is so: when I choose show me only-Completed-tasks (setting showOnlyCompleted to true), I get expected list of tasks which are all 'completed'.
But, when I change the state of 'todo' right now, the shown list isn't changing (uncompleted task doesn't filter immediatly), it's changing only after I set showOnlyCompleted to false and back to true.
I assume it's not happening, because I don't 'update' the todoList for MobX, which I provide (using function useFilterByAllFilters) by props in My TodoList component.
In my opinion the problem is with the useMemo or with something in Mobx.
Please, help me out.
Yes, it's because of useMemo. useFilterByQuery only has [list, query] deps, but when you change some todo object to be (un)completed you don't really change the whole list, only one object inside of it. And that is why this useMemo does not rerun/recompute, so you still have the old list. Same with useFilterByAllFilters.
What you are doing is not really idiomatic MobX I would say! MobX is designed to work with mutable data and React hooks are designed to work with immutable data, so to make it work with hooks you need to do some workarounds, but it would be easier to just rewrite it like that:
class Todos {
// ... your other code
// Add computed getter property to calculate filtered list
get listByQuery() {
if (!this.query) return list
return this.todoList.filter(todo => todo.description.toLowerCase().includes(this.query.toLowerCase()))
}
// Another one for all filters
get listByAllFilters() {
if(!this.showOnlyCompleted) return this.listByQuery
return this.listByQuery.filter(todo => todo.completed)
}
}
And just use this two computed properties in your React components, that's it! No need for hooks. And these properties are cached/optimized, i.e. will only run when some of their observables change.
More info about computeds

Apollo Client cache does not update

I am using Apollo Server / Client and the cache does not seem to work on update mutations. (Create, Delete). The server gets updated but nothing happens on the front end. I have to reload the page to show the new item / show change of an item.
I followed the Apollo docs and modeled it after their sandbox implementation.
Let me know if you need more of my code, thank you.
Here is my code:
<form
onSubmit={(e) => {
e.preventDefault();
createUser(
{
variables: {
name: input.value,
email: input.value,
password: input.value
}
},
{
update(cache, { data: { createUser } }) {
cache.modify({
fields: {
allUsers(existingUsers = []) {
const newUser = cache.writeFragment({
data: { createUser },
fragment: gql`
fragment NewUser on User {
name
email
}
`
});
return existingUsers.concat(newUser);
}
}
});
}
}
);
}}
>
You need to provide an id property in the writeFragment method. Here's the example on the docs:
client.writeFragment({
id: '5',
fragment: gql`
fragment MyTodo on Todo {
completed
}
`,
data: {
completed: true,
},
});
Also, writeFragment returns void, so you need to use readFragment to get the data you want, or just use the data available in the mutation's result

React Question about promise in a for loop

I am in the process of learning React and making HTTP requests.
Recently Im trying to implement a dropdown for the webpage that Im working on. In my code I had to loop through an array of id, and make a post request for each of the id to extract the metadata. So Im encountering a problem with the dropdown options. The dropdown options are suppose to be the names for the corresponding id.
The array of id is an array of objects that looks like this
[{key: "someidnumber1", count: 5}, {key: "someidnumber2", count: 5}, {key: "someidnumber3", count: 10},....]
So what I did first is to loop through the id array, and make a post request on each of the id as parameter. This is inside my render method.
render() {
return(
<SomeOtherComponent>
{ //Do something to fetch the ids
let promises = [];
let names = [];
let options = [];
ids.map(id => {
promises.push(
axios
.post(TARGET_META_URL, {
filters: [
{
field: "id",
values: [id.key]
}
]
})
.then(response => {
// adding the name from the data into the names array
names.push(response.data[0].name);
})
});
Promise.all(promises).then(() => {
// Wait for the promises to collection all the names
// and pass into a new array
options = [...names];
}
return (
<Dropdown
options={options}
/>
);
}
</SomeOtherComponent>
);
}
My dropdown options after opening it is empty. So I did a couple console log and figured out that the options is declared outside the Promise.all so when the render() method is called, the dropdown takes in an empty array. I need help on how to setup the options for the dropdown so it waits for all the code before it finish running. I tried putting the second return inside the Promise.all() but I get an error method saying that render() doesn't have a return.
Make another component which fetches the data and renders them once the responses have come back. Use Promise.all to wait for all of the Promises to resolve together.
const getName = id => axios
.post(TARGET_META_URL, {
filters: [
{
field: "id",
values: [id.key]
}
]
})
.then(response => response.data[0].name);
const AsyncDropdown = ({ ids }) => {
const [options, setOptions] = useState();
useEffect(() => {
Promise.all(ids.map(getName))
.then(setOptions)
.catch((err) => {
// handle errors
});
}, [ids]);
return options ? <Dropdown options={options} /> : null;
}
And replace your original render method with:
render() {
return(
<SomeOtherComponent>
<AsyncDropdown ids={ids} />
</SomeOtherComponent>
);
}
Maybe this will help -
componentDidMount() {
let promises = [];
let options = [];
ids.map(id => {
promises.push(
axios
.post(TARGET_META_URL, {
filters: [
{
field: "id",
values: [id.key]
}
]
})
});
Promise.all(promises).then((response) => {
// Wait for the promises to collection all the names
// and pass into a new array
options = response.map(res => res.data[0].name);
this.setState({ options })
}
}
render() {
return(
<SomeOtherComponent>
{ this.state.options?.length ? <Dropdown options={this.state.options} /> : null }
</SomeOtherComponent>
);
}

how to get data from DB using axios

I’m having a problem.
i have list of project i want when i click in one project he take me to another page(components) this is the component ProjectDetail when i can find the detail of that project Inside of it, I have this block of code:
this is the route who take me to another components:
{path: '/detail/:id', name: detail , component: detail },
I want to get the project with the id in the url detail from DB w but nothing Happen this is ProjectDetail.vue:
export default {
data(){
return{
id: this.$route.params.id,
projets:[],
projet:{
id:'',
name:'',
durre:'',
description:'',
budget:'',
owner:'',
}
}
},
methods:{
afficherProjets(){
axios.get('api/getProjects/'+this.id)
.then(data => {
this.projets = data.data;
});
}
},
mounted() {
console.log('Component mounted.')
this.afficherProjets();
}
}
and this is my controller:
public function getProjects($id)
{
return Projet::findOrFail($id);
}
in your vue.
export default {
data(){
return{
id: this.$route.params.id,
projet:{}
}
},
methods:{
//GETID
afficherProjet(){
axios.get('api/getProject/'+this.id)
.then(data => {
this.projet = data.data;
});
}
},
mounted() {
console.log('Component mounted.')
this.afficherProjet();
}
File in routes api.php
//ALL
Route::get('getProjects', ['uses' =>'API\ProjetController#getProjects']);
//SPECIFIQ ID
Route::get('getProject/{id}', ['uses' =>'API\ProjetController#getProject']);
In your controller, app/Http/API/ProjetController
public function getProjects()
{
return Projet::latest()->paginate(15);
}
public function getProject($id)
{
return Projet::findOrFail($id);
}
Your model projet
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Projet extends Model
{
public $timestamps = false;
protected $table = 'projets';
protected $fillable = [
'name',
'durre',
'description',
'budget',
'owner'
];
}
Do you have your model?
Your error comes from the 'data.data' i thing it's empty, you should just call 'data'.
Personally i prefer Mounted.
I thing now it's working!

My query is failing in relay and I don't know why?

I have this simple query which works fine in my Graphql but I cannot pass data using relay to components and I don't know why :(
{
todolist { // todolist returns array of objects of todo
id
text
done
}
}
this is my code in an attempt to pass data in components using relay:
class TodoList extends React.Component {
render() {
return <ul>
{this.props.todos.todolist.map((todo) => {
<Todo todo={todo} />
})}
</ul>;
}
}
export default Relay.createContainer(TodoList, {
fragments: {
todos: () => Relay.QL`
fragment on Query {
todolist {
id
text
done
}
}
`,
},
});
And lastly my schema
const Todo = new GraphQLObjectType({
name: 'Todo',
description: 'This contains list of todos which belong to its\' (Persons)users',
fields: () => {
return {
id: {
type: GraphQLInt,
resolve: (todo) => {
return todo.id;
}
},
text: {
type: GraphQLString,
resolve: (todo) => {
return todo.text;
}
},
done: {
type: GraphQLBoolean,
resolve: (todo) => {
return todo.done;
}
},
}
}
});
const Query = new GraphQLObjectType({
name: 'Query',
description: 'This is the root query',
fields: () => {
return {
todolist: {
type: new GraphQLList(Todo),
resolve: (root, args) => {
return Conn.models.todo.findAll({ where: args})
}
}
}
}
});
This code looks simple and I cannot see why this won't work and I have this error Uncaught TypeError: Cannot read property 'todolist' of undefined, but I configure todolist and I can query in my graphql, you can see the structure of the query is same, I don't know why this is not working?
todolist should be a connection type on Query. Also, your ids should be Relay global IDs. You will not have access to your objects' raw native id fields in Relay.
import {
connectionArgs,
connectionDefinitions,
globalIdField,
} from 'graphql-relay';
// I'm renaming Todo to TodoType
const TodoType = new GraphQLObjectType({
...,
fields: {
id: uidGlobalIdField('Todo'),
...
},
});
const {
connectionType: TodoConnection,
} = connectionDefinitions({ name: 'Todo', nodeType: TodoType });
// Also renaming Query to QueryType
const QueryType = new GraphQLObjectType({
...,
fields: {
id: globalIdField('Query', $queryId), // hard-code queryId if you only have one Query concept (Facebook thinks of this top level field as being a user, so the $queryId would be the user id in their world)
todos: { // Better than todoList; generally if it's plural in Relay it's assumed to be a connection or list
type: TodoConnection,
args: connectionArgs,
},
},
});
// Now, to be able to query off of QueryType
const viewerDefaultField = {
query: { // Normally this is called `viewer`, but `query` is ok (I think)
query: QueryType,
resolve: () => ({}),
description: 'The entry point into the graph',
}
};
export { viewerDefaultField };
The above is not fully complete (you'll likely also need to setup a node interface on one or more of your types, which will require node definitions), but it should answer your basic question and get you started.
It's a huge, huge pain to learn, but once you struggle through it it starts to make sense and you'll begin to love it over RESTful calls.

Resources