In the process of writing my first Ember-app, I have not been able to figure out how to POST a new resource to my API using the REST Adapter.
The app successfully retrieves a list of resources and displays it, so it seems my Model is defined correctly and the communication between the Adapter and the API works. When I click on the CreateBugView-button, the new instance shows up in the list, so this part works as well. However, I can see in the inspector that no POST-request is made to the API and when I refresh the page, the instance is, predictably, nowhere to be seen.
I am using the 1.0.0-rc.1 version of Ember and a version of Ember Data that I cloned and built today.
Below is my code, I'd be grateful to anyone who could help me figure out what's wrong.
App = Em.Application.create()
# Templates
require 'templates/application'
require 'templates/index'
# Models
App.Bug = DS.Model.extend
name: DS.attr 'string'
# Views
App.CreateBugView = Em.View.extend
tagName: 'button'
click: (evt) ->
bug = App.Bug.createRecord
name: 'Sean'
# Routes
App.IndexRoute = Em.Route.extend
model: -> App.Bug.find()
setupController: (controller, bugs) ->
controller.set 'bugs', bugs
controller.set 'App', App
# Store
App.Store = DS.Store.extend
revision: 12
# Router
App.Router.map ->
#route 'index', path: '/'
App.initialize()
Index-template
<ul>
{{#each bugs}}
<li>{{name}}</li>
{{/each}}
</ul>
{{#view App.CreateBugView}}
Create new bug!
{{/view}}
I believe that the model only gets persisted once commit() is called on the store.
Obviously, I can't fully test without building the REST api, but using the fixture adapter, I can call a save() method on my controller, which pushes the #store.commit(). For a rest adapter, this should produce the POST event.
Please have a look at this jsFiddle:
http://jsfiddle.net/nrionfx/uqRG5/5/
click: (evt) ->
bug = App.Bug.createRecord
name: 'Sean'
#.get('controller').save()
and
App.IndexController = Em.ArrayController.extend
save: ->
console?.log("Commiting changes to store")
#store.commit()
Related
Maybe I don't really understand the this.getView().getModel().refresh(true) or updateBindings.. Somehow it doesn't refresh the model, or my main idea is wrong. I mean; I can do a workaround to call a function that reads the odata service again, but this is not really beautiful. So, I read the Model in the onInit
onInit: function () {
var that = this;
var oViewModel = new sap.ui.model.json.JSONModel({});
this.getView().setModel(oViewModel, "detailView");
sap.ui.getCore().setModel(oViewModel,"detailView");
var oFilter = [];
var zAppFilter = new sap.ui.model.Filter("XXX", sap.ui.model.FilterOperator.EQ, "XXXX");
oFilter.push(zAppFilter);
var oModel = that.getView().getModel();
oModel.setDefaultBindingMode("TwoWay");
oModel.read("/XXXXSet", {
filters: oFilter,
success: function (oData) {
that.getView().getModel("detailView").setData(oData.results);
},
// ...
});
},
I use this "detailView"-JSONModel model in my view for bindings. This works.. Now, the add or delete function for example:
onDelete: function (oEvent) {
var that = this;
var oModel = this.getOwnerComponent().getModel();
var oSelectedItem = oEvent.getSource().getParent();
var oSourceID = oSelectedItem.getBindingContext("detailView").getObject().Zid;
oModel.remove("/XXX(XXX='XXX',XXXX='" + XXXX+ "')", {
method: "DELETE",
success: function(data) {
that.getView().getModel("detailView").refresh(true);
sap.ui.getCore().getModel("detailView").refresh(true);
},
// ...
});
},
That does not work.. but why? I mean also when I do updateBindings or something else. Am I understanding or doing something wrong?
Your JSONModel is not connected to anything. It's just a bunch of JSON data. So if you tell it to refresh, how should it know where to get the new data?
What refresh does not do is getting new data.
What refresh actually does (in a JSONModel) is telling the bindings that it has new data. One of these bindings can be the items of a sap.m.List for example. The list then knows that it needs to rerender to show the new data.
If you don't fetch new data and call refresh nothing will happen. The actual data is still the same.
i can do a workaround to call a function that reads agean the odata service but this is not really beautyfull
Well using an additional JSONModel when you already have a perfectly fine ODataModel isn't beautiful in the first place. If you just dropped your JSONModel and bound your view to your ODataModel then the view would automatically update after calling remove.
To bind the view to your ODataModel you can start with
<Table id="table0" items="{/XXXXSet}">
Don't forget to remove detailView from your cells.
You're mixing a client-side model (JSONModel) with a server-side model (ODataModel), expecting them to synchronize.
Client-side models and server-side models are two separate models serving two different purposes.
Client-side models
The main purpose of the client-side models is to provide and to sync data that are only available during the runtime of the application. If the app is gone, the data are gone. Some of the prominent use cases of client side models are:
Device model via JSONModel which provides information about user's device and its states.
ResourceModel which provides client side translatable UI texts for i18n purposes.
Synchronizing states from UI or application
The models here are not aware of any server-side data, and they shouldn't since it's not their purpose.
When dealing with a remote data provider that complies with a certain specification (e.g. OData or FHIR), the appropriate server-side model should be used instead.
Server-side models
Server-side models, such as ODataModel, have the advantage that they're server aware.
They know how to fetch, delete, update, create data, and even call functions from the backend system. They can be used to share states between the client and the server efficiently.
How? Simply use the server-side model in the binding definition directly. With OData as the default model for example:
<List items="{
path: '/MyEntitySet',
filters: [
{
path: 'ThatProperty',
operator: 'EQ',
value1: 'something'
}
]
}"> <!-- given "MyEntitySet", "ThatProperty", "EntityTitle", and "EntityDesc" are defined in $metadata -->
<StandardListItem title="{EntityTitle}" description="{EntityDesc}" />
</List>
This creates an ODataListBinding instance which will send a request to the service with the following URL:
https://....svc/MyEntitySet?$filter=ThatProperty eq 'something'
When the request succeeds, the list will show the entities accordingly. Afterwards, when calling myODataModel.remove(...);, the corresponding list will be refreshed automatically.
TL;DR
Am I understanding or doing something wrong?
Yes. Having an intermediate JSONModel in such cases is a common anti-pattern creating high maintenance costs. Try using the ODataModel only. The framework will do the work for you.
I am using:
.NET Framework 4.7.2
Visual Studio 2017
OData V4
Web API V 5.2.4 with Entity Framework V6.2 code first with existing MS SQL DB
My $expand and $select commands are generating an error. For example, when I use the select command as follows:
http://localhost:62681/data/Advances?$select=Description
I get the following error:
The query specified in the URI is not valid. Could not find a
property named 'Description' on type
'Microsoft.AspNet.OData.Query.Expressions.SelectSome_1OfAdvance'
$filter and $orderby do work
The odd thing is that this used to work a couple of weeks ago, but when I came back from vacation I could not get it to work. Any help is appreciated.
I've scoured the internet for any clue to my problem, but no luck.
I've upgraded to Microsoft.OData.Core version=7.5.0 and Microsoft.OData.Edm" version="7.5.0
My register method in WebApi.Config does have the line config.Select().Expand().Filter().OrderBy().Count().MaxTop(null);
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
ODataModelBuilder builder = new ODataConventionModelBuilder();
config.Select().Expand().Filter().OrderBy().Count().MaxTop(null);
builder.EntitySet<Advance>("Advances");
builder.EntitySet<Advance_Payments>("Advance_Payments");
config.MapODataServiceRoute(
routeName: "ODataRoute",
routePrefix: "data",
model: builder.GetEdmModel());
config.AddODataQueryFilter(new SecureAccess2Attribute());
}
What am I doing wrong?
I found the solution in another stack overflow posting
EntitySetController $expand and $select not working
Go down about 2/3 of the page and look for a posting that starts with:
I came across a similar issue in OData V4. In this case it turned out if you used an attribute on the Get method and registered another attribute in your config, it errors because you are calling the EnableQuery code twice:
For OData V4, Remove the attribute [EnableQuery] on Controllers/Methods in the controller class IF you have enabled it globally. It accepts it only at one place.
this.getModel.metadataLoaded()
What is the main purpose of using this metadataLoaded?
What are the advantage and disadvantage of metadataLoaded?
Metadata is the information about the oData service itself. It contains entity, entityset, association information, field labels and all the other configuration it needs. The oData model uses it, smart controls are built on top of it. You'll find calls to fetch this at the start of every application (look for $metadata in the console of the browser). Before this is loaded, you can't use the service. In most cases this will be ready before your view is displayed.
metadataLoaded() returns a promise you can use to do things if you want to make certain that the service is ready, like:
this.getModel().metadataLoaded().then(_ => {
//use the service here to load some data
this.getView().bindElement({
path: `/PathToMyEntitySet('Key')`,
events: {
dataRequested: _ => this.getView().setBusy(true),
dataReceived: data => console.log(data),
change: _ => this.getView().setBusy(false)
}
});
});
I've implemented a REST/CRUD backend by following this article as an example: http://coenraets.org/blog/2012/10/creating-a-rest-api-using-node-js-express-and-mongodb/ . I have MongoDB running locally, I'm not using MongoLabs.
I've followed the Google tutorial that uses ngResource and a Factory pattern and I have query (GET all items), get an item (GET), create an item (POST), and delete an item (DELETE) working. I'm having difficulty implementing PUT the way the backend API wants it -- a PUT to a URL that includes the id (.../foo/) and also includes the updated data.
I have this bit of code to define my services:
angular.module('realmenServices', ['ngResource']).
factory('RealMen', function($resource){
return $resource('http://localhost\\:3000/realmen/:entryId', {}, {
query: {method:'GET', params:{entryId:''}, isArray:true},
post: {method:'POST'},
update: {method:'PUT'},
remove: {method:'DELETE'}
});
I call the method from this controller code:
$scope.change = function() {
RealMen.update({entryId: $scope.entryId}, function() {
$location.path('/');
});
}
but when I call the update function, the URL does not include the ID value: it's only "/realmen", not "/realmen/ID".
I've tried various solutions involving adding a "RealMen.prototype.update", but still cannot get the entryId to show up on the URL. (It also looks like I'll have to build the JSON holding just the DB field values myself -- the POST operation does it for me automatically when creating a new entry, but there doesn't seem to be a data structure that only contains the field values when I'm viewing/editing a single entry).
Is there an example client app that uses all four verbs in the expected RESTful way?
I've also seen references to Restangular and another solution that overrides $save so that it can issue either a POST or PUT (http://kirkbushell.me/angular-js-using-ng-resource-in-a-more-restful-manner/). This technology seems to be changing so rapidly that there doesn't seem to be a good reference solution that folks can use as an example.
I'm the creator of Restangular.
You can take a look at this CRUD example to see how you can PUT/POST/GET elements without all that URL configuration and $resource configuration that you need to do. Besides it, you can then use nested resources without any configuration :).
Check out this plunkr example:
http://plnkr.co/edit/d6yDka?p=preview
You could also see the README and check the documentation here https://github.com/mgonto/restangular
If you need some feature that's not there, just create an issue. I usually add features asked within a week, as I also use this library for all my AngularJS projects :)
Hope it helps!
Because your update uses PUT method, {entryId: $scope.entryId} is considered as data, to tell angular generate from the PUT data, you need to add params: {entryId: '#entryId'} when you define your update, which means
return $resource('http://localhost\\:3000/realmen/:entryId', {}, {
query: {method:'GET', params:{entryId:''}, isArray:true},
post: {method:'POST'},
update: {method:'PUT', params: {entryId: '#entryId'}},
remove: {method:'DELETE'}
});
Fix: Was missing a closing curly brace on the update line.
You can implement this way
$resource('http://localhost\\:3000/realmen/:entryId', {entryId: '#entryId'}, {
UPDATE: {method: 'PUT', url: 'http://localhost\\:3000/realmen/:entryId' },
ACTION: {method: 'PUT', url: 'http://localhost\\:3000/realmen/:entryId/action' }
})
RealMen.query() //GET /realmen/
RealMen.save({entryId: 1},{post data}) // POST /realmen/1
RealMen.delete({entryId: 1}) //DELETE /realmen/1
//any optional method
RealMen.UPDATE({entryId:1}, {post data}) // PUT /realmen/1
//query string
RealMen.query({name:'john'}) //GET /realmen?name=john
Documentation:
https://docs.angularjs.org/api/ngResource/service/$resource
Hope it helps
I'm a novice programmer who is very new to both AngularJS and the practice of unit testing. I've spent hours trying to find the solution to this but I'm becoming increasingly more confused. If anyone could point me in the right direction I would greatly appreciate it. I'll try to be as descriptive as possible.
The situation is this:
I have created a service in AngularJS (Service A) that has a couple of functions. Each of these functions makes an $http GET request to a REST API and returns an $http promise object that contains JSON data. Within these functions, the URL is constructed through the implementation of another very simple service (Service B) that has been injected as a dependency into Service A. I have created a mock of Service B to isolate it from all of its dependencies. Both of these services are defined inside of the same module named "services". In this case, there is no real need for this dependency but I just want to understand how it works.
Using Jasmine, I would like to construct a unit test for Service A to ensure that the requests it is making to the API are constructed correctly and possibly if the correct JSON data is being returned. At the same time, I do not want any real API calls to be made.
This is what I know:
$httpBackend mock is what I need to be able to make fake calls to the API and it provides functionality to expect certain requests and return specified results.
I need to test the real Service A and inject the mock I've created of Service B. I know there are ways to do this using Jasmine Spies and $provide. I've also seen examples where sinon.js is used, and I'm not sure which is the best approach.
I will post my source code below, which is written in CoffeeScript.
Service A:
'use strict'
angular.module("services")
.service("ServiceA", ["$http", "ServiceB", ($http, ServiceB) ->
#Uses underscore.js to set this default attribute
defaults = withCredentials:true
getVarset: (itemName, options={}) ->
options.method = "GET"
options.url = ServiceB.makeUrl("item/#{itemName}")
$http _.defaults(options, defaults)
getVarsets: (options = {}) ->
options.method = "GET"
options.url = ServiceB.makeUrl("items")
$http _.defaults(options, defaults)
getModelsForVarset: (itemName, options = {}) ->
options.method = "GET"
options.url = ServiceB.makeUrl("item/#{itemName}/prices")
$http _.defaults(options, defaults)
])
Service B:
'use strict'
angular.module('services')
.service 'ServiceB', [ ->
# Just return the string
# This service builds the real URL, but I've removed this
makeUrl: (Url) ->
"#{Url}"
]
so are you saying that you know how to do this with $provide/Jasmine spies and are looking for alternatives? I've mostly just used the $provide/spy method for mocking and it's worked out really well for me so far.
something like:
beforeEach(function() {
// set up a default value for your mock
bMock = {
makeUrl: jasmine.createSpy('makeUrl() mock').andReturn('http://www....')
}
// use the $provide service to replace ServiceB
// with your mock
module('services', function($provide) {
$provide.value('ServiceB', bMock);
});
});
it('should do what its supposed to do', function() {
// test...
});
then, if you want to use $httpBackend to mock the http requests in service A, you just need to use the $injector service to grab $httpBackend, then call .when(...) on it to set things up, a la http://docs.angularjs.org/api/ngMock.$httpBackend