Skip to content

Use inherited models in two-way databinding #33

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Tinostarn opened this issue Oct 12, 2016 · 17 comments
Closed

Use inherited models in two-way databinding #33

Tinostarn opened this issue Oct 12, 2016 · 17 comments
Labels

Comments

@Tinostarn
Copy link

Tinostarn commented Oct 12, 2016

Hi,

I created a Datastore service, and updated a model extending JsonApiModel, as described in the doc.
Now, I don't know how to make the model works in a basic two-way databinding form.

In my component :

export class CreateNoteComponent {

    note: Note = new Note();

    constructor() { }

    onSubmit() { 
         console.log(this.note);
    }

}

My Note model :

@JsonApiModelConfig({
    type: 'notes'
})
export class Note extends JsonApiModel {

    @Attribute()
    title: string;

    @Attribute()
    content: string;

    /** Removed this constructor, as it enters in conflict with the properties declared with @Attributes */
    /** constructor ( 
         public title: string,
         public content: string
    ) { } */
}

My view :

<input type="text" name="title" [(ngModel)]="note.title" />
<input type="text" name="content" [(ngModel)]="note.content" />
<button type="submit" (click)="onSubmit()">Submit</button>
{{ JSON.stringify(claimFile) }}

Before I change my model and make it inherit from JsonApiModel, everything worked fine. Each change I typed in the inputs was reflected in the model and displayed in the debug string interpolation.

Now Typescript gives me this error :

create-note.component.ts
(11,28): error TS2346: Supplied parameters do not match any signature of call target.

It comes from the note: Note = new Note() line.
Seems normal as the JsonApiModel classes has its own constructor signature...

So my questions are :

  • is it still possible to instanciate my model like before in a component ?
  • If not, how can I achieve the two-way databinding ?
  • in the doc, for a creation request, it is said :
this.datastore.createRecord(Post, {
    title: 'My post',
    content: 'My content'
});

I'd like to pass my model as the second argument and not have to explicitely describe each values (especially in a Service). How can I make it ?

Thx a lot for your help

@Tinostarn Tinostarn changed the title Use JsonApiModel inherited models in two-way databinding Use inherited models in two-way databinding Oct 12, 2016
@ghidoz
Copy link
Owner

ghidoz commented Oct 12, 2016

Your model should be just this:

@JsonApiModelConfig({
    type: 'notes'
})
export class Note extends JsonApiModel {

    @Attribute()
    title: string;

    @Attribute()
    content: string;
}

and, as you already read, you should create it with:

this.note = this.datastore.createRecord(Note, {
    title: 'My note',
    content: 'My content'
});

If you do this, everything should go well.

