I'm new to angular-meteor and trying to make an application similar to Reddit for excercise. But I'm not sure what's the right approach to subscribe and publish. Here's my code.
angular.module('app')
.config(($stateProvider) ->
$stateProvider
.state('boards-list',
url: '/board'
templateUrl: 'client/boards/boards-list.view.ng.html'
controller: 'BoardsListCtrl'
)
.state('board-detail',
url: '/board/:symbol'
templateUrl: 'client/boards/board-detail.view.ng.html'
controller: 'BoardDetailCtrl'
)
)
I want to show popular post on /board and if you go to specific board I want to list all the post in the board.
This is board-list controller
angular.module('app')
.controller('BoardsListCtrl', ($scope, $meteor, $modal, $log) ->
$meteor.subscribe('getPopularPosts')
$meteor.subscribe('getAllBoards')
$scope.boards = $scope.$meteorCollection(->
Boards.find({})
)
$scope.posts = $scope.$meteorCollection(->
Posts.find({}, _.defaults(DEFAULT_QUERY_OPTIONS, limit: 5))
)
$scope.getHref = (link) -> if link then "http://#{link}" else "#"
$scope.remove = (board) ->
$scope.boards.remove(board)
$scope.open = ->
modalInstance = $modal.open(
templateUrl: 'client/common/modal-new-board.view.ng.html'
controller: 'ModalNewBoardCtrl'
)
)
And this is board-detail controller
angular.module('app')
.controller('BoardDetailCtrl', ($scope, $stateParams, $meteor, $modal) ->
boardSymbol = $stateParams.symbol
$scope.posts = []
$scope.$meteorSubscribe('getBoardBySymbol', boardSymbol)
$scope.board = $meteor.object(Boards, symbol: boardSymbol, false)
$scope.$meteorSubscribe('getPostsByBoard', boardSymbol)
$scope.posts = $scope.$meteorCollection(->
Posts.find(board: boardSymbol, DEFAULT_QUERY_OPTIONS)
)
$scope.open = ->
modalInstance = $modal.open(
templateUrl: 'client/common/modal-post.view.ng.html'
controller: 'ModalPostCtrl'
resolve:
symbol: -> $scope.board.symbol
)
)
My question is if I write Posts.find({}, DEFAULT_QUERY_OPTIONS) for boardDetailCtrl's $scope.posts, It actually shows all the posts as if the routes is /board when the state is changed. When you directly access the route like http://localhost:3000/angular it gets proper posts. Not sure why this is happening but I changed it to Posts.find({board: boardSymbol}, DEFAULT_QUERY_OPTIONS) Then it works as expected.
Considering that I only subscribe getPostByBoard which is about getting post only at given board, I should be able to get only a few of them. I checked console doing like Posts.find({}).count() and surprisingly it's having every post.
So my question is should I add query to find even after I subscribe the proper publish function? Or am I wrong at some point?
Whenever you start a subscription in Meteor you are adding those subscribed collections to a local collection. So if you have a boards collection and in your home page you subscribe to see ALL collections like:
$meteor.subscribe('getAllBoards')
You are adding ALL of the boards to the local collection. This local collection will be persistent until you explicitly stop the subscription. So in your board details page you still have access to that full collection.
If you notice you are using 2 different methods for subscribing. $scope.$meteorSubscribe and $meteor.subscribe. The first one will automatically remove the subscription when the $scope is destroyed. The second one will keep it.
So, if you wanted to remove the collection and re-subscribe when entering the board details page, you would want to use $scope.$meteorSubscribe in your main page as well.
Here's some food for thought. If you are on your main page and you use $meteor.subscribe, then you already have that information for those posts locally, correct? So, when you click on a link to view the board details, you don't actually need to subscribe again. You can just call:
$scope.board = $meteor.object(Boards, symbol: boardSymbol, false);
And you'll have access to that boards information. ( I just realized you were talking about Posts and not Boards, but the idea is still the same).
Short Answer: $meteor.subscribe() does not cancel the subscription on it's own, you have to tell it to. $scope.$meteorSubscribe does cancel the subscription.
Best practice? I would set the options for the subscription and then just create a local collection using find() like:
$scope.$meteorSubscribe('getPopularPosts', _.defaults(DEFAULT_QUERY_OPTIONS, limit: 5).then(-> {
$scope.posts = $scope.$meteorCollection(->
Posts.find();
)
})
Then in your publish add in the options:
Meteor.publish('getPopularPosts', function(options) {
return Posts.find({}, options);
});
This would limit the number of posts sent to the client rather than sending all of them and the only using 5.
When you switch to the board details view it will no longer have this information available to trip you up, because we used $scope.$meteorSubscribe and $scope.$meteorCollection.
Related
i am new to angular js .i know about controllers,which uploads data to view.
a sample controller with my knowledge,,
angular.module('marksTotal',[]).controller('totalMarks',function($scope){
$scope.totMarks=$scope.sub1+$scope.sub2+$scope.sub3+$scope4.sub;
$scope.totMarks+=$scope.sub5;
$scope.avgMarks=$scope.totMarks/5;
}
i have a doubt how to share data b/w different controllers in the same page & b/w different pages.
could you please clarify my doubt
thanks.
ravi
The best way is to write a service for this on the long term.
But to get you startet you could put things in the $rootScope:
$rootScope.data.var = someValue
and access this values from any scope.
You can open different pages and controllers via modal windows. I use this one. Basically you open a template and a controller in a modal window and determine the data that gets passed or returned in it. My advice is reading the Github project for more information. It is just about passing $scope variables onwards and backwards in the callbacks. Apologies if I'm not more clear. I haven't used Angular in a few months.
Example from one of mine:
$scope.domain = {};
$scope.edit = function (domain, size) {
ModalService.showModal({
templateUrl: '/assets/layouts/domain-edit-template.html',
controller: 'domainsEditTemplate',
size: size,
inputs: {
item: domain
}
})
.then(function (modal) {
modal.close.then(function (selectedObject) {
if(selectedObject.save == "update") {
console.log(selectedObject);
domain.template_id = selectedObject.templateId.template_id;
domain.template_name = selectedObject.templateId.name;
} else if(selectedObject.save == 'insert') {
selectedObject.template_name = selectedObject.templateId.name;
selectedObject.template_id = selectedObject.templateId.template_id;
$scope.domains.push(selectedObject);
}
});
});
};
Otherwise I would suggest you learn how to use AngularJS routes to send parameters in the URL.
I develop an application with Angularjs. In order to access to the application, the user has to be identified thanks to an external system (not developed by myself).
When the user is correctly identified, this external system redirected to my application.
I have implemented a general remote service for retrieving data (thanks to Coldfusion and an Oracle database) about a user (thanks to the login). This service is equally used in other applications. It returns general user information: firstname, lastname,..., and the user role for the specific application.
I would like to authorize the access to the sections of my application when the user has the corresponding role.
Thus I have called once the function of this service before to implement the routes in order to avoid many calls to the functions in the controllers. It works and I retrieve the user role.
var app=angular.module('ContactsApp', ['ngRoute', 'RemoteService']);
// CALL the method from THE REMOTE SERVICE - used $http.get and retrieve JSON data
app.factory('RemoteServiceFunction', ['RemoteServiceFactory', function (RemoteServiceFactory) {
return RemoteServiceFactory.Auth_getUserFromLogin(userid)
}]);
app.config(function($routeProvider, $httpProvider){
// FUNCTION USING THE REMOTE SERVICE IN ORDER TO INJECT DATA IN THE ROUTES
var wait = ['RemoteServiceFunction', function(RemoteServiceFunction) {
return RemoteServiceFunction;
}];
$routeProvider.when('/all-contacts',
{
templateUrl: 'template/allContacts.html',
controller: 'ctrlContacts',
resolve: {
personInfo: wait
}
})
.when('/view-contacts/:contactId',
{
templateUrl: 'template/viewContact.html',
controller: 'ctrlViewContacts',
resolve: {
personInfo: wait
}
})
.when('/search-contacts',
{
templateUrl: 'template/fastSearch.html',
controller: 'ctrlContactSearch',
resolve: {
personInfo: wait
}
})
.when('/add-contacts',
{
templateUrl: 'template/manageContact.html',
controller: 'ctrlAddContacts',
resolve: {
personInfo: wait
}
})
.otherwise({redirectTo:'/all-contacts'});
});
//THE CONTROLERS WITH DEPENDANCIES INJECTED
app.controller('ctrlContacts', function ($scope, ContactService, personInfo){
// alert(personInfoRole.data["VALUES"][0]["ROLES"]); OK the role is retrieved
}
Now I would like to enable or not the access to the various sections ( for example the role "admin" is mandatory for example in order to add a contact: ctrlAddContacts) . I do not know if I have to add a condition or a function in the resolve part to give the access.
Could you please tell me how to do that? Or tell me if another solution is better for using roles with Angularjs.
Thanks
Your best bet is probably to "intercept" the route change, that is: listening to the $routeChangeStart event on your rootScope. There you can check against whatever model variable you store the user priviledge in, and stop the routeChange from happening or redirect to an authentication screen.
Keep in mind though that such client-side authentication in an angular (or any javascript) application is just a user interface thing, since the user's browser will have full access to all the resources, and can even rewrite the validating parts of the javascript code, bypassing any route restrictions you set up. Sensitive information shouldn't travel to the client in the first place without proper authentication.
edit: see a full code solution here:best way to limit access to 'logged in' users
I am building an app for a client who has, as one of their data sets a master list of all their members. I have the data coming in from Firebase and everything runs peachy, but it's not that DRY I am now realizing. I would like to use some of the data from the membership set in other views within the site. I copied the code listed below into other controllers that I need to have access to it and although everything works, my IDE (RubyMine) shows the 'syncObject' as redundant.
So, my question is, if there's a way to code it better and dryer to be used in other views? Thank you for your time.
.controller( 'MembershipCtrl', function MembershipCtrl( $scope, $firebase ) {
var ref = new Firebase("https://myid.firebaseio.com/members");
var sync = $firebase(ref);
var syncObject = sync.$asArray();
$scope.members = syncObject;
});
I have a json endpoint that returns a list of objects. The first view is a list with just some basic info but if you select an item I want to show more details. Should I just construct and object in my factory and have another method to get the details? so
getItems() // returns list data
getItem(id)// goes through the object already retrieved and returns just the data?
I don't want to hit the server again for each item but I'm not sure of the best way to do this in Angular. It doesn't appear the Model is made for this in Angular.
Also, assuming Item has additional information that I may need to make another server request. What's the best way to store this locally so if I already fetches these details once I don't need to get it again?
If you want to exchange data between 2 controllers, you can use a provider
App.provider('Data', function() {
//Properties declaration
this.info = '';
//Setters
this.setInfo = function(info) {
this.info = info;
};
//Getters
this.getInfo = function() {
return this.info;
};
controller 1
App.controller(
'controller1',
['$scope', 'Data',
function($scope, Data) { Data.setInfo('here you put what you want') }]);
controller which get back information
App.controller(
'controller2',
['$scope', 'Data',
function($scope, Data) { Data.getInfo() }]);
More likely, you'll want to use either a factory or a service:
https://docs.angularjs.org/api/auto/service/$provide#factory
https://docs.angularjs.org/api/auto/service/$provide#service
In both cases, you provide a function which Angular will call once and store the return reference, then provide that reference to any controller or other Angular-managed object that requests it. The difference between a factory and service is that a service function is expected to be a constructor function (it's called using the "new" keyword) whereas a factory is a just invoked as a regular function.
What is the best practice of doing CRUD operations via REST with AngularJS?
Specially what is the Angular-Way here. By this I mean the way using the least code and the most default angular settings to achive this.
I know $resource and it's default operations. Where I'm not sure is how to implement/name the endpoints and which controllers to use.
For this example I would like to implement a simple user-management system which creates / updates /deletes / lists users. Since I'm implementing the Server-Endpoints by myself I'm completely free in doing it in the most angular friendly way.
What I like as answer is something like:
Server-Endpoints:
GET /service/users -> array of users
GET /service/user/new -> return an empty user with default values which has no id
POST /service/user/new -> store a new user and create an id. return the saved user.
POST /service/user/:ID -> save an existing user. Return the saved user
DELETE /service/user/:ID -> delete an existing user
Angular-Services:
.factory( 'User', [ '$resource', function( $resource ){
return $resource( '/service/user/:userId', { userId: '#id' } )
[...]
}])
Routing:
.when( '/users', {
templateUrl: BASE + 'partials/user-list.html',
controller: 'UserListCtrl' } )
.when( '/user/new', {
templateUrl: BASE + 'partials/user-edit.html',
controller: 'UserNewCtrl' } )
.when( '/user/:userId', {
templateUrl: BASE + 'partials/user-edit.html',
controller: 'UserEditCtrl' } )
...
Controllers:
UserListCtrl:
$scope.data = User.get(...)
UserNewCtrl:
$scope.user = User.get( { userId: "new" } )
...
Note that I'm not interessted in opinion what is the best (tm) way to do this but I'd like to know what is the Angular intended way (which I think should produce the least code because it can use the most default).
EDIT:
I'm looking for the whole picture. What I would love would be an answer like e.g.: "You can do this using online 3 Endpoints [...], 2 routes [...] and 2 controllers [...] if you do it this way using that defaults ..."
There is no Angular prescribed way for what you are asking. It's up to you to determine the implementation detail.
Typically I only use two controllers and templates per resource:
ListController
FormController
The Form controller is used for both Edit and Create operations. Use the resolve option in your route definitions to pass in either User.get() or User.new() and a flag indicating if this is an edit or create operation. This flag can then be used inside your FormController to decide which save method to call. Here's a simple example:
.when( '/users', {
templateUrl: BASE + 'partials/user-list.html',
controller: 'UserListCtrl' } )
.when( '/user/new', {
templateUrl: BASE + 'partials/user-form.html',
resolve: {
data: ['User', function(User) { return User.new(); }],
operation: 'create'
}
controller: 'UserFormCtrl' } )
.when( '/user/:userId', {
templateUrl: BASE + 'partials/user-form.html',
resolve: {
data: ['User', '$route', function(User, $route) { return User.get($route.current.params.userId); }],
operation: 'edit'
}
controller: 'UserFormCtrl' } )
And your form controller:
app.controller('UserFormCtrl', ['$scope', 'data', 'operation', function($scope, data, operation){
$scope.data = data;
$scope.save = function() {
if (operation === 'edit') {
// Do you edit save stuff
} else {
// Do you create save stuff
}
}
}]);
You can go a step further and create a base list and form controller to move stuff like error handling, server-side validation notifications etc. In fact for the majority of CRUD operations you can even move the save logic to this base controller.
My research into a similar quest has lead me to this project "angular-schema-form" https://github.com/Textalk/angular-schema-form.
For this approach...
You make a JSON-Schema that describes your data. Then augment it with another little JSON-struct that describes a "form" (ie. view specific info that does not belong in the data schema) and it makes a UI (form) for you.
One cool advantage is that the Schema is also useful in validating the data (client and server side), so that is a bonus.
You have to figure out what events should fire off GET/POST/... back to your API. but that would be your preference, eg. Hit the API for every key stroke OR the classic [Submit] button POST back style OR something in between with a timed Auto Save.
To keep this idea going, I think that it is possible to use StrongLoop to make a quick API, which (again) uses your data's schema (augmented with some storage details) to define the API.
no <3 uses of that schema, see [http://json-schema.org/] which is central to this approach.
(read: no less than three :)
You maybe mixing things up. CRUD operations at API level are done using $resource and these may or may not map to UI.
So using $resouce if you define resource as
var r = $resource('/users/:id',null, {'update': { method:'PUT' }});
r.query() //does GET on /users and gets all users
r.get({id:1}) // does GET on /users/1 and gets a specific user
r.save(userObject) // does a POST to /users to save the user
r.update({ id:1 }, userObject) // Not defined by default but does PUT to /users/1 with user object.
As you see the API is resource full but is in no way linked to any UI view.
For view you can use the convention you have defined, but nothing specific is provided by Angular.
I think what you are looking for can be found in http://www.synthjs.com/
Easily create new RESTful API resources by just creating folders and
naming functions a certain way.
Return data or promises from these
functions and they'll be rendered to the client as JSON.
Throw an
error, and it'll be logged. If running in dev mode, the error will
also be returned to the client.
Preload angular model data on page load (saving an extra
roundtrip).
Preload html view on page load (saving another extra
roundtrip!)
A simplified project structure
where front-end code (angular code, html, css, bower packages, etc)
is in the 'front' folder and back-end code (node code and node
packages) are in the 'back' folder.
A command-line tool for
installing third party packages, using npm + bower, that auto-updates
manifest files.
Auto compilation of assets on request for dev, and
pre-compilation for prod (including minification and ngmin).
Auto-restarts the server when changes are detected.
Support for
various back-end and front-end templates to help get a new project
going quickly.