I have defined myself an api for use with Angular.js:
angular.module('api', ['ngResource'])
.factory('Server', function ($resource) {
return $resource('http://localhost\\:3000/api/servers/:name');
})
.factory('ActiveServer', function ($resource) {
return $resource('http://localhost\\:3000/api/servers/active/:name', {},
{ start: {method: 'POST'}, stop: {method: 'DELETE'} });
});
The idea, is that I have a set of defined servers, available through the /api/servers/ uri. A particular server can be started by adding it to the /api/servers/active/ uri, and stopped by deleting it.
In my controller, I have the following code:
$scope.start = function() {
ActiveServer.start(this.server);
};
$scope.stop = function() {
ActiveServer.stop(this.server);
};
which again is triggered by buttons
<div class="well span4" ng-repeat="server in servers">
<h1>{{server.definition.name}}</h1>
<span class="label label-info">{{server.status}}</span>
<button type="button" class="btn btn-primary"
ng-click="start()">Start</button>
<button type="button" class="btn btn-primary"
ng-click="stop()">Stop</button>
</div>
Starting the server works alright, but I have trouble stopping it. The code above ends up with the following request:
Request URL:http://localhost:3000/api/servers/active?$$hashKey=005&_events=[object+Object]&definition=[object+Object]&status=ready
Request Method:DELETE
The definition-part of my server object, containing the name that identifies the server to stop, isnĀ“t serialized right.
How can I fix this?
If your API (ActiveServer) only needs to receive the server's name to work, then pass only the server's name during the service call. Like this:
$scope.start = function() {
ActiveServer.start({name: this.server.definition.name});
};
$scope.stop = function() {
ActiveServer.stop({name: this.server.definition.name});
};
Passing the entire this.server object in any service call will result on the entire object being parametrized into the HTTP request.
extended explanation:
When you use something like api/servers/:name on your $resource URL, you are basically saying that the :name part will be replaced by the value of a property with the same name (in this case, 'name') received on the parameters (i.e. {name: 'someName'}).
From the angularJS $resource documentation:
Each key value in the parameter object is first bound to url template
if present and then any excess keys are appended to the url search
query after the ?. Given a template /path/:verb and parameter
{verb:'greet', salutation:'Hello'} results in URL
/path/greet?salutation=Hello. If the parameter value is prefixed with
# then the value of that parameter is extracted from the data object
(useful for non-GET operations).
So, if you call service.get({name: 'abc'}) then URL of the request will be
api/server/abc
If you call service.get({name: 'abc', id: '123'}) then URL will be api/server/abc?id=123
In your case, the this.server Object looks something like this:
{
$$hashKey: 005,
_events: {},
definition: {},
status: ready
}
Since AngularJS does not do in-depth parametrization, _events and definitions are shown as _events=[object+Object]&definition=[object+Object] on the URL.
Related
I have code in Nodejs as backend and Angular as frontend.
I want to receive and send data by one endpoint and based on that data from server toggle a button. Toggling is working now but I want when I sign out from the dashboard next time that I log in I could see the value of the key is based on the value from the database.
For example, first, it's SET after clicking it changed to CLEAR and I sign out from the dashboard. When next time I log in I want to see the CLEAR label on my button.
These are codes for several parts of the app:
Angular Service
this.setUserFeatured = function(id, setFeatured) {
return $http.put('/admin/v2/users/' + id + '/featured', { setFeatured: setFeatured })
.then(returnedDataOrError);
};
Angular Controller
function updateFeaturedButtonLabel() {
$scope.featuredButtonLabel = $scope.user.setFeatured ? "Clear Featured" : "Set Featured";
}
function toggleFeatured () {
$scope.user.setFeatured = !$scope.user.setFeatured;
UserService.setUserFeatured($stateParams.id, $scope.user.setFeatured)
updateFeaturedButtonLabel();
};
Html File
<a class="btn btn-info" ng-click="toggleFeatured()" ng-class="{on:user.setFeatured}">{{featuredButtonLabel}}</a>
Server Controller
function addFeaturedUser(req: $Request, res: $Response, next: NextFunction) {
const schema = Joi.object().keys(_.pick(validate, ['userId', 'setFeatured']));
const queryParams = { userId: req.params.id };
if (!req.params.id) {
return new errors.BadRequest('userId is not specified');
}
return validate.validate(queryParams, schema)
.then(validatedParams =>
userService5.updateUserLabel(validatedParams.userId, req.body.setFeatured))
.then(result => res.json(result))
.catch(next);
}
router.put('/users/:id/featured', addFeaturedUser);
And updateUserLabel is a function that handling the connection to the database and retrieving the data.
I just wonder how can I use the data from the server to change the label of the button?
true/false for the setting the button is coming from the .then(result => res.json(result))
Thanks in advance for help
For your question, I suppose you are asking how to use the response object returned in
$http.put().then(function(response){})
You can find the structure of response object in following document.
https://docs.angularjs.org/api/ng/service/$http
To access the data returned from server:
$http.put().then(function(response){response.data})
which corresponds to what your server sends.
Besides, the toggleFeatured function should be add to $scope object.
Otherwise, ng-click can't trigger that function in html template.
Hope it helps.
I am working on a site where I have to search in the DB for string that come after the / on the root domain. I can't find anything about it in the documentation.
I am trying to make it work with Iron Router but any other suggestion would work out.
Thanks for the help!
Edit: Basically I just want to pass anything that comes after domain.com/ to a variable.
Here's something i've been doing so maybe it'll lead you down the right path
Route sends URL params to ownedGroupList template
Router.route('/users/:_id/groups', {
name: 'owned.group.list',
template: 'ownedGroupList',
data: function() {
return {params: this.params};
}
});
Template ownedGroupList can access params object using this.data in onCreated, onRendered, and onDestroyed template event handlers
Template.ownedGroupList.onCreated(function(){
this.subscribe("owned-groups", this.data.params._id );
});
Template ownedGroupList can access params through this variable in helper methods
Template.ownedGroupList.helpers({
groups: function() {
return Groups.find({owner: this.params._id });
}
});
Template ownedGroupList can access params through template.data variable in event handlers
Template.ownedGroupList.events({
'click .a-button': function(event, template) {
var group = Groups.findOne({owner: template.data.params._id });
// do something with group
}
});
Here's a simple route that should do the trick
Router.route('/:keyword', {
name: 'keyword',
template: 'keywordTemplate',
data: function() {
return this.params.keyword;
}
});
This will pass the keyword as the data context to your template and then you can do whatever you want with it. Alternatively you can perform the search straight in the router (especially if you're passing the keyword to a subscription so that the search runs on the server). For example:
Router.route('/:keyword', {
name: 'keyword',
template: 'keywordTemplate',
waitOn: function(){
return Meteor.subscribe('keywordSearch',keyword);
},
data: function() {
return MyCollection.find();
}
});
This second pattern will send your keyword to a subscription named keywordSearch that will execute on the server. When that subscription is ready, the route's data function will run and the data context passed to your keywordTemplate will be whatever documents and fields have been made available in MyCollection.
I have started learning Angular and I'm stuck on something that should be trivial. I'm from a Rails background and created a very simple Rails web-service that manages a MailingList. Currently 2x API endpoints exist in my web-service:
/api/v1/mailing_lists [GET]
/api/v1/mailing_lists [POST]
The POST endpoint requires a valid email as a PARAM in the API call. And this is where I'm stuck. How do I pass this param to my API in Angular? I've tried using solutions from around the web but I'm just not getting it right. Here's what I have thus far:
In my services.js:
angular.module('webFrontendApp.services', []).factory('MailingList', function($resource) {
var data = $resource(
'http://localhost:3000/api/v1/mailing_lists.json',
{},
{
// 'get': { method:'GET', cache: false},
'query': { method:'GET', cache: false, isArray:true },
'save': {method: 'POST', cache: false }
});
return data;
});
In app.js
....
.when('/list/new', {
templateUrl: 'views/list-new.html',
controller: 'MailingListCtrl'
})
....
Controller as mailinglist.js
angular.module('webFrontendApp.controllers', [])
.controller('MailingListCtrl', function($scope, MailingList) {
// Get all posts
$scope.mailing_lists = MailingList.query();
// Our form data for creating a new post with ng-model
$scope.memberData = {};
$scope.newMember = function() {
var member = new MailingList($scope.memberData);
member.$save();
};
});
The form looks like this:
<form role="form" ng-submit="newMember()">
<div class="row">
<div class="input-group">
<input type="text" ng-model="memberData.email" placeholder="New mailing list member" class="form-control">
<span class="input-group-btn">
<input type="submit" class="btn btn-primary" value="Add">
</span>
</div>
</div>
</form>
When I submit the form, nothing seems to happen, but I am receiving a 405 error response from my web service. After looking into my Web Service logs, I can see that a POST request was received but no params were passed and thus it was rejected.
Any help will be greatly appreciated!
PS. I have made sure to Enable CORS on my Rails app.
Pass params inside $save method, pass post_data inside MailingList, so basically this should work
$scope.newMember = function() {
var member = new MailingList();
member.$save({email: $scope.memberData.email});
};
I have a property in the scope that has an id of external object, also I have a filter that expands this id into a full object like this:
{{ typeId | expandType }}
Filter:
.filter('expandType', ['TypeService', function (tsvc) {
return function (id) {
return tsvc.types.get({ id: id });
}
}])
where tsvc.types.get() is normal resource get method with added cache option.
.factory('TypeService', ['$resource', function ($resource) {
var typeResource = $resource('/api/types/:id', { id: '#id' }, {
get: { method: 'GET', cache: true, params: { id: '#id' } }
});
return {
types: typeResource
}
}])
As I understand angular runs additional digest after the fist one just to make sure that nothing changed. But apparently on the next digest the filter is returning a different object and I get the infdig error (digest is executed in infinite loop).
I hoped that if the resource is cached it will return the same object from cache all the time. I can confirm that there is only one trip to server while executing get() so the cache is working.
What can I do to make it work and use the filter to expand ids to full objects?
Although possible, it is usually not a good idea to bind promises to the view. In your case, filters are reevaluated on every digest, and quoting from https://docs.angularjs.org/api/ng/service/$http:
When the cache is enabled, $http stores the response from the server in the specified cache. The next time the same request is made, the response is served from the cache without sending a request to the server.
Note that even if the response is served from cache, delivery of the data is asynchronous in the same way that real requests are.
To clarify, ngResource uses $http internally.
You can still use the filter calling it from your controller:
app.filter('expandType', function ($http) {
return function (id) {
return $http.get('data.json');
};
});
app.controller('MainCtrl', function ($scope, expandTypeFilter) {
var typeId = 'hello';
expandTypeFilter(typeId).success(function (data) {
$scope.expandedTypeId = data[typeId];
});
});
Plunker: http://plnkr.co/edit/BPS9IY?p=preview.
With this approach, if the only reason you were caching the response was to avoid repeated calls to the server, you can now stop caching it so that it gets fresh data later on, but that depends on your needs, of course.
I really wanted to use a filter because it was used all over the app and I didn't want to clutter my controllers. At this point the solution I came out with looks as follows:
.filter('expandType', ['TypeService', function (tsvc) {
var cache = {};
return function (id) {
if (!id) {
return '';
}
var type = cache[id];
if (!type) {
tsvc.types.get({ id: id }).$promise.then(function (data) {
cache[id] = data;
});
cache[id] = {}
return cache[id];
}
else {
return type;
}
}
}])
I am building a SPA app with the default Durandal setup. I have multiple views returning data with ajax calls however, it is not working perfectly. I created my shell page with a search box so I can search through a list of employees shown here.
Shell.js
define(['require', 'durandal/plugins/router', 'durandal/app', 'config'],
function (require, router, app, config) {
var shell = {
router: router,
searchData: searchData,
employees: ko.observable(),
search: search,
activate: activate,
};
var searchData = ko.observable('');
function search(searchData) {
var url = '#/employeeSearch/' + searchData.searchData;
router.navigateTo(url);
}
return shell;
function activate() {
router.map(config.routes);
return router.activate(config.startModule);
}
});
shell.html
<div class="input-group">
<input type="text" class="form-control" placeholder="Search Employees" data-bind="value: searchData" />
<span class="input-group-btn">
<button type="submit" class="btn btn-default" data-bind="click: search">Search</button>
</span>
</div>
The user puts in a search and when they click the search button the view below navigates to the employeeSearch page. This does work and return the data and view I need it to here.
define(['require', 'durandal/plugins/router', 'durandal/app', 'config', 'services/logger'],
function(require, router, app, config, logger) {
var goBack = function() {
router.navigateBack();
};
function details(employee) {
var url = '#/employee/' + employee.Id + '/profile';
router.navigateTo(url);
}
var vm = {
goBack: goBack,
employees: ko.observable(),
details: details,
};
return {
activate: function (route) {
var self = this;
return self.getEmployees(route.q);
},
getEmployees: function (query) {
return $.ajax(app.url('/employees?q=' + query),
{
type: "GET",
contentType: 'application/json',
dataType: 'json',
}).then(querySucceeded).promise;
function querySucceeded(result) {
self.employees = result;
logger.log(query + ' Search Activated!', null, 'employeeSearch', true);
}
},
};
});
So then, if I try to search for another name, the url will navigate, the logger will show the value I searched for, however the view itself will not show the new results. Exploring in the Chrome debugger, I view the employees object to contain the new result set, but the view has still not updated. If I refresh the page however, the view does properly show up I have viewed multiple questions on here with similar issues about keeping your data calls in the activate method, make sure the data returns a promise before completing the activate method, and putting DOM manipulation in the viewAttached.
Javascript is not rendering in my SPA
How to use observables in Durandal?
HotTowel: Viewmodel lose mapping when navigating between pages
After putting those practices in my code, I am still having problems getting the view to update correctly.
Are there any other Durandal/Knockout properties I need to be aware of? How can I get the bindings to update every time I navigate to a view with (router.navigateTo(url);). I have set my shell properties (cacheViews: true) to true and false but nothing seems to change.
This is one of many problems I have been having with building SPA apps with Durandal. Trying not to give up yet.
I cant test this quick but a think you handle the observable wrong.
I suspect the "result" var is an array of employees. In this case you might handle this with an observableArray (http://knockoutjs.com/examples/collections.html)
And you cant set the value directly like self.employees
You must call the observable function to set the value like
function querySucceeded(result) {
self.employees(result)
logger.log(query + ' Search Activated!', null, 'employeeSearch', true);
}
In your samples you have not shown/mentioned where knockout is being loaded. If you are using Durandal 2.0 then add the following line to the top of your main.js file above your existing define statement
define('knockout', ko);