#Ember front-end for Loopback 3 back-end

1 messages · Page 1 of 1 (latest)

proven granite
#

Help me set an ember front end for Loopback 3 API, the API itself is done and tested, but for some reason it failed to build or show on ember, any help will be appreciated, will post snippets of codes and errors I encountered.

#

Error log:

#

app/models/task.js:

import { attr } from '@ember-data/model';

export default class TaskModel extends Model {
  @attr('string') title;
  @attr('string') description;
  @attr('string') status;
  @attr('date') dueDate;
}
#

app/routes/tasks.js:


export default class TasksRoute extends Route {
  model() {
    console.log('Checking store:', this.store);

    if (this.store) {
      return this.store.findAll('task');
    } else {
      console.error('Store is undefined!');
      return [];
    }
  }
}
#

app/adapters/application.js


export default class ApplicationAdapter extends JSONAPIAdapter {
  namespace = 'api';
}
#

app/app.js:

import Resolver from 'ember-resolver';
import loadInitializers from 'ember-load-initializers';
import config from 'ember-task-manager/config/environment';

export default class App extends Application {
  modulePrefix = config.modulePrefix;
  podModulePrefix = config.podModulePrefix;
  Resolver = Resolver;
}

loadInitializers(App, config.modulePrefix);
#

any additional information required will be provided, thanks in advance!

glad pine
#

@proven granite can you show your tasks.hbs file?

I'm going to guess you're trying to access {{model.something}}, if so it should be {{@model.something}}

proven granite
#

I got:

#

task-form.hbs:

  <form {{on 'submit' this.saveTask}}>
    <div class="form-group">
      <label for="title">Title:</label>
      <input type="text" id="title" required {{on 'input' (action (mut this.title))}} />
    </div>

    <div class="form-group">
      <label for="description">Description:</label>
      <textarea id="description" {{on 'input' (action (mut this.description))}}></textarea>
    </div>

    <div class="form-group">
      <label for="status">Status:</label>
      <select id="status" {{on 'input' (action (mut this.status))}}>
        <option value="todo">To-Do</option>
        <option value="in-progress">In Progress</option>
        <option value="completed">Completed</option>
      </select>
    </div>

    <div class="form-group">
      <label for="dueDate">Due Date:</label>
      <input type="date" id="dueDate" {{on 'input' (action (mut this.dueDate))}} />
    </div>

    <div class="button-group">
      <button type="submit">Save</button>
      <button type="button" {{on 'click' this.cancel}}>Cancel</button>
    </div>
  </form>
</div>
#