At the moment, the error you receive is because you are overwriting the JsonApiModel constructor (even if you don't need to do it), without injecting the right parameters in the super() constructor.

@Tinostarn
Copy link
Author

Tinostarn commented Oct 12, 2016

Hi @ghidoz
Thx a lot for your answer.

Well, actually, I commented the constructor that was in my model but maybe you didn't notice. It was just to show how it was before. So no, I didn't overwrite the constructor. After that, the error message is pretty explicit, as it says that I don't provide the right arguments (logical, since I instanciate an empty Note object without any argument)

I don't know if I was clear enough about the blocking point I'm encountering, or maybe it's me who's taking this problem the wrong way. Let's try to be simple :

How can I do two-way databinding, using the model that extends JsonApiModel ?

Before using angular2-jsonapi, I did it in the standard way (the way Angular2 describe in the official doc):

// create-note.component.ts
export class CreateNoteComponent {

    public note: Note = new Note();

    constructor(noteService: NoteService) { }

    onSubmit() {
        this.noteService.createNote(this.note);
    }

}

// create-note.component.html
<input type="text" name="title" [(ngModel)]="note.title" />
<input type="text" name="content" [(ngModel)]="note.content" />
<button type="submit" (click)="onSubmit()">Submit</button>
{{ note | json }}

Now I can't do this anymore, as I can't instantiate a Note object in my component, as it now inherits from JsonApiModel.
If I don't instanciate it, I don't have any Note object binded to the view. And yes I need a model constantly updated in the view to perform some checks.
I also prefer having a model two-way binded to the form, because I don't want to explicitely describe each values to this.datastore.createRecord() method, but doing something like :
this.datastore.createRecord(Note, note) // note being the model of my component

I hope my problem is clearer. Thx to correct me if I take the problem in the wrong way, I'm still new in Angular2.

@ghidoz
Copy link
Owner

ghidoz commented Oct 12, 2016

Ok, I got the point: you need the note object right in the form, before submitting it.
You can try like this:

export class CreateNoteComponent {

    public note: Note;

    constructor(private datastore: Datastore) {
        this.note = this.datastore.createRecord(Note);
    }

    onSubmit() {
        this.note.save().subscribe(() => {
            // saved!
        });
    }

}

@Tinostarn
Copy link
Author

It looks better, but another problem : createRecord() method returns a jsonApiModel object, whereas I expect note to be a Note object :

create-note.component.ts (5,9): error TS2322: Type 'JsonApiModel' is not assignable to type 'Note'. Property 'title' is missing in type 'JsonApiModel'.

If I remove the typing
public note;
It works.

Any other solution or am I forced to not type any of my objects ?

@ghidoz
Copy link
Owner

ghidoz commented Oct 13, 2016

If you upgrade to the new v.3.2.0 you shouldn't have the type error anymore.

@Tinostarn
Copy link
Author

Tinostarn commented Oct 13, 2016

Allright.
Is there any problem with the last release ?

I just updated my package.json with the last version :
"angular2-jsonapi": "^3.2.0",
and run
npm update

I have an unexpected :
./app/shared/datastore.service.ts (2,58): error TS2307: Cannot find module 'angular2-jsonapi'. compiler.umd.js?9df7:13996 Uncaught Error: Unexpected value 'JsonApiModule' imported by the module 'AppModule'

When I open the angular2-jsonapi folder in my node_modules, I've got like a not compiled repository :

dist/
node_modules/
test/
...
karma.conf.js
package.json
tsconfig.json
tslint.json
webpack.test.conf.js
...

Same thing after an uninstall and reinstall of the package.
Also tried to include the module from the dist/ folder but still the same second error message.

@ghidoz
Copy link
Owner

ghidoz commented Oct 13, 2016

Mmm... it seems the same problem of #13. Try to put the importing of angular2-jsonapi as the first import.

Btw there are no problems with the release, as long as you have the dist folder. It's normal to have also the source.

@Tinostarn
Copy link
Author

Tinostarn commented Oct 13, 2016

Well, strange.
I actually had the problem described in #13 yesterday when I installed the 3.1
I changed the position of the JsonApiModule in my ngModule imports[] and then it worked

But now, the same message remains. Also I tried to move the
import { JsonApiModule } from 'angular2-jsonapi';
to the top of my app.module.ts
Tried to point to angular2-jsonapi/dist too
Nothing works

Strange thing also is that my VS code underline the import with

Cannot find module 'angular2-jsonapi'

I use webpack 1.13.2, but not angular-cli

@ghidoz
Copy link
Owner

ghidoz commented Oct 13, 2016

Later I'll try to check. Meanwhile you can downgrade again to v3.1.0 and use this workaround:

this.note = <Note>this.datastore.createRecord(Note);

@Tinostarn
Copy link
Author

Tinostarn commented Oct 13, 2016

Yeah I think I don't have other choice than downgrade.

I tried every fix proposed in the issues related to #13, like adding
resolve: { modules: [ path.join(__dirname, "node_modules") ] },
to my webpack config, but none of the solution worked.
But I guess we should create another ticket for that.

I'll do the downgrade this afternoon and try the transtyping as your proposed, and come back to you
Thx :)

