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"
}]
}
};
Related
In my application, I'm retrieving some fields from the database and setting the values in local storage when a user logs in.
and then retrieving from local storage to display it to user:
if (localStorage.getItem('a') != undefined) {
$rootScope.a = localStorage.getItem('a');
}
So this is working fine. But the problem is when the value gets updated in the database and user logs in after logging out, then even if the local storage has correct value (i.e., recently updated value), the first time it will display the old value of the scope variable which just got updated.
I tried $apply() and also $digest() as suggested in different posts here :
$timeout( function () {
$scope.$apply( function () {
$rootScope.a = localStorage.getItem('a');
});
});
But it didn't work out. It always displays the old value of scope.
It will only display the new value after reloading the page once.
P.S. - The web page in my application won't be reloaded in any module, even when logging in and out.
You can try watching for the scope variable like this:
$rootScope.$watch('a', function (newVal, oldVal){
if newVal != oldVal
$rootScope.a = newVal;
}
Something else to try is to change 'a' from string to object as I think that angular watches for values using object reference.
here's some useful reference for $watch
http://www.learn-angular.org/#!/lessons/watch
https://www.bennadel.com/blog/2852-understanding-how-to-use-scope-watch-with-controller-as-in-angularjs.htm
Hope it helps in any way
EDIT
ok I tested it. You don't need watch neither $apply if you refresh the scope when data refreshing.
Here's what I've done:
(function() {
angular.module('myapp', []).controller('myctrl', [
'$scope', function($scope) {
var data, getDataFromLocalStorage;
console.log("scope is ", $scope);
getDataFromLocalStorage = function() {
return JSON.parse(localStorage.getItem('data'));
};
data = [
{
id: 1,
text: "test1"
}, {
id: 2,
text: "test2"
}, {
id: 3,
text: "test3"
}
];
localStorage.setItem('data', JSON.stringify(data));
$scope.myData = getDataFromLocalStorage();
return $scope.changeData = function() {
var dataNew;
dataNew = [
{
id: 4,
text: 'text4'
}, {
id: 5,
text: 'text5'
}, {
id: 6,
text: 'text6'
}
];
localStorage.setItem('data', JSON.stringify(dataNew));
return $scope.myData = getDataFromLocalStorage();
};
}
]);
}).call(this);
https://codepen.io/NickHG/pen/rzvGGx?editors=1010
I need a bit of a code review, i'm having trouble getting my ng-change function to trigger and update the value in both controllers, i've created a factory service and have injected it into both controllers but on the second AppCtrl console.log() value prints only once during initialization, and would like to have the ng-change value also update on the second controller and not only on the first.
This is what i have so far:
<ion-radio ng-repeat="rate in rates"
ng-value="rate.id"
ng-change="rateTypeChanged(rate)"
ng-checked="rate.selected"
ng-model="currentRate.selectedRate">
{{ rate.title }}
</ion-radio>
controller for sidebar:
.controller('SidebarCtrl', function($scope, typesOfRates) {
$scope.rates = typesOfRates.rateType;
$scope.currentRate = {
selectedRate: 'hourly'
};
$scope.rateTypeChanged = function(rate) {
console.log("Selected goalType, text:", rate.title, "value:", rate.id);
typesOfRates.setRate(rate.id);
}
In controller 2:
.controller('AppCtrl', function($scope, typesOfRates, $state, $rootScope) {
console.log( typesOfRates.getRate() );
//runs only once, but not again when ng-change event is triggered
my service:
.factory('typesOfRates', function typesOfRates($rootScope) {
var typesOfRates = {};
typesOfRates.myRates = [];
typesOfRates.rateType = [
{ title: "Hourly", id: "hourly", selected: true },
{ title: "Daily", id: "daily", selected: false },
{ title: "Monthly", id: "monthly", selected: false }
];
typesOfRates.currentRate = "hourly";
var setRate = function(currentRate) {
if (typesOfRates.myRates.length > 0) typesOfRates.myRates = [];
typesOfRates.myRates.push(currentRate);
}
var getRate = function() {
return typesOfRates.myRates;
}
return {
rateType: typesOfRates.rateType,
getRate: getRate,
setRate: setRate
}
});
The way you are doing to achieve the objective seems bit out of the box. The second controller will be initialized only once. If you want to access the undated value in the second controller you need to following one of the following approaches.
1) Watch for changes in typesOfRates.myRates in the second controller.
$watch is used to track changes for a model variable in the scope. The
$watch requires $scope, as we have 2 different controllers, the scopes will be different (I feel so unless you have bound the two controllers
in the same html). So it won't be the correct to use $watch in this
situation.
2) Use a broad cast receiver concept
Advantage : It's preferred as there is no continuous watching required, and triggered only when the value changes
Step 1) In the first controller, register a broadcast as:
.controller('SidebarCtrl', function($scope, typesOfRates) {
$scope.rates = typesOfRates.rateType;
$scope.currentRate = {
selectedRate: 'hourly'
};
$scope.rateTypeChanged = function(rate) {
console.log("Selected goalType, text:", rate.title, "value:", rate.id);
typesOfRates.setRate(rate.id);
//$broadcast(name, args); here name you have to give in a file
//which is commonly accessible like constants.js, just create a
//file and include in you index.html, pass your rates as args
$rootScope.$broadcast(constants_config.TYPE_RATES_CHANGED, rate.id);
}
});
Step 2) Create constants.js file and include in your index.html as:
<!-----Constants Classes---->
<script src="Constants.js"></script>
In constants.js add the following code:
var constants_config = {
TYPE_RATES_CHANGED : "TYPE_RATES_CHANGED",
}
Step 3) Register your listener in the second controller as
.controller('AppCtrl', function($scope, typesOfRates, $state, $rootScope) {
// #CallBack
// Description : Callback function for user details fetched
$scope.$on(constants_config.TYPE_RATES_CHANGED, function(args) {
//Assign the value to a global variable or a scope variable so
//that you can access it throughout your controller
$scope.Rates = typesOfRates.getRate();
//Now the console will work
console.log( typesOfRates.getRate() );
});
});
Further Reference:
- Broadcasts : $broadcast
- Listeners : $on
- Watch : $watch
I have a issue in sharing data between 2 controllers and 2 views. I have 2 views. I created 2 separate controllers and bind with 2 different views. Now I have 2 share data between 2 controllers so I created a service. Issues is one controller get data from remote source and other controller is consuming that data. But the view that consumes data loads first, so pulled data from remote source is not exactly utilize by first one. Eg.
//My Services
as.service('songGenreService', function () {
var genreList = [];
var addGenres = function (newObj) {
genreList = newObj;
};
var getGenres = function () {
return genreList;
};
return {
addGenres: addGenres,
getGenres: getGenres
};
});
as.controller('SongListController', ['$scope', 'Song', "$stateParams", 'Category', 'Album', 'base64', 'langConversion', 'CONFIG', 'Poet', "songGenreService",
function ($scope, Song, $stateParams, Category, Album, base64, langConversion, CONFIG, Poet, songGenreService) {
$scope.getCategories = function () {
Category.find({
filter: {
fields: ["id", "name"],
where: {
parentId: 2
}
}
}).$promise.then(function (categories) {
$scope.categories = categories;// Here I am giving data to other source.
songGenreService.addGenres(categories);
$scope.genreId = $scope.categories[0].id;
$scope.genreName = $scope.categories[0].name;
});
}();
}
]);
as.controller('SongGenreController', ['$scope', 'Song', "songGenreService",
function ($scope, Song, songGenreService) {
$scope.categories = songGenreService.getGenres();
console.log($scope.categories);
}
]);
Issue is "SongGenreController" loads first because of HTML as it loads first. I wish to populate it when data loads successfully. "songGenreService.getGenres();" doesn't run with remote source.
The way I fixed a similar issue is by using a publish subscribe mechanism.
In your service you can put a publish when genres are added like so:
var addGenres = function (newObj) {
genreList = newObj;
$rootScope.$broadcast('genresUpdated, genreList);
};
then in your two controllers you subscribe to the event :
$scope.$on('genresUpdated', function(event, genreList){
$scope.genres = genreList;
// and other code you want to have triggered when the genreList changes
});
I'm attempting to create a Showdown extension in my Angular app which will show scope variables. I was able to get it setup to show basic scope variables easily enough, but now I'd like to get it to where I can use the results of an ng-repeat and I can't get anything other than [[object HTMLUListElement]] to show.
Here's my controller so far:
app.controller('MyCtrl', ['$scope', '$window', '$compile', function($scope, $window, $compile){
$scope.machines = [
{ abbv: 'DNS', name: 'Did Not Supply' },
{ abbv: 'TDK', name: 'The Dark Knight' },
{ abbv: 'NGG', name: 'No Good Gofers'}
];
$scope.machine = $scope.machines[0];
$scope.machine_list = $compile('<ul><li ng-repeat="m in machines">{{m.abbv}}: {{m.name}}</li></ul>')($scope);
$scope.md = "{{ machine_list }}";
var scopevars = function(converter) {
return [
{ type: 'lang', regex: '{{(.+?)}}', replace: function(match, scope_var){
scope_var = scope_var.trim();
return $scope.$eval(scope_var);
}}
];
};
// Client-side export
$window.Showdown.extensions.scopevars = scopevars;
}]);
Plunkr: code so far
I feel like I've got to be close, but now I don't know if I'm on the completely wrong track for this, or if it's a showdown thing or an angular thing or what.
I realized I was fighting Angular (and losing quiet badly) with the way I was doing that. DOM in a controller is a no-no. And now I'm kind of angry about how easy it is once I started thinking properly and looking at the directive.
Now instead of trying to do the compile and everything within the controller, I included the $compile service in the directive I'm using, so:
htmlText = converter.makeHtml(val);
element.html(htmlText);
became:
htmlText = converter.makeHtml(val);
element.html(htmlText);
$compile(element.contents())(scope);
With that change in place I no longer need the portion of the extension that just did the basic evaluation, since it's being compiled {{ machine.name }} is automatically converted.
But that still left me not being able to specify a variable to insert a template, just variables. But now that the output is going to be compiled through angular I can put the template in a partial and use an extension to convert from a pattern to an ng-include which works.
New Controller Code:
app.controller('MyCtrl', ['$scope', '$window', '$compile', function($scope, $window, $compile){
$scope.machines = [
{ abbv: 'DNS', name: 'Did Not Supply' },
{ abbv: 'TDK', name: 'The Dark Knight' },
{ abbv: 'NGG', name: 'No Good Gofers'},
{ abbv: 'TotAN', name:'Tales of the Arabian Nights'}
];
$scope.machine = $scope.machines[0];
$scope.tpls = {
'machinelist': 'partials/ml.html'
};
$scope.md = "{{machines.length}}\n\n{{include machinelist}}";
var scopevars = function(converter) {
return [
{ type: 'lang', regex: '{{include(.+?)}}', replace: function(match, val){
val = val.trim();
if($scope.tpls[val] !== undefined){
return '<ng-include src="\''+$scope.tpls[val]+'\'"></ng-include>';
} else {
return '<pre class="no-tpl">no tpl named '+val+'</pre>';
}
}}
];
};
// Client-side export
$window.Showdown.extensions.scopevars = scopevars;
}]);
And of course the new plunkr
Hope this can help someone later down the road
App design question. I have a project which has a very large number of highly customized inputs. Each input is implemented as a directive (and Angular has made this an absolute joy to develop).
The inputs save their data upon blur, so there's no form to submit. That's been working great.
Each input has an attribute called "saveable" which drives another directive which is shared by all these input types. the Saveable directive uses a $resource to post data back to the API.
My question is, should this logic be in a directive at all? I initially put it there because I thought I would need the saving logic in multiple controllers, but it turns out they're really happening in the same one. Also, I read somewhere (lost the reference) that the directive is a bad place to put API logic.
Additionally, I need to introduce unit testing for this saving logic soon, and testing controllers seems much more straightforward than testing directives.
Thanks in advance; Angular's documentation may be… iffy… but the folks in the community are mega-rad.
[edit] a non-functional, simplified look at what I'm doing:
<input ng-model="question.value" some-input-type-directive saveable ng-blur="saveModel(question)">
.directive('saveable', ['savingService', function(savingService) {
return {
restrict: 'A',
link: function(scope) {
scope.saveModel = function(question) {
savingService.somethingOrOther.save(
{id: question.id, answer: question.value},
function(response, getResponseHeaders) {
// a bunch of post-processing
}
);
}
}
}
}])
No, I don't think the directive should be calling $http. I would create a service (using the factory in Angular) OR (preferably) a model. When it is in a model, I prefer to use the $resource service to define my model "classes". Then, I abstract the $http/REST code into a nice, active model.
The typical answer for this is that you should use a service for this purpose. Here's some general information about this: http://docs.angularjs.org/guide/dev_guide.services.understanding_services
Here is a plunk with code modeled after your own starting example:
Example code:
var app = angular.module('savingServiceDemo', []);
app.service('savingService', function() {
return {
somethingOrOther: {
save: function(obj, callback) {
console.log('Saved:');
console.dir(obj);
callback(obj, {});
}
}
};
});
app.directive('saveable', ['savingService', function(savingService) {
return {
restrict: 'A',
link: function(scope) {
scope.saveModel = function(question) {
savingService.somethingOrOther.save(
{
id: question.id,
answer: question.value
},
function(response, getResponseHeaders) {
// a bunch of post-processing
}
);
}
}
};
}]);
app.controller('questionController', ['$scope', function($scope) {
$scope.question = {
question: 'What kind of AngularJS object should you create to contain data access or network communication logic?',
id: 1,
value: ''
};
}]);
The relevant HTML markup:
<body ng-controller="questionController">
<h3>Question<h3>
<h4>{{question.question}}</h4>
Your answer: <input ng-model="question.value" saveable ng-blur="saveModel(question)" />
</body>
An alternative using only factory and the existing ngResource service:
However, you could also utilize factory and ngResource in a way that would let you reuse some of the common "saving logic", while still giving you the ability to provide variation for distinct types of objects / data that you wish to save or query. And, this way still results in just a single instantiation of the saver for your specific object type.
Example using MongoLab collections
I've done something like this to make it easier to use MongoLab collections.
Here's a plunk.
The gist of the idea is this snippet:
var dbUrl = "https://api.mongolab.com/api/1/databases/YOURDB/collections";
var apiKey = "YOUR API KEY";
var collections = [
"user",
"question",
"like"
];
for(var i = 0; i < collections.length; i++) {
var collectionName = collections[i];
app.factory(collectionName, ['$resource', function($resource) {
var resourceConstructor = createResource($resource, dbUrl, collectionName, apiKey);
var svc = new resourceConstructor();
// modify behavior if you want to override defaults
return svc;
}]);
}
Notes:
dbUrl and apiKey would be, of course, specific to your own MongoLab info
The array in this case is a group of distinct collections that you want individual ngResource-derived instances of
There is a createResource function defined (which you can see in the plunk and in the code below) that actually handles creating a constructor with an ngResource prototype.
If you wanted, you could modify the svc instance to vary its behavior by collection type
When you blur the input field, this will invoke the dummy consoleLog function and just write some debug info to the console for illustration purposes.
This also prints the number of times the createResource function itself was called, as a way to demonstrate that, even though there are actually two controllers, questionController and questionController2 asking for the same injections, the factories get called only 3 times in total.
Note: updateSafe is a function I like to use with MongoLab that allows you to apply a partial update, basically a PATCH. Otherwise, if you only send a few properties, the entire document will get overwritten with ONLY those properties! No good!
Full code:
HTML:
<body>
<div ng-controller="questionController">
<h3>Question<h3>
<h4>{{question.question}}</h4>
Your answer: <input ng-model="question.value" saveable ng-blur="save(question)" />
</div>
<div ng-controller="questionController2">
<h3>Question<h3>
<h4>{{question.question}}</h4>
Your answer: <input ng-model="question.value" saveable ng-blur="save(question)" />
</div>
</body>
JavaScript:
(function() {
var app = angular.module('savingServiceDemo', ['ngResource']);
var numberOfTimesCreateResourceGetsInvokedShouldStopAt3 = 0;
function createResource(resourceService, resourcePath, resourceName, apiKey) {
numberOfTimesCreateResourceGetsInvokedShouldStopAt3++;
var resource = resourceService(resourcePath + '/' + resourceName + '/:id',
{
apiKey: apiKey
},
{
update:
{
method: 'PUT'
}
}
);
resource.prototype.consoleLog = function (val, cb) {
console.log("The numberOfTimesCreateResourceGetsInvokedShouldStopAt3 counter is at: " + numberOfTimesCreateResourceGetsInvokedShouldStopAt3);
console.log('Logging:');
console.log(val);
console.log('this =');
console.log(this);
if (cb) {
cb();
}
};
resource.prototype.update = function (cb) {
return resource.update({
id: this._id.$oid
},
angular.extend({}, this, {
_id: undefined
}), cb);
};
resource.prototype.updateSafe = function (patch, cb) {
resource.get({id:this._id.$oid}, function(obj) {
for(var prop in patch) {
obj[prop] = patch[prop];
}
obj.update(cb);
});
};
resource.prototype.destroy = function (cb) {
return resource.remove({
id: this._id.$oid
}, cb);
};
return resource;
}
var dbUrl = "https://api.mongolab.com/api/1/databases/YOURDB/collections";
var apiKey = "YOUR API KEY";
var collections = [
"user",
"question",
"like"
];
for(var i = 0; i < collections.length; i++) {
var collectionName = collections[i];
app.factory(collectionName, ['$resource', function($resource) {
var resourceConstructor = createResource($resource, dbUrl, collectionName, apiKey);
var svc = new resourceConstructor();
// modify behavior if you want to override defaults
return svc;
}]);
}
app.controller('questionController', ['$scope', 'user', 'question', 'like',
function($scope, user, question, like) {
$scope.question = {
question: 'What kind of AngularJS object should you create to contain data access or network communication logic?',
id: 1,
value: ''
};
$scope.save = function(obj) {
question.consoleLog(obj, function() {
console.log('And, I got called back');
});
};
}]);
app.controller('questionController2', ['$scope', 'user', 'question', 'like',
function($scope, user, question, like) {
$scope.question = {
question: 'What is the coolest JS framework of them all?',
id: 1,
value: ''
};
$scope.save = function(obj) {
question.consoleLog(obj, function() {
console.log('You better have said AngularJS');
});
};
}]);
})();
In general, things related to the UI belong in a directive, things related to the binding of input and output (either from the user or from the server) belong in a controller, and things related to the business/application logic belong in a service (of some variety). I've found this separation leads to very clean code for my part.