and task-list.hbs:

  {{#each model as |task|}}
    <li>{{task.title}}</li>
  {{/each}}
</ul>```
glad pine
#

change

{{#each model as |task|}}

to

{{#each @model as |task|}}
proven granite
#

i still got the same error

#

but first

#

this is what my page looks like

#

after I add the @model it just become blank

glad pine
#

ah, I see the store is not injected into your route.
You'll want something like this to inject the store

import Route from "@ember/routing/route";
import { service } from "@ember/service";
// this might be import { inject as service } from "@ember/service" if you're on an older version of ember

export default class BlogPostsIndexRoute extends Route {
  @service store;

  model() {
    return this.store.findAll("posts");
  }
}
proven granite
#

wait lemme try, i tried to inject it, but it just messed up everything

#

but lemme try this one

#

hmmm

#

well its something new hahaha

#

the schema should be from the api no?

glad pine
#

hmm, that's odd.

Can you share the devDependencies of your package.json?

proven granite
#

here it is:

    "@babel/core": "^7.23.6",
    "@babel/eslint-parser": "^7.23.3",
    "@babel/plugin-proposal-decorators": "^7.23.6",
    "@ember/optional-features": "^2.0.0",
    "@ember/string": "^3.1.1",
    "@ember/test-helpers": "^3.2.1",
    "@glimmer/component": "^1.1.2",
    "@glimmer/tracking": "^1.1.2",
    "broccoli-asset-rev": "^3.0.0",
    "concurrently": "^8.2.2",
    "ember-auto-import": "^2.7.0",
    "ember-cli": "~5.5.0",
    "ember-cli-app-version": "^6.0.1",
    "ember-cli-babel": "8.2.0",
    "ember-cli-clean-css": "^3.0.0",
    "ember-cli-dependency-checker": "^3.3.2",
    "ember-cli-htmlbars": "^6.3.0",
    "ember-cli-inject-live-reload": "^2.1.0",
    "ember-cli-sri": "^2.1.1",
    "ember-cli-terser": "^4.0.2",
    "ember-data": "~5.3.0",
    "ember-fetch": "^8.1.2",
    "ember-load-initializers": "^2.1.2",
    "ember-modifier": "^4.1.0",
    "ember-page-title": "^8.1.0",
    "ember-qunit": "^8.0.2",
    "ember-resolver": "^11.0.1",
    "ember-source": "~5.5.0",
    "ember-template-lint": "^5.13.0",
    "ember-welcome-page": "^7.0.2",
    "eslint": "^8.55.0",
    "eslint-config-prettier": "^9.1.0",
    "eslint-plugin-ember": "^11.11.1",
    "eslint-plugin-n": "^16.4.0",
    "eslint-plugin-prettier": "^5.0.1",
    "eslint-plugin-qunit": "^8.0.1",
    "loader.js": "^4.7.0",
    "prettier": "^3.1.1",
    "qunit": "^2.20.0",
    "qunit-dom": "^2.0.0",
    "stylelint": "^15.11.0",
    "stylelint-config-standard": "^34.0.0",
    "stylelint-prettier": "^4.1.0",
    "tracked-built-ins": "^3.3.0",
    "webpack": "^5.89.0"
  },```
glad pine
#

Do you have an application serializer file?

#

something like

// app/serializers/application.js
import JSONAPISerializer from '@ember-data/serializer/json-api';

export default class ApplicationSerializer extends JSONAPISerializer {
}
proven granite
#

actually no

#

should I add it?

glad pine
#

Yep, though I'm not 100% sure that is the issue, but if you're using JSON:API the app needs to know to use it.

proven granite
#

lemme tinkering with it for a bit

#

I just learn this thing this morning, still need to get a grasp of it

glad pine
#

Yeah ember data has a bit of a learning curve but once it's working and you start to understand it, it definitely makes dealing with your API a lot nicer than just using fetch.

#

(fetch is also a totally valid option though btw)

#

Quick question, in the network tab do you see the app making a the call to your tasks api?

#

and if you do can you share the JSON

proven granite
#

this is what i got in the network tab

glad pine
#

Can you show me an example of the JSON returned from your API. I'm not familiar with Loopback, but I'm not sure it uses JSON:API (it's a specification https://jsonapi.org/).

If it's just normal plain JSON you'll probably want to use the RESTAdapter and RESTSerializer instead of the JSONAPI variants.

proven granite
#

parsed:

{
"title": "Updated Task",
"description": null,
"status": "to-do",
"dueDate": null,
"id": 1,
"createdAt": "2024-01-06T16:50:29.755Z",
"updatedAt": "2024-01-06T16:51:26.436Z"
}
]```

raw:
```[{"title":"Updated Task","description":null,"status":"to-do","dueDate":null,"id":1,"createdAt":"2024-01-06T16:50:29.755Z","updatedAt":"2024-01-06T16:51:26.436Z"}]```
#

its something like this

glad pine
#

Ok, you'd have to implement your own serializer to be able to parse that correctly. But I still think you're running into another issue right now (since the app doesn't appear to be making the API call at all right now)

The ember-data store requires all JSON it receives to be normalized so it conforms with the JSON:API specification. But that might be jumping into the deep end if ember is all new to you today.

If you're just trying to learn ember today, I'd suggest just using fetch in your routes model() method and working with the data as a plain JS object so you can get a feel for ember itself.

If you want to dive into it though you'd setup a normalizeResponse method in your application serializer that receives the data from your API and transforms it so it matches the https://jsonapi.org specification.

The example json would be something like

{
  "data": [
    {
      "type": "tasks"
      "id": 1,
      "attributes": {
        "title": "Updated Task",
        "description": null,
        "status": "to-do",
        "dueDate": null,
        "createdAt": "2024-01-06T16:50:29.755Z",
        "updatedAt": "2024-01-06T16:51:26.436Z"
      }
    }
  ]
}
#

@violet magnet do you know what might be causing the error no model was found for 'tasks' and no schema handles the type with how mado has setup their model? It seems ok to me.

proven granite
#

wait lemme read the doc first

#

just get back from taking a break

glad pine
#

yeah no worries, ember-data is undergoing quite a bit of transformation at the moment, so those docs are marked as legacy, but should give you the info you need to build a serializer.

#

I've pinged you in our ai chat room with an example serializer using the JSONSerializer.

proven granite
#

thank you man!

proven granite
#

@glad pine hey man

#

i found that i have tasks.hbs somehow

#

tasks.hbs:

<h2>Task List</h2>
{{task-list model=model}}
{{outlet}}
#

does this have anything to do with model?

glad pine
#

it does, so task-list is a component right?

Modern convention would be to mark that up as <TaskList @model={{@model}} />

#

tasks.hbs will be your route template file

proven granite
#

well we get back to the first error log again hahaha

glad pine
#

So to explain whats happening here

{{task-list model=model}}

the model you are passing to the component is a variable that doesn't exist basically. With modern ember (you're on 5.5.0 so it's modern) the model() method in the route file is passed to your template as @model so you would pass it to your component like

{{task-list model=@model}}

but in modern ember we have angle brackets for components

<TaskList @model={{@model}} />

The @model "attribute" is prepended with @ to distinguish it from normal attributes like class

This is then accessible in your components template as @model, but you could give the attribute any name eg

<TaskList @taskList={{@model}} />

then in your component you would access @taskList instead of @model

proven granite
#

idk if you ever do ruby on rails

#

but this is similar to that

#

thanks for the explanation

#

still the error log persists

#

hahaha

glad pine
#

which error?

#

sidenote Ember was originally built by big fans of rails, so it took a lot of inspiration from it initially 🙂

#

could you send a screenshot of your expanded app folder structure?

#

just so I can make sure everything is in the right location

proven granite
glad pine
#

What's in your services/store.js? (that file shouldn't be needed unless you're doing something very custom)

#

Also can you show me the contents of

adapters/application.js
adapters/task.js
serializers/application.js
serializer/task.js

Shouldn't need the task specific files if we can handle it generically in the application adapter and serializer.

proven granite
#

lemme check

#

just get back after break

#

hahah very much needed

proven granite
#

adapters/application.js:


export default class ApplicationAdapter extends JSONAPIAdapter {}
#

adapters/task.js:


export default ApplicationAdapter.extend({
  pathForType: (modelName) => modelName,
});```
#

serializers/application.js:


export default class ApplicationSerializer extends JSONAPISerializer {}```
#

serializer/task.js:


export default class TaskSerializer extends ApplicationSerializer {}```
glad pine
#

Thanks

#

So I think you can remove the store service file, it will be made available to your app without needing to re-export it yourself.

Now since Loopback returns just basic JSON we shouldn't use the JSONAPIAdapater or JSONAPISerializer as they expect a very specific format for the JSON.

Change your adapters/application.js to

import RESTAdapter from '@ember-data/adapter/rest';

export default class ApplicationAdapter extends RESTAdapter {
  namespace = 'api';
  // pathForType = (modelName) => modelName;
  // this is commented out as I don't think you'll need it but we can uncomment if needed
}

Change your serializers/application.js to

import JSONSerializer from '@ember-data/serializer/json';

export default class ApplicationSerializer extends JSONSerializer {}

remove the adapters/task.js and serializers/task.js files

Let's see if that changes/fixes things.

#

Also this assumes your api will be accessible on http://localhost:4200/api/tasks if that is not correct you'll need to add a host field to the ApplicationAdapter eg

import RESTAdapter from '@ember-data/adapter/rest';

export default class ApplicationAdapter extends RESTAdapter {
  host = 'http://url-to-your-api-server.com';
  namespace = 'api';
}
proven granite
#

should i fill api to it?

#

because api accessible on localhost:4200/tasks

glad pine
#

then you can remove the namespace

proven granite
#

and the host api server is localhost:3000/tasks

glad pine
#

but you will need to set host to http://localhost:3000

proven granite
#

aight

#

ill try

#

this is what ive got

#

afaik, the yellow part we can ignore no?

glad pine
#

ahh, that'll be why you had that file 😄 sorry

#

give me a minute to figure out why my app doesn't do that

#

ahh, I'm using a different folder structure so my store.js is located somewhere else. Sorry, you will want to undelete the services/store.js file. My bad!

#

Once the yellow warning goes away show me you current task-list.hbs file

proven granite
#

me rn:

#

hahaha just kidding

#

trying to lighten the mood

#

my mood exactly

glad pine
#

:p

#

We'll get there!

proven granite
#

tasks-list.hbs:

  {{#each tasks as |task|}}
    <li>{{task.title}} - {{task.description}}</li>
  {{/each}}
</ul>```
#

here mate

glad pine
#

{{#each @tasks as |task|}}

any arguments you pass to a component will need the @ prepending to the name

Are you following a third party guide? Some of the code you've shared is the old way of doing things.

proven granite
#

tbh, I cant find any proper guide, thats why I just try to compile the scattered tutorials here and there

#

and a lot of questions and answers from stackoverflow that I found is from 2016-2018

glad pine
#

yep, a lot of the tutorials you'll find on google and SO answers are quite outdated. Definitely makes things more difficult for people new to ember.

If you are able to afford it I highly recommend this ebook https://balinterdi.com/rock-and-roll-with-emberjs/

proven granite
#

YOOOOO

#

the other red is gone

proven granite
#

hahhaa

#

honestly, and a lil bit of backstory, I used to do RoR and JS, but I couldnt land a job, anywhere, so I applied to some country, and 1 from singapore give me this test, which I never touch at all, but I thought I could do it, and after scrambling in loopback 3 for 3 days, here we are at day 1 ember🥹

glad pine
#

The official guides and api docs do a pretty decent job as well but they expect everything to just be working and using JSON:API specification API's, so if you need something custom it can be difficult to figure out what you need to do.

#

Well let's get you to a point where you can talk to the API and land you that job 🙂

#

So, that error looks like your tasks.js route file is missing the service injection?

proven granite
#

I think right now

#

its just the matter of connection I guess?

#

like the structure's there, but it just not hitting it

glad pine
#
import Route from '@ember/routing/route';
import { service } from '@ember/service';

export default class TasksRoute extends Route {
  @service store;

  model() {
    console.log('Checking store:', this.store);

    if (this.store) {
      return this.store.findAll('task');
    } else {
      console.error('Store is undefined!');
      return [];
    }
  }
}
#

Your tasks.js should look someting like that

#

with the store being injected to the class

glad pine
proven granite
#

well this is new

#

never get this error log before

#

lemme read it

glad pine
#

If you check network tab now you'll see we made a connection to the api 🙂

Couple of more steps and you should be good to go 😄

proven granite
#

yoooo

#

it connects tho

#

if you dont mind

#

would you explain like what went wrong?

proven granite
#

not exactly

#

but it got different error

glad pine
#

Yep, so the store is now correctly making a connection to the API and it's trying to ingest the JSON it sees, but Loopback doesn't follow the JSON:API specification (https://jsonapi.org/) so we need to adjust the adapter and serializer so they understand what to do with the basic JSON it's getting.

#

Did you make these changes before?

#

notice we are importing the RESTAdapter and JSONSerializer instead of JSONAPIAdapter and JSONAPISerializer

proven granite
#

adapter:


export default class ApplicationAdapter extends RESTAdapter {
  host = 'http://localhost:3000';
}```
#

ser:


export default class ApplicationSerializer extends JSONSerializer {}```
#

i think yes

glad pine
#

ok, and did you delete the adapters/task.js and serializers/task.js files?

#

customizing ember-data to work outside of it's default expectations is the hardest part of ember, so once we get this working everything else should be a lot simpler to get working and understand

proven granite
#

im still looking too who got similar issues

glad pine
#

So, older versions of ember-data would do this transformation for you, but the newer versions needs you to tell it how to normalize your payloads, which I was unaware of.

So two options, downgrade ember-data to the older version (4.12.5), or we write the normalize operation ourselves.

#

hang on, let me do some more investigating

#

looking at the source code it should still be doing the normalization for you.

#

let me keep digging, something is going wrong

proven granite
#

I kept seeing people stumble across the ember-data version too

#

Ill be waiting

glad pine
#

yeah I'm not sure what I clicked but I saw in the source code an empty normalize method but now I can't find that, looking at the code on npm I can see it should still be doing the transformation

#

oh I was looking at the base serializer instead of the json serializer

#

in your application serializer can you add this method

normalize(modelClass, resourceHash) {
  const normalized = super.normalize(modelClass, resourceHash);
  console.log('should be normalized');
  return normalized;
}

Then see if it is logs out that console message

proven granite
#

like this?


export default class ApplicationSerializer extends JSONSerializer {}

normalize(modelClass, resourceHash) {
  const normalized = super.normalize(modelClass, resourceHash);
  console.log('should be normalized');
  return normalized;
}
glad pine
#

into the class

import JSONSerializer from '@ember-data/serializer/json';

export default class ApplicationSerializer extends JSONSerializer {
  normalize(modelClass, resourceHash) {
    const normalized = super.normalize(modelClass, resourceHash);
    console.log('should be normalized');
    return normalized;
  }
} 
proven granite
#

oof yea

#

my bad, my brain is loading so hard

glad pine
#

No worries 🙂 Lots to take in right now lol

proven granite
glad pine
#

hmm

#

can you try restarting the ember dev server

#

it's like the serializer is not being used

proven granite
#

I restarted the server before

#

restarted again

#

still the same

glad pine
#

ok, and that file is definitely in app/serializers/application.js?

proven granite
#

yessir

glad pine
#

can you search your codebase for JSONAPISerializer

violet magnet
#

normalize isn’t guaranteed to be called

#

The public contract is normalizeResponse

#

Implementations of that may or may not delegate to other methods (like normalize) depending on the payload shape and their implementation specifics

glad pine
#

ok, but shouldn't the JSONSerializer be normalizing the JSON into JSON:API without needing to add anything to the application serializer?

violet magnet
#

unsure, the original format seems closer to REST and the JSONSerializer is really very basic

glad pine
#

@proven granite whats the response you're getting from the api network request?

proven granite
#

network

glad pine
#

can you show the JSON that the tasks request returns, is it

[
  {
    "title": "Updated Task",
    "description": null,
    "status": "to-do",
    "dueDate": null,
    "id": 1,
    "createdAt": "2024-01-06T16:50:29.755Z",
    "updatedAt": "2024-01-06T16:51:26.436Z"
  }
]
glad pine
#

let's make sure the serializer is being loaded and calling normalizeResponse then

import JSONSerializer from '@ember-data/serializer/json';

console.log('I am in your app');

export default class ApplicationSerializer extends JSONSerializer {
  normalizeResponse(store, primaryModelClass, payload, id, requestType) {
    const normalized = super.normalizeResponse(store, primaryModelClass, payload, id, requestType);
    console.log('should be normalized');
    return normalized;
  }
} 
proven granite
#

it is not I guess

glad pine
#

well that's odd, you should at least see I am in your app logged

violet magnet
#

Are we sure about what’s coming back in the network tab?

#

And are we sure the task serializer was deleted and only the application serializer remains?

proven granite
#

it is deleted

glad pine
#

i see the problem 😄

#

not sure when but you renamed the folder from serializers to serializer

proven granite
#

oh my

#

it showed up

#

FFS

proven granite
#

Thanks a lot guys!

#

Like really

#

thanks

glad pine
#

You're welcome

proven granite
#

especially you @glad pine bro

glad pine
#

Any other issues just ask again

proven granite
#

Aight, I wont close this thread, so we can continue here

#

next step is get a sleep

glad pine
#

And good luck with the rest of the app 🙂

#

You earned it!

proven granite
#

been doing this for 15hrs

glad pine
#

Sleep well

proven granite
proven granite
#

@glad pine yo bro, just wanted to give you an update, the recruiter took an interest in me, and he gave me time to finish what I can't finish, but its looking bright for now

#

and if you dont mind, will you help me with 1 or 2 things?