I have been trying out some AngularJS tutorials and dabbling around trying to create a simple website.
I have a directive that creates a side navigation bar and populates the list of items from some json data stored on my server. The name list is populated without an issue.
The problem is that I need to share that name between several controllers, but I am not having any luck doing so.
My app.js contains:
var app = angular.module("Myapp", ['ui.bootstrap']);
app.service('nameService', function() {
var name = "";
this.setName = function(name) {
this.name = name;
}
this.getName = function() {
return name;
}
});
app.directive("sideBar", ['$http', function($http) {
return {
restrict: 'E',
templateUrl: "views/sidebar.html",
controller: function($scope) {
$scope.updateName = function(name) {
alert(name);
};
$http.get('../data/names.json').
success(function(data, status, headers, config) {
$scope.names = data;
}).error(function(data, status, headers, config) { });
}
};
}]);
From what I've read you should just be able to inject the service as a dependency as follows:
app.directive("sideBar", ['$http', 'nameService', function($http, nameService) {
Then I should be able to update the value like so:
$scope.updateName = function(name) {
nameService.setName(name);
};
However, when I try to inject the dependency it breaks the entire directive and the sidebar will no longer load.
I have been pulling my hair out trying to figure out why it breaks without much luck. Is there something I am missing? Or am I going about this the complete wrong way?
Related
I'm working on an Angular app, where I'm running into mostly the same problem as in this post:
AngularJS App: Load data from JSON once and use it in several controllers
I've got a factory that reads a JSON file, and returns the whole data object. Each controller, then, uses this factory (as a service?) to obtain the data, but then each controller has to pick it apart on its own. The JSON has to be searched and processed to get the relevant payload like, $scope.currentArray = data.someThing.allItems[i]; etc, and I obviously don't want to repeat this code in all the controllers.
Seems to me I can either find some way to share the data, after, say, MainController (the "first one") has finished working it, or I can add some new module "between" the controllers and the factory. This new module -- let's call it myProcessService? -- would then be the one getting the data object from the factory, and do all the processing there... once and for all. Then, each controller would only deal with myProcessService to (somehow) get the ready-formatted variables and arrays etc onto their respective scopes (yes, this is Angular 1).
If I try to give an example of how I'm doing this so far, maybe someone can help me with the necessary improvements? And, I am aware that it is a good idea to begin using the Angular 2 patterns already today, but please understand that I am first trying to get some grasp of how A1 works, before delving into A2 :)
var app = angular.module('myApp', ['ngRoute']);
app.factory('getDataFile', ['$http', function($http) {
function getStream(pid) {
return $http.get("data/" + pid + ".json")
.success(function(data) {
console.info("Found data for pid " + pid);
return data;
})
.error(function(err) {
console.error("Cant find data for pid " + pid);
return err;
});
}
return {getStream: getStream};
}]);
app.controller('MainController', ['$scope', 'getDataFile',
function($scope, getDataFile) {
getDataFile.getStream('10101011').success(function(data) {
// process "data" into what's relevant:
var i = getRelevantIndexForToday(new Date());
$scope.myVar = data.someField;
$scope.currentArray = data.someThing.allItems[i];
// etc... you get the drift
}
}]);
app.controller('SecondController', ['$scope', 'getDataFile',
function($scope, getDataFile) {
getDataFile.getStream('10101011').success(function(data) {
// process "data" into what's relevant:
var i = getRelevantIndexForToday(new Date());
$scope.myVar = data.someField;
$scope.currentArray = data.someThing.allItems[i];
// etc... you get the drift
}
}]);
Edit:
My ngRouter is set up something like this. They fill the ng-view div in my index.html. However -- and maybe this is frowned upon? -- I've also got a "MainController" which sits directly in the index.html body tag, such that I can show some data (from the back end) in the header part of the single page application.
app.config(function($routeProvider) {
$routeProvider
.when('/:id/work/:page_id', {
controller: 'AssetController',
templateUrl: 'app/views/work.html'
})
.when('/:id/work/', {
redirectTo: '/:id/work/1'
})
.when('/:id/', {
controller: 'DashController',
templateUrl: 'app/views/dashboard.html'
})
.otherwise({
redirectTo: '/'
});
});
and index.html is a lot like this:
<body ng-app="myApp">
<div class="container" ng-controller="MainController">
<h1>Welcome, {{username}}</h1>
<div ng-view></div>
</div>
</body>
You can add another helper function in your factory, that returns the required object that you want to share between controllers.
app.factory('getDataFile', ['$http', function($http) {
function getStream(pid) {
return $http.get("data/" + pid + ".json")
.success(function(data) {
console.info("Found data for pid " + pid);
return data;
})
.error(function(err) {
console.error("Cant find data for pid " + pid);
return err;
});
}
function getCurrent(pid) {
return getStream(pid).then(function() {
var i = getRelevantIndexForToday(new Date());
return {
myVar: data.someField,
currentArray: data.someThing.allItems[i];
};
});
}
return {
getStream: getStream,
getCurrent: getCurrent
};
}]);
app.controller('MainController', ['$scope', 'getDataFile',
function($scope, getDataFile) {
getDataFile.getCurrent('10101011').success(function(data) {
$scope.myVar = data.myVar;
$scope.currentArray = data.currentArray;
// etc... you get the drift
}
}]);
app.controller('SecondController', ['$scope', 'current',
function($scope, current) {
.success(function(data) {
$scope.myVar = data.myVar;
$scope.currentArray = data.currentArray;
}
}]);
Suggestion:
Also I suggest you to use resolve which allows you to pass data to your controller from your route.
Route:
.when('/:id/work', {
controller: 'AssetController',
templateUrl: 'app/views/work.html',
resolve: {
// you are injecting current variable in the controller with the data. You can inject this to each of your controller. you dont need to add the whole function in your next route. Just use current
current: function(getDataFile){
return getDataFile.getCurrent('10101011');
}
})
Controller:
app.controller('MainController', ['$scope', 'current',
function($scope, current) {
$scope.myVar = current.myVar;
$scope.currentArray = current.currentArray;
}]);
app.controller('SecondController', ['$scope', 'current',
function($scope, current) {
$scope.myVar = current.myVar;
$scope.currentArray = current.currentArray;
}]);
Now that you have
Thanks for giving an answer in line with my use of deprecated methods success and error, Subash. However, I had some problems with the code in your answer, and I got some help on #angularjs, so I thought I should post the updated code here.
var myApp = angular.module('myApp',[]);
myApp.factory('getDataFile', ['$http', function($http) {
function getStream(pid) {
// here, using placeholder data URL, just to get some data:
return $http.get('http://jsonplaceholder.typicode.com/users')
.then(function(result) {
console.info("Found data for pid " + pid);
return result.data;
})
.catch(function(err) {
console.error("Cant find data for pid " + pid);
return err;
});
}
function getCurrent(pid) {
return getStream(pid).then(function(data) {
var i = 1; // test
console.log("myVar = ", data[i].name);
return {
myVar: data[i].name
};
});
}
return {
getStream: getStream,
getCurrent: getCurrent
};
}]);
myApp.controller('MainController', ['$scope', 'getDataFile',
function($scope, getDataFile) {
$scope.name = "j";
getDataFile.getCurrent('10101011').then(function(user) {
$scope.myVar = user.myVar;
console.log("controller. myVar = ", user);
// etc... you get the drift
});
}]);
I have a basic app currently that has a list of names in a sidebar nav populated from a json call to my server. When the user clicks on a name in the sidebar, it updates the nameService to the name clicked on by the user.
When the nameService gets updated, I wanted to name-data view to make another json call the the server for the correct json file based on the name that the user clicked on.
I am having difficulty updating a view based on changes in a value contained in my service. I have two controllers and a service currently in my AngularJS app as follows:
app.js
var app = angular.module("myapp", ['ui.bootstrap']);
app.directive("sideBar", ['$http', 'nameService', function($http, nameService) {
return {
restrict: 'E',
templateUrl: "views/sidebar.html",
controller: function($scope) {
$scope.updateName = function(name) {
nameService.setName(name);
};
$http.get('../data/names.json').
success(function(data, status, headers, config) {
$scope.names = data;
});
}
};
}]);
app.directive("nameData", ['$http', 'nameService', function($http, nameService) {
return {
restrict: 'E',
templateUrl: "views/name-data.html",
controller: function($scope) {
$scope.service = nameService;
var path = "../data/" + $scope.service.name + ".json";
$http.get(path).success(function(response) {
$scope.info= response.info;
});
}
};
}]);
app.service('nameService', ['$http', function($http) {
this.name = "TestName";
this.setName = function(name) {
this.name = name;
};
this.getName = function() {
return this.name;
};
}]);
How can I update the nameData view whenever the user clicks on the sidebar nav and updates the nameService.name property?
I tried putting $scope.service.name under a watch but that didn't seem to do anything.
Is there some form of angular magic I can use to dynamically make new json calls when a new user is selected from the name list contained in my side bar?
maybe angular event broadcasts?
add rootScope to service and broadcast an event on name change:
app.service('nameService', ['$http','$rootScope', function($http,$rootScope) {
this.name = "TestName";
this.setName = function(name) {
this.name = name;
$rootScope.$broadcast('nameService-nameChanged');
};
this.getName = function() {
return this.name;
};
}]);
and then bind to that event on your directive controller scope:
app.directive("nameData", ['$http', 'nameService', function($http, nameService) {
return {
restrict: 'E',
templateUrl: "views/name-data.html",
controller: function($scope) {
$scope.service = nameService;
//turned your load mechanism in to a function
$scope.loadNameData = function(){
var path = "../data/" + $scope.service.name + ".json";
$http.get(path).success(function(response) {
$scope.info= response.info;
});
}
//initial load
$scope.loadNameData();
//subscribe to broadcast event, this will call $scope.loadNameData when the 'nameService-nameChanged' event is broadcast
$scope.$on('nameService-nameChanged',$scope.loadNameData);
}
};
}]);
I found many similar questions but I couldn't apply the solutions to my problem.
So in my angular app I am drawing nvd3 charts.
I am doing a get request in the service and I can see from the network in my browser that I am ALWAYS getting the chart data as I am supposed to.
The problem is that, when I am running grunt serve to start my angular app, I am still getting the data through the api, but for some reason they are not shown.
That just happens only when I run grunt serve. However, if I hit refresh, after running grunt serve, the data are shown correctly.
Thanks in advance for any help.
this is what I am trying to do:
'use strict';
angular.module('myApp')
.service('mainService', function($http) {
this.getData = function() {
return $http({
method: 'GET',
url: '/rest/api/config',
});
}
})
.controller('MainCtrl', function($scope, $http, mainService) {
var name = 'main';
var model;
mainService.getData().then(function(d) {
model = d.data;
$scope.modelling();
});
$scope.modelling = function() {
if(!model) {
console.log("no model");
// set default model for demo purposes
model = {
title: "about",
structure: "12/4-4-4",
};
}
console.log(model);
$scope.name = name;
$scope.model = model;
$scope.collapsible = true;
}
});
Try something like this. Initially, in your example, $scope.model is going to be undefined.
.controller('MainCtrl', function($scope, $http, mainService) {
var name = 'main';
mainService.getData().then(function(d) {
$scope.modelling(d.data);
});
$scope.modelling = function(data) {
//do something with data here, or set
$scope.model = data;
}
$scope.name = name;
$scope.collapsible = true;
}
});
That might work, depends on how you set up the nvd3 charts.
My basic premise is I want to call back to the server to get the logged in user in case someone comes to the site and is still logged in. On the page I want to call this method. Since I am passing the user service to all my controllers I don't know which controller will be in use since I won't know what page they're landing on.
I have the following User Service
app.factory('userService', function ($window) {
var root = {};
root.get_current_user = function(http){
var config = {
params: {}
};
http.post("/api/user/show", null, config)
.success(function(data, status, headers, config) {
if(data.success == true) {
user = data.user;
show_authenticated();
}
});
};
return root;
});
Here is an empty controller I'm trying to inject the service into
app.controller('myResourcesController', function($scope, $http, userService) {
});
So on the top of my index file I want to have something along the lines of
controller.get_current_user();
This will be called from all the pages though so I'm not sure the syntax here. All examples I found related to calling a specific controller, and usually from within another controller. Perhaps this needs to go into my angularjs somewhere and not simply within a script tag on my index page.
You could run factory initialization in run method of your angular application.
https://docs.angularjs.org/guide/module#module-loading-dependencies
E.g.
app.run(['userService', function(userService) {
userService.get_current_user();
}]);
And userService factory should store authenticated user object internaly.
...
if (data.success == true) {
root.user = data.user;
}
...
Then you will be able to use your factory in any controller
app.controller('myController', ['userService', function(userService) {
//alert(userService.user);
}]);
You need to inject $http through the factory constructor function, for firsts
app.factory('userService', function ($window, $http) {
var root = {};
root.get_current_user = function(){
var config = {
params: {}
};
$http.post("/api/user/show", null, config)
.success(function(data, status, headers, config) {
if(data.success == true) {
user = data.user;
show_authenticated();
}
});
};
return root;
});
in your controller you can say
$scope.get_current_user = UserService.get_current_user();
ng attributes in your html if needed. besides this, i am not sure what you need.
I'm trying to create a simple blog website using AngularJS. I'm just starting out, so what I'm thinking my not be the best way to do this, so any alternative suggestions are welcome.
I have a controller.js file with two blog controllers. One to display a list of blog posts, and the other that displays the post content by including an HTML file.
controller.js
myAppControllers.controller('BlogListCtrl', ['$scope', '$http', function ($scope, $http) {
$http.get('articles/articles.json').success(function (articles) {
$scope.articles = articles;
});
}]);
myAppControllers.controller('BlogPostCtrl', ['$scope', '$routeParams', function ($scope, $routeParams) {
$scope.includeFile = 'articles/' + $routeParams.blogPostId + '.html';
}]);
articles.json
[
{
"id": "test-article-one",
"title": "Test Article one",
"author": "Gareth Lewis",
"datePosted": "2015-06-23",
"summary": "This is a test summary"
},
{
"id": "test-article-two",
"title": "Test article two",
"author": "Gareth Lewis",
"datePosted": "2015-06-23",
"summary": "This is a test for article two"
}
]
app.js
when('/blog', {
templateUrl: 'partials/blog-articles.html',
controller: 'BlogListCtrl'
}).
when('/blog/:blogPostId', {
templateUrl: 'partials/blog-post.html',
controller: 'BlogPostCtrl'
}).
blog-post.html
<ng-include src="'partials/header.html'"></ng-include>
<!-- Want to add title, author, datePosted information here... -->
<article class="content">
<ng-include src="includeFile"></ng-include>
</article>
This blog listings work fine. When I click into a blog post, it also serves up the content from the HTML file OK as well. However, I want to be able to reuse the title, author and datePosted properties from the selected article in the blog-post.html partial view. What's the best way to do this? Would I need to pass them to the Controller somehow to then pass to the view? I don't really want to pass these as routeParams. Or would I need to do a $http.get on articles.json and iterate through to find the selected article and then pass the property values back to the view?
Thanks for the help.
You said that suggestions are welcome, so here it goes.
1 - Transport all your Blog logic to a service;
2 - Provide the data on resolving routes. This is a better approach to handle errors during the load time, 404s, and so on. You can provide a listener to $routeChangeError and deal with it there;
3 - On the service declared below, you have the methods to call your data and a method to retrieve the list cached on the service:
// services.js
myAppServices
.service('BlogService', ['$http', '$q', function ($http, $q) {
var api = {},
currentData = {
list: [],
article: {}
};
api.getSaved = function () {
return currentData;
};
api.listArticles = function () {
var deferred = $q.defer(),
backup = angular.copy(currentData.list);
$http.get('articles/articles.json')
.then(function (response) {
currentData.list = response;
deferred.resolve(response);
}, function () {
currentData.list = backup;
deferred.reject(reason);
});
return deferred.promise;
};
api.getArticle = function (id) {
var deferred = $q.defer(),
backup = angular.copy(currentData.article),
path = 'articles/' + id + '.html';
$http.get(path, {
cache: true
})
.then(function (response) {
currentData.article = {
path: path,
response: response
};
deferred.resolve(currentData.article);
}, function (reason) {
currentData.article = backup;
deferred.reject(currentData.article);
});
return deferred.promise;
};
return api;
}]);
The BlogService.getSaved() will retrieve the stored data, made after each call.
I've made a method to call the ng-include path too, so you can verify if it exists, with cache === true, the browser will keep a copy of it, when calling it again on the view. A copy of the response of the blog article is made too, so you can access its path and the response whenever you need.
On the controllers below, they were adaptated to supply the current needs:
// controller.js
myAppControllers
.controller('BlogListCtrl', ['$scope', 'articles',
function ($scope, articles) {
$scope.articles = articles;
/* OTHER STUFF HERE */
}
])
.controller('BlogPostCtrl', ['$routeParams', '$scope', 'article' 'BlogService',
function ($routeParams, $scope, article, BlogService) {
// On `article` dependency, you have both the original response
// and the path formed. If you want to use any of it.
$scope.includeFile = article.path;
// To get the current stored data (if any):
$scope.articles = BlogService.getSaved().list;
// Traverse the array to get your current article:
$scope.article = $scope.articles.filter(function (item) {
return item.id === $routeParams.id;
});
/* OTHER STUFF HERE */
}
]);
And the route declarations were changed to load the data when resolving the routes.
// app.js
$routeProvider
.when('/blog', {
templateUrl: 'partials/blog-articles.html',
controller: 'BlogListCtrl',
resolve: {
articles: ['BlogService', '$routeParams', function (BlogService, $routeParams) {
return BlogService.listArticles();
}]
}
})
.when('/blog/:id', {
templateUrl: 'partials/blog-post.html',
controller: 'BlogPostCtrl',
resolve: {
article: ['BlogService', '$routeParams', function (BlogService, $routeParams) {
return BlogService.getArticle($routeParams.blogPostId);
}]
}
})
This is maybe a common question in angular. What you have to understand is that Scope is defined per controller... In order to share data across controller you still have the option to use $scope.$parent or $rootScope to link controllers but I would use those carefully.
It is better to use Angular Services which are based on singleton patterns therefore you can use them to share information between controllers and I think it will be a better approach.
I found that this has been previously discussed and here are some good examples:
AngularJS Service Passing Data Between Controllers
You can use a global scope to set this data, or you can use service to communicate between the controllers. There is a lot of ways to resolve this problem read a little bit more about services in the link bellow and see if you can find how to resolve your problem.
AngularJS: Service vs provider vs factory