Is there any way that I can create a template in AngularJS but not run it until some event?
Actually I am using SignalR. So when server calls a client function then I want some HTML to show based on the data sent by server.
Suppose I am making a Chess Game.
I don´t have the board to display on the page until the server sends it.
In HandleBar I can create a template and when the server sends the board in json format I can compile the template and display it on page.
gameHub.client.buildBoard = function(game){
var template = Handlebars.compile($("board-template").html());
$("board").html(template(game.Board))
}
This builBoard function gets called by server.
In AngularJS I have to create a Controller, but the $scope.board has not loaded let.
The angular Code is
<div ng-app="game">
<div ng-controller="boardController">
<div ng-repeat="row in board.Pieces">
// Display Pieces
</div>
</div>
JS
var app = angular.module("game", []);
app.controller("boardController", function ($scope) {
$scope.size = 5;
$scope.board= [];
});
Here the board is initialized to empty array.
Now the server will call the buildBoard() function with the necessary data required.
gameHub.client.buildBoard = function(board){
// Code to take this board object and assign it to the $scope.board
}
From here (outside the scope of controller ), how can I update the $scope.board array?
While I believe you should explain yourself a bit more, and show some angular code, I think you are looking for a solution to a problem that angular doesn't have.
Consider this code:
<div class="board-thing" ng-bind-html="board" ng-hide="board === null"></div>
...
.controller('myController', function($scope) {
$scope.board = null;
gameHub.client.buildBoard = function(game) {
$scope.board = game.board;
});
})
When the data is loaded $scope.board is assigned to the data and the view is immediately updated through the scope and ng-bind-html. No need for any compilation!
You can define directives to avoid cluttering your controller with presentation related code.
For all business related code, define services outside of your controller.
In this case, as you want push data, you need to define an event dispatcher ("Server") that will inform the controller when remote changes take place.
Here is an example controller and related template.
angular
.module('app')
.controller("ChessController", function($scope, Server) {
$scope.list = [0, 1, 2, 3, 4, 5, 6, 7];
$scope.pieces = [];
// "Server" is an angular service that abstracts your connection with your server
// which can be based on socket.io, a raw websocket, HTTP polling...
var chessGame = Server.getChessGame('xxx');
chessGame.on('init', function(pieces) {
// pieces == [
// {x: 0, y: 0, type: "pawn", color: "white"}
// {x: 0, y: 3, type: "king", color: "white"}
// {x: 2, y: 0, type: "queen", color: "white"}
// ];
$scope.pieces = pieces;
});
chessGame.on('remote_piece_moved', function(move) {
// remove pieces at destination
$scope.pieces = $scope.pieces.filter(function(p) { return p.x != move.dest_x && return p.y != move.dest_y; })
// move piece.
var piece = $scope.pieces.find(function(p) { return p.x == move.start_x && p.y == move.start_y; });
piece.x = move.dest_x;
piece.y = move.dest_y;
});
});
.directive('chessPiece', function() {
return {
scope: false,
link: function(scope, element) {
scope.$watch('pieces', function(pieces) {
var piece = pieces.find(function(p) { return p.x == scope.col && p.y == scope.row; });
if (piece)
element.html('<img src="pieces/' + piece.type + '-' + piece.color + '.jpg"/>')
else
element.html('');
}, true);
}
}
});
Template
<table ng-controller="ChessController">
<tr ng-repeat="row in list">
<td ng-repeat="col in list" chess-piece></td>
</tr>
</table>
Related
Hi i'm looking for an Angular.js example like this,
http://www.jqueryscript.net/form/Knob-Style-Rotary-Switch-Plugin-with-jQuery-rotarySwitch.html
Ideally a complete path from rotary switch to posting some commands to the service side, based in the switch position.
Any good example please ?
Otherwise i plan to write a angular module using this sample jquery file, every time the rotary switch moves to a new location, it's call $http.post to let service side know. The main module will use this rotary switch module. But really don't want to write my own.
Like mentioned in the comments I also think that there is probably no directive for that rotary switch plugin.
So something like in the demo below should work for this. I've just created a directive where I've initialized the jquery plugin inside the link method (dom is ready to manipulate inside postLink).
To reduce http traffic I've added a timeout so that the callback is called at a lower rate. (see rotKnob_delayedCallback in DEFAULT_OPTS)
You could write a directive for every jQuery plugin where you can't find a wrapper directive.
Please also have a look at the browser console. In jsfiddle you can see the post echo of the position in the console.
Here is also the fiddle to the demo below.
angular.module('demoApp', ['rotaryKnobModule'])
.factory('knobService', KnobService)
.controller('mainController', MainController)
angular.module('rotaryKnobModule', [])
.constant('DEFAULT_OPTS', {
// Minimal value
minimum: 0,
// Maximum value
maximum: 12,
// Step size
step: 1,
// Snap to steps in motion
snapInMotion: true,
// Start point in deg
beginDeg: 0,
// Length in deg
lengthDeg: 360,
// // Which value will used, if the the start and the end point at the same deg.
minimumOverMaximum: true,
// Show input element
showInput: false,
// Show deg marks
showMarks: false,
// Theme class
themeClass: 'defaultTheme',
// custom option for directive
rotKnb_delayedCallback: 500 // in ms
})
.directive('rotaryKnob', RotaryKnob);
function MainController(knobService, $timeout) {
var vm = this;
vm.position = 0;
vm.options = {
minimum: 0,
maximum: 100
};
vm.onChange = function(pos) {
console.log('current position change handler', pos);
knobService.postPos(pos).then(function(response){
console.log('success', response)
}, function(response) {
console.log('failed');
});
}
}
function KnobService($http) {
return {
postPos: function(pos) {
var data = $.param({
json: JSON.stringify({
position: pos
})
});
return $http.post('/echo/json/', data);
}
}
}
function RotaryKnob($timeout) {
return {
scope: {},
bindToController: {
pos: '=',
options: '=?',
onChange: '&?'
},
controllerAs: 'rotKnobCtrl',
template: '<input type="text" class="rotarySwitch"/>',
link: function(scope, element, attrs) {
var options = scope.rotKnobCtrl.options,
ctrl = scope.rotKnobCtrl,
pos = 0, changeTimeout;
//console.log(options);
var $rotKnob = $(element).rotaryswitch(options);
$rotKnob.on('change', function() {
pos = parseInt($(this).val());
scope.rotKnobCtrl.pos = pos;
if ( !changeTimeout ) // reduce update rate to server
changeTimeout = $timeout(function() {
ctrl.onChange({pos: pos});
changeTimeout = null;
}, options.rotKnb_delayedCallback);
scope.$apply();
})
},
controller: function(DEFAULT_OPTS) {
var self = this;
self.pos = 0;
//console.log(self.options);
self.options = angular.extend({}, DEFAULT_OPTS, self.options);
}
}
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<link href="https://cdn.rawgit.com/r12r/com.redwhitesilver.rotarySwitch/master/jquery.rotaryswitch.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.4/angular.js"></script>
<script src="https://cdn.rawgit.com/r12r/com.redwhitesilver.rotarySwitch/master/jquery.rotaryswitch.js"></script>
<div ng-app="demoApp" ng-controller="mainController as mainCtrl">
<rotary-knob
pos="mainCtrl.position"
options="mainCtrl.options"
on-change="mainCtrl.onChange(pos)"
></rotary-knob>
Current position {{mainCtrl.position}}
</div>
I'm developing a mini-basket in angular for an ecommerce application but have a problem with a scoped variable not updating via a service.
When i click on an add to basket button in the product grid it fires the upDateMiniBasket function in the product grid controller which has the UpdateMiniBasket service injected into it.
The controller:
whiskyControllers.controller('whiskyListCtrlr', ['$scope', 'UpdateMiniBasket', '$http',
function($scope, UpdateMiniBasket, $http){
$http.get('json/whiskies.json').success(function(data){
$scope.whiskies = data;
})
$scope.updateMiniBasket = function(e){
var targetObj = e.target.getAttribute('data-item');
UpdateMiniBasket.getUpdate(targetObj);
}
}
])
Here is the service:
whiskyrumApp.factory('UpdateMiniBasket', [function(){
var miniBasketTotal = 0,
itemCount = 0;
var miniBasketItems = [{
imageUrl : '',
name : 'There are currently no items in your basket',
price: 0
}]
var getUpdate = function(obj){
miniBasketTotal = 0;
if(obj) {
if(miniBasketItems[0].price === 0) miniBasketItems.pop();
obj = jQuery.parseJSON(obj);
miniBasketItems.push(obj);
}
for(var i = 0, j = miniBasketItems.length; i < j; i++){
miniBasketTotal += parseFloat(miniBasketItems[i].price);
}
itemCount = miniBasketItems[0].price === 0 ? 0 : miniBasketItems.length;
return {
miniBasketItems : miniBasketItems,
miniBasketTotal : miniBasketTotal,
itemCount : itemCount
}
}
return {
getUpdate : getUpdate
}
}]);
The problem is that when I add a product, the function fires and calls the service ok, but a scoped variable that should update the amount of items in the basket is not updating. This variable lives in another controller for the minibasket that also has teh UpdateMiniBasket service injected.
whiskyControllers.controller('miniBasketCtrlr', ['$scope', 'UpdateMiniBasket',
function($scope, UpdateMiniBasket){
var mbItems = UpdateMiniBasket.getUpdate();
$scope.miniBasketItems = mbItems.miniBasketItems;
$scope.itemCount = mbItems.itemCount;
}])
And this is html:
<div class="mini-basket grey-box" ng-controller="miniBasketCtrlr">
<a href="#" data-toggle="dropdown" class="btn dropdown-toggle">
{{itemCount}} Items
</a>
<!-- the ng-repeat code for the mini basket which works fine -->
I just assumed that when the variables in the service are updated, that would feed through to the scoped var in the other controller as they are both linked to the same service. I thought maybe I need to add a $watch as I cant see why this {{itemCount}} is not updating.
Any help would be greatly appreciated.
If you change the return object of your UpdateMiniBasket factory to instead of returning the primitive value for the count of items but to a function like below:
return {
miniBasketItems : miniBasketItems,
miniBasketTotal : miniBasketTotal,
itemCount : function () {
return itemCount;
}
}
And then in your controller change the binding to $scope to this:
$scope.itemCount = function () {
return mbItems.itemCount();
};
You should then just have to change the binding in the html to {{ itemCount() }} and this value should then successfully update when the values in the factory update.
I have a simplified solution to this on jsbin here: http://jsbin.com/guxexowedi/2/edit?html,js,output
In my AngularJS I have two controllers. One is responsible for handling data about people. The other controller is used to handle information about how the person data is displayed:
.controller('PersonCtrl', ['$rootScope', '$scope', function ($rootscope, $scope)
{
$scope.Persons = ['John', 'Mike'];
} ]);
.controller('WidgetCtrl', ['$scope',
function($scope) {
$scope.myWidgets = {
'1': {
id: '1',
name: 'Home',
widgets: [{
col: 0,
row: 0,
sizeY: 1,
sizeX: 1,
name: "John"
}, {
col: 2,
row: 1,
sizeY: 1,
sizeX: 1,
name: "Mike"
}]
}
};
Notice in the PersonCtrl, I have two names: John and Mike. In the WidgetCtrl, I want to attach those names to the name field (in the example below, I just typed it in so that it's obvious where the data goes). How do I do this?
just to elaborate the comment... you can use services to share data between controllers. check out this video from egghead.io
egghead.io is an excellent resource for starting with angualrjs.
this is how that service should look like
app.service('productService', function() {
var personList = [];
var add = function(newObj) {
personList.push(newObj);
}
var get = function(){
return personList;
}
return {
add: add,
get: get
};
});
EDIT
to address your comment on how to get names in widget
controller('WidgetCtrl', ['$scope', 'PersonService'
function($scope, personService ) {
/* init the myWidgets*/
var count = 0;
angular.forEach(personService.personList, function(item){
$scope.myWidgets['1'].widgets[count].name = item.name;
count = count +1;
});
}]);
A controller is for mediation between a model (in its $scope) and the template. Sometimes nested controllers is the right way to do things, i.e. your WidgetCtrl assumes that it is inside a PersonCtrl. In this case it can access the parent controller scope members through the scope prototypical inheritance.
A more sophisticated way of doing it though is to use a service that deals with fetching data from a back end and presenting it to various controllers that may need it.
There are a few ways to share data between controllers - events, services and controller inheritance through the use of parent controllers.
There is no right or wrong way, but typically if you are fetching data from a server then a service is the way forward. If you wanna utilize the observer pattern aka pub/sub then use events. We use multiple solutions in our spa.
This however looks like the job for a service - especially since you say one is responsible for showing data - aka the truth - aka a controller (widget). The other is responsible for handling data about a person - use a service which returns the array you need.
Service example:
.factory('persons', function() {
var persons = ['john', 'mike'];
function getPersons() {
return persons;
}
return {
getPersons: getPersons
}
});
Some ex HTML for the update:
<button ng-click="updateWidget(getPeople())">Update Widget</button>
Your Controller:
.controller('WidgetCtrl', ['$scope', 'persons'
function($scope, persons) {
//Both not used only for example purposes...
var peeps = [];
peeps = $scope.getPeople();
//Now in yer HTM you can pass this function into the updateWidget fct
// ex <button ng-click="updateWidget(getPeople())">Update Widget</button>
$scope.getPeople = function() {
// Now this is not async but be away in the future if you req from server you need to use promises
return persons.getPersons();
}
$scope.updateWidget = function(people) {
var item = $scope.myWidgets[1].widgets;
angular.forEach(items, function(item, $index) {
item.name = people[$index];
});
}
$scope.myWidgets = {
'1': {
id: '1',
name: 'Home',
widgets: [{
col: 0,
row: 0,
sizeY: 1,
sizeX: 1,
name: "X"
}, {
col: 2,
row: 1,
sizeY: 1,
sizeX: 1,
name: "Y"
}]
}
};
I am trying to make ng-table work by example 6 (ajax data loading) but instead of using mock backend I use actual DreamFactory backend connected to MongoDB. My relevant code looks like this now:
MainApp.factory('Servant', function ($resource) {
"use strict";
console.log('loading');
return $resource('https://dsp-mydspname.cloud.dreamfactory.com:443/rest/mongodb/tablename/:id/?app_name=appname&fields=*', {}, { update: { method: 'PUT' }, query: {
method: 'GET',
isArray: false
} });
});
var MainCtrl = function ($scope, $timeout, $resource, Servant, ngTableParams) {
"use strict";
$scope.action="Add";
var Api = Servant;
$scope.tableParams = new ngTableParams({
page: 1, // show first page
count: 10, // count per page
}, {
total: 0, // length of data
getData: function($defer, params) {
// ajax request to api
Api.get(params.url(), function(data) {
$timeout(function() {
// update table params
params.total(data.record.length);
// set new data
$defer.resolve(data.record);
}, 500);
});
}
});
}
The table is displying data but it displays all data on one page, I cant figure out how to pass "count" and "offset" parameters into my api call. Any help would be appreciated.
Sorry for the delayed response. I played a little with ng-table and found that I was spending a lot of time trying to make it work and couldn't get it to. So..I thought it would be more helpful to show you how to build your own table with pagination so you can adapt it for any situation that may arise using DreamFactory. Here's the code. You should be able to copy and paste. Just make sure to add your table fields to the table row for data. The table headers will populate automatically.
Here is the controller and the service with comments:
.controller('TableCtrl', ['$scope', 'Servant', function($scope, Servant) {
// function to get records for building the table
var _getRecords = function(fieldsStr, limitInt, offsetInt, schemaBool) {
Servant.get({fields: fieldsStr, limit: limitInt, offset: offsetInt, include_schema: schemaBool},
function(data) {
$scope.table = data;
}
)
};
// Get the total records on load
Servant.get({fields: 'id'}, function(data) {
// Get the total number of records
$scope.totalRecords = data.record.length;
});
// Options for rest call
$scope.fields = '*';
$scope.currentOffset = 0;
$scope.limit = 4;
// Used to do pagination
// store total records
$scope.totalRecords = 0;
// store page objects
$scope.pageObjs = [];
// Get initial data
_getRecords($scope.fields, $scope.limit, $scope.currentOffset, true);
// Pagination
$scope.next = function() {
//check if we are on the last page
if ($scope.currentOffset == $scope.pageObjs[$scope.pageObjs.length - 1].pageOffset) {
return false;
}
// we are not
// advance the page
else {
$scope.currentOffset = $scope.currentOffset + $scope.limit;
_getRecords($scope.fields, $scope.limit, $scope.currentOffset, true);
}
};
// change page directly
$scope.changePage = function (offsetInt) {
$scope.currentOffset = offsetInt;
_getRecords($scope.fields, $scope.limit, $scope.currentOffset, true);
};
$scope.back = function() {
// are we on the first page
if ($scope.currentOffset == 0) {
return false
}
// we are not
// go previous page
else {
$scope.currentOffset = $scope.currentOffset - $scope.limit;
_getRecords($scope.fields, $scope.limit, $scope.currentOffset, true);
}
};
// watch for total records to be populated. When we have this number
// we can generate our page objects that will help build our pagination
$scope.$watch('totalRecords', function(newValue, oldValue) {
var numPages = Math.ceil(newValue / $scope.limit);
for(var i = 0; i < numPages; i++) {
$scope.pageObjs.push({pageNumber: i, pageOffset: i*$scope.limit})
}
});
}])
.service('Servant', ['$resource', function($resource) {
// define and return our $resource
// replace /rest/db/TheTable with your mongodb/tablename
// you don't need the port either
return $resource('http://localhost:8081/rest/db/TheTable',
{
// set params to bind too
app_name: APP_NAME
fields: '#fields',
limit: '#limit',
offset: '#offset'
},
{
// set update method to 'PUT'
update: {
method: 'PUT'
}
}
)
}]);
Here is the template i used:
<table class="table">
<!-- this will build the table headers dynamically -->
<!-- they will populate in order of the table's schema -->
<tr>
<th data-ng-repeat="field in table.meta.schema.field">
{{field.name}}
</th>
</tr>
<!-- replace these fields with your field names -->
<!-- for example: {{row.YOUR_FIELD_NAME}} -->
<tr data-ng-repeat="row in table.record">
<td>
{{row.id}}
</td>
<td>
{{row.first_name}}
</td>
<td>
{{row.last_name}}
</td>
</tr>
</table>
<!-- this will build dynamically as well-->
<ul class="pagination">
<li data-ng-click="back()"><a>«</a></li>
<li data-ng-click="changePage(page.pageOffset)" data-ng-repeat="page in pageObjs"><a>{{page.pageNumber + 1}}</a>
</li>
<li data-ng-click="next()"><a>»</a></li>
</ul>
I am displaying a list of elements inside a ng-include.
The list of elements comes from the server using $resource query service.
The list is paginated with the ui-bootstrap pagination directive. the server send the pagination informations inside the Json header (properties are named X-MyApp-…) and are intercepted by the query callback function.
here is the html :
<table ng-include src="'partials/tplList.html'" ng-init="listInit = {'type': collec.type, 'offset': 1}" ng-controller="ListCtrl" >
</table>
the tplList.html :
<tbody ng-init="loadList(listInit)"><tr ng-repeat="elm in list">
<td>{{elm.prop1}}</td><td>{{elm.prop2}}</td><td>{{elm.prop3}}</td>
</tr></tbody>
<tfoot><tr><td colspan="4">
<span ng-show="pageCount>1">
<pagination num-pages="pageCount" current-page="currentPage" max-size="10" on-select-page="loadList(collect(listInit, {offset: page}))">
</pagination>
</span>
</td></tr></tfoot>
and the controller:
controller('ListCtrl', ['$scope', 'List', function($scope, List) {
// collect: concatenate the objects before to send it to loadList()
$scope.collect = function (a,b){
var c = {};
for (var att in a) { c[att] = a[att]; }
for (var att in b) { c[att] = b[att]; }
return c;
}
$scope.loadList = function (param) {
$scope.list = List.query(p, function(list, response) {
$scope.currentPage = response("X-MyApp-currentPage");
$scope.pageCount = response("X-MyApp-pagesCount");
console.log($scope.currentPage); // returns 1 when the page loads.
});
}
}])
and the service :
factory('List', function($resource){
return $resource('url/to/the/json/:type', {type:'#type'});
})
everything is working fine except one thing : when the page loads, the first page button ("1") inside the pagination component is not disabled like it should (and the "previous" and "first" buttons are not either). It's not disabled until i click on another page number (which is disabled correctly when selected) and then click back on the first page button.
any idea ?
This happens because ng-include creates a new scope and the model is not modified in your $parent scope.
Try the following code or create a controller that communicates with the parent one.
<pagination num-pages="pageCount" current-page="$parent.currentPage" max-size="10" on-select-page="loadList(collect(listInit, {offset: page}))">
</pagination>
i found a way to make it work:
removed this line from the controller :
$scope.currentPage = response("X-MyApp-currentPage");
and added this one :
$scope.currentPage = 1;
which gives :
controller('ListCtrl', ['$scope', 'List', function($scope, List) {
// collect: concatenate the objects before to send it to loadList()
$scope.collect = function (a,b){
var c = {};
for (var att in a) { c[att] = a[att]; }
for (var att in b) { c[att] = b[att]; }
return c;
}
$scope.currentPage = 1;
$scope.loadList = function (param) {
$scope.list = List.query(p, function(list, response) {
$scope.pageCount = response("X-MyApp-pagesCount");
});
}
}])
apparently the pagination component doesn't need the X-MyApp-currentPage information from the server (and i'm not sure to understand why).