@Tinostarn
Copy link
Author

Tinostarn commented Oct 13, 2016

@ghidoz your workaround seems to make the job. Good :)

I hope I'll be able to migrate to the last version soon.

Another question, more about the initial topic : does angular2-jsonapi preconise to not use any services between view component and Datastore ? In other terms, should components call directly the Datastore service ?

I have a NoteService that I instanciate in my CreateNoteComponent, from my point of view this service should be responsible of passing datas from client to API, with the help of Datastore.
But for the creation of a record, the examples shows the fact that we call directly the datastore.createRecord() in a component. It returns an object A on which we can call save() that returns an Observable B, on which we can subscribe, etc....
As I need to get the object A on my component to bind it to the form, how should I do ?

  • Either I forget definitely my services and I use Datastore within my component. Good practice ?
  • Either I create a method in my service that returns my object A to my component, but it sounds wonky...

@Tinostarn
Copy link
Author

Tinostarn commented Oct 13, 2016

I tried this approach, to keep my service :

create-note.component.ts

export class CreateNoteComponent {
    note: Note;

    constructor(private noteService: NoteService) {
        this.note = <Note>{}; // transtyping an empty object binded to the form
    }

    onSubmit() {
        this.noteService.createNote(this.note);
    }
}

note.service.ts

export class NoteService {

    constructor(private datastore: Datastore) { }

    createNote(note: Note) {
        this.datastore.createRecord(Note, note).save().subscribe();
    }
}

I used an empty object as a member of my component to get the databinding, and transtyped it to to pass the typing.
What's your thought about it ?
I can't find a proper way to instanciate a real Note object from my NoteService and pass it directly to the component.

@ghidoz
Copy link
Owner

ghidoz commented Oct 13, 2016

does angular2-jsonapi preconise to not use any services between view component and Datastore ? In other terms, should components call directly the Datastore service ?

Yes, the idea is having just one service that manage everything, called directly from the components.

Either I forget definitely my services and I use Datastore within my component. Good practice ?

Exactly. There's no point in having a specific service for each resource, mapping the datastore methods.

@Tinostarn
Copy link
Author

Allright. Is it not a bit risky ?
Well, so far, I don't have enough code, but when the usecases will appear, I fear that some resources need specific treatment (filtering, editing...)
Where should these functions be ? If it's in a component, this will complexify it.
Datastore ? I'm not sure it's the right place to put it.
Also, I try to follow as much as possible the recommendations of Angular doc, and they always do the subscribe() to an Observer within a service. Feel weird to do it in a component.

Well I'm interested by your thought, as it's the first time I have to deal with that kind of architecture. Maybe I make it more complex in my mind than it is, and the mapping in the Datastore is largely enough (as long as you define correctly your entities & relations)...

@ghidoz
Copy link
Owner

ghidoz commented Oct 13, 2016

Well, maybe it depends on how complex is your application, yes. In some cases it may be useful creating custom services, when doing filtering, editing... as you suggested.

Btw, what I recommend you is to not subscribe the Observer within a service. You should do it in a component, as the service should just return the cold Observer. I don't know where do you find this recommendation, but I assure you it is the opposite. Have a look to this Angular style guide, for example: they refactor all the logic inside the service, but the subscribe() is still in the component ;)

@Tinostarn
Copy link
Author

You're right, the service should only return a cold Observer. I don't remember where I saw a service subscribing, but maybe I've been mistaken by all these updates :)
Ok, I'll follow your advices then. But for some more complex cases, do you think the example I posted above with the service between the component and the Datastore is ok ?

Then I think this discussion is closed. It's clearer for me, thanks for your help :)

@ghidoz
Copy link
Owner

ghidoz commented Oct 13, 2016

Well, the right version should be without the subscribe. Btw in the future may be a new interface that will let you do better what you are trying to do: #34

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants