Basically I am using angular routing for my pages and its respective template. Every pages has form in which it has more HTML fields(input/select/textarea). I am trying to create reusable Directive to create html field like below
app.directive('field',function(){
return {
restrict : "E",
scope : {
},
link : function(scope,elem,attr){
var content;
scope.Options = {
id: scope.$id+'_'+elem.attr('id'),
label : elem.attr('label'),
placeholder : elem.attr("placeholder"),
};
scope.contentUrl = 'templates/fields/'+elem.attr('template')+'.html';
},
template: '<div ng-include="contentUrl"></div>'
}
})
Now from my respective page HTML, I will use this directive. For example from customer page HTML has,
<field id="NAME" template="text" label="First Name" placeholder="Enter First Name"></field>
So far so good. Field is generated as expected. Now I wanted to prepopulate the customer JSON data into directive respective fields.
I tried to create factory service to get JSON data and inject this service to my customer controller like below
Factory service
app.factory('dataService', function($http) {
return {
getCustomerData: function() {
//return the promise directly.
return $http.get('offline/customer.json')
.then(function(result) {
//resolve the promise as the data
return result.data;
});
}
}
});
customerController
app.controller('customerController', ['$scope', 'dataService',function($scope,dataService) {
dataService.getCustomerData();//need to parse this data into field directive
}]);
Am I doing right way? How do we parse respective page data into their page fields created by directive?
First of all, I think, you need to bind fetched data with controller's scope:
app.controller('customerController', ['$scope', 'dataService',function($scope,dataService) {
dataService.getCustomerData().then(function ( data ) {
$scope.data = data; // let data == { someField: 42 }
};
}]);
And after that, you need to use data from scope into angular's template:
<field id="NAME" template="text" label="First Name" placeholder="Enter First Name">{{someField}}</field>
To prepopulate your fields, you need to use Angular binding i.e ngModel. Using ng-include in your directive is redundant, you can use directly the template attribute in your directive.
I would do it that way :
app.directive('customtext',function() {
return {
restrict:'E',
require:'ngModel',
scope:{
thevalue:'='
},
template:'<input type="text" ng-model="thevalue"/>',
}
});
and use :
<customtext thevalue="name" />
And now you can populate the controller's scope and the bind will be done this way :
app.controller('customerController', ['$scope','dataService',function($scope,dataService) {
var data = dataService.getCustomerData();
$scope.name = data.name;
}]);
You will need to create a directive for each field you want to create.
ps: the JSON that get through $http is automatically converted as an object. You don't need to use JSON.parse.
Related
I have a side menu and its controller. In another controller I get the user data and want to update my menu items. How to achieve that ? I tried using $watch and services but to no success. Also I never used $rootScope so please avoid if it is not the only solution.
.controller('menuCtrl', function($scope) {
$scope.username = ""
})
.controller('afterloginCtrl', function($scope) {
var a = "this is username"
$scope.username = a // here I wish to update username in menuCtrl
})
Please let me if you need more code or details.
EDIT
Actually I update user info via database table, from their I am retrieving the username and other info. So menu controller needs to update with current values in database every time
You can to use some service for sharing data between controllers.
For example:
.service('currentUser', () => ({data: {}, someMethod: () => {}}))
.controller('Ctrl1', (currentUser, $scope) => {$scope.user = currentUser.data;})
.controller('Ctrl2', (currentUser) => {currentUser.data.name = 'username';});
Obviously, you can also extend your service with some appropriate methods. etc.
Then you can use these methods:
.controller('Ctrl2', (currentUser, api) => {
api.retrieveUser()
.then(user => currentUser.setData(user));
});
.service('loginuser', () => ({}))
.controller('fistCtrl', (currentUser, $scope) => {
$scope.data1 = loginuser;
})
.controller('secondectrl', (loginuser) => {
loginuser.name = 'sessionname';
})
If you are using service or factory data will be lost after page refresh.
You Can use browser session storage.
sessionStorage.setItem('NAME', "XYZ"); //set Data
sessionStorage.getItem('NAME'); // Get Data
//Afetr use you can clear data.
sessionStorage.clear();
You can use localStorage
Use the following code in your first controller:
localStorage.setItem($scope.username);
and the next controller:
$scope.username = localStorage.getItem();
As per the above comments your requirement is to persistent the username if user closes app and reopens.So, by using service or factory data will be lost once app reload or close.
Work around :
You can use localStorage to store username and can also access the data across controllers.
Code :
You can create a common factory service that will save and return the saved local storage data based on the key.
app.factory('storageService', ['$rootScope', function($rootScope) {
return {
get: function(key) {
return localStorage.getItem(key);
},
set: function(key, data) {
localStorage.setItem(key, data);
}
};
}]);
In controller :
Inject the storageService dependency in the controller to set and get the data from the local storage.
.controller('menuCtrl', ['$scope','storageService',function($scope,storageService) {
// Get local storage data from storageService
storageService.get('username');
}])
.controller('afterloginCtrl',['$scope','storageService',function($scope,storageService) {
var a = "this is username"
// Set local storage data to storageService
storageService.set('username', a);
}])
I'm currently trying to pull in some JSON from an API with Angular's http method. I can retrieve the data fine, however I can't target specific ID's - to be clear what I'd like to do is have say /#/work/1 and /#/work/2, and be able to use the data relevant to that ID. I have looked into using $routeParams to do this but I have only gotten so far.
My JSON looks exactly like this:
[
{
"id": 1,
"title": "New post",
"body": "oh yeah"
},
{
"id": 2,
"title": "another one",
"body": "test test test",
}
]
and I'm trying to route dynamically to each ID, set up in my config app like so:
myApp.config(function($routeProvider){
$routeProvider.
when('/',{
templateUrl: 'partials/home',
controller: 'mainController'
}).
when('/work/:id', {
templateUrl: 'partials/project',
controller: 'postController'
})
});
This is linked in my view like this, as I'm using ng-repeat all of the data for this view is succesfully being printed - it runs through my JSON fine
<div class = "images" ng-repeat = 'data in projects">
<a href = "#/work/{{ data.id }}">
<p> {{ data.title }} </p>
</a>
</div>
and my controller is set up like this, as you can see I'm calling a function defined in my factory to make the http request here:
var postController = angular.module('postControllers', []);
postControllers.controller('projectController', ['$scope', '$routeParams', 'workPost',
function($scope, $routeParams, workPost) {
$scope.projectID = $routeParams.id;
workPost.getData().success(function(data){
$scope.data = data;
});
}]);
so my factory is set up like so:
var workServices = angular.module('workServices', ['ngResource']);
projectServices.factory('workPost', function($http) {
return {
getData: function() {
return $http({
url: 'somelink/todata.json',
method: 'GET'
})
}
}
});
and finally I'm trying to access the data associated with the ID in my view like so:
<p> {{ data.body }} </p>
This does not work, however repeating it or specifying which item in the array I want (like data[1].title) does
Clicking the href routes me correctly to #/work/id, so my config is fine. I'm just not sure how to get the relevant data (i.e. title, body) associated with only that ID? I was thinking it may be the way my JSON is structured, perhaps I need to nest the parameters under an object for each?
Thanks for looking.
When you do $scope.data = data;, you are setting the entire array of objects into the scope's data variable.
What you want to do is find the object with id matching the route param's id from the array and set that object as scope's data. You can use Array.prototype.find() to do just that.
workPost.getData().success(function(data){
$scope.data = data.find(function (element) {
return element.id === $scope.projectID;
});
});
Your issue is that you are working with only one endpoint which is a static resource file that has all data in it.
You need to loop over that data and filter out the one you need by matching properties in the array of objects with the $routeParams value.
When you find the match assign it to the scope
I've stumbled upon a problem that should be common and obvious but I can't seem to wrap my head around it.
I'm working on a small prototype app. My backend developer provides me with profile data in a JSON object. Let's say, it looks like this:
profile = {Name: 'John', Email: 'john#mail.com', DOB: '1980-11-03'}
I need these values in multiple locations and I also don't want to put backend http calls in the controllers, so I've created a service to handle this:
angular.module('app', [])
.service('ProfileService', ['$http', function ($http) {
var service = this;
service.Name = null;
service.Email = null;
service.DOB = null;
service.getProfile = function () {
return $http.get('/profile').then(function (response) {
service.Name = response.data.Name;
service.Email = response.data.Email;
service.DOB = response.data.DOB;
return true;
});
};
return service;
}])
.controller('ProfileCtr', ['ProfileService', function (service) {
var vm = this;
service.getProfile().then(function () {
vm.Name = service.Name;
vm.Email = service.Email;
vm.DOB = service.DOB;
});
}]);
There are a number of problems with this solution:
Since the profile data consists of primitives, directly binding to the service properties won't give automagically synchronization of data.
More importantly, it breaks the DRY concept, as I've written data declarations in at least 3 different places (the database schema, in getProfile() and in the controller).
One solution would be to add a layer of indirection and create an object within the service:
angular.module('app', [])
.service('ProfileService', ['$http', function ($http) {
var service = this;
service.profile = {};
service.getProfile = function () {
return $http.get('/profile').then(function (response) {
for (key in response.data) {
service.profile[key] = response.data[key];
};
return true;
});
};
return service;
}])
.controller('ProfileCtr', ['ProfileService', function (service) {
var vm = this;
service.getProfile().then(function () {
vm.profile = service.profile;
});
}]);
This works in general, but now I get awkward controllerAs syntax:
<div ng-controller="ProfileCtr as ctr">
<h1> {{ ctr.profile.Name }}</h1>
<p> Email: {{ ctr.profile.Email }} <br /> DOB: {{ ctr.profile.DOB }}</p>
</div>
I'm wondering whether there is a way that gives me both: clean HTML {{ ctr.Name }} syntax and
a DRY programming style.
Thanks for any hints!
I have a feeling that you want more than this, but this to me is at least DRY:
angular.module('app', [])
.service('ProfileService', ['$http', function ($http) {
var service = this;
service.getProfile = function () {
return $http.get('/profile').then(function (response) {
return response.data;
});
};
return service;
}])
.controller('ProfileCtr', ['ProfileService', function (ProfileService) {
var vm = this;
ProfileService.getProfile().then(function (profile) {
vm.profile= profile;
});
}]);
The service gets the data. You could add functionality for caching here too. The controller uses the service to get the data. There is no repeated code.
I like to use the $scope variable, which would remove the one-layer of indirection issue. However, the controllerAs does have it's advantages, particuarly if you are using nested controllers and want to make it clear which controller you are using. And the $scope identifier will be removed in version 2.
Using a directive for this section of html instead of a controller should make you code easier to read and re-use. It also is advised to make it ready to be upgraded to version 2.
Then:
app.directive('isolateScopeWithControllerAs', function () {
var controller = ['ProfileService', function (ProfileService) {
var vm = this;
ProfileService.getProfile().then(function (profile) {
vm.profile= profile;
});
}];
return {
restrict: 'EA', //Default for 1.3+
controller: controller,
controllerAs: 'vm',
bindToController: true, //required in 1.3+ with controllerAs
templateUrl: // path to template
};
});
Then your HTML still gives you:
<h1> {{ vm.profile.Name }}</h1>
<p> Email: {{ vm.profile.Email }} <br /> DOB: {{ vm.profile.DOB }}</p>
The ProfileCtr as vm would come into more use if you were using the directive for more than one object. For example, if you has a user directive, then you could have:
controllerAs: 'user',
with user.profile.name and ng-repeat='friend in user.friends' etc.
So this is my service that I use to fetch user details.
angular.module('app')
.factory('userDetailService', function($http) {
var userData = {};
function getUserDetails(userId) {
if (userId) {
return $http.get("/users/" + userId).success(function(data) {
angular.copy(data[0], userData);
});
}
}
return {
userData: userData,
getUserDetails: getUserDetails
}
})
Now in Controller 1 that uses this service, I have this bit of code which works fine as I get the relevant data.
$scope.getUserId = function(userId) {
if (userId) {
$scope.userData = userDetailService.userData;
userDetailService.getUserDetails(userId).success(function() {
console.log($scope.userData); //Prints valid user data
});
}
};
After this function executes in Controller 1, I try to do the following in Controller 2:
$scope.userData = userDetailService.userData;
console.log($scope.userData); //Prints null
But $scope.userData is null. Isn't the whole purpose of using a service to share data between controllers? Since I have already set the value of userData in Controller 1, shouldn't I be able to access it in Controller 2?
Weirdly enough, the modal dialog which is the template for Controller 2 is able to access data in the form of {{userData.first_name}} or {{userData.last_name}}. If this works, why is $scope.userData null? What am I missing?
Edit:
Template 1:
<div id="myModal" ng-controller="Controller 1">
<modal-configure-user></modal-configure-user>
<a data-toggle="modal" data-target="#configureUserModal" href="#" ng-click="getUserId(user.id)" data-id="user.id">{{user.first_name + ' ' +user.last_name}}</a>
</div>
Template 2:
<div ng-controller="Controller 2" id="configureUserModal">
</div>
Both are modal dialog windows.
Your approach is not very reliable, since you can't be 100% sure that data has already loaded when you try to access it in the second controller. Instead of assigning user data to variable always invoke getUserDetails method, which returns a promise. Then you just need to cache loaded data to avoid duplicated requests.
angular.module('app')
.factory('userDetailService', function($q, $http) {
var userData;
function getUserDetails(userId) {
if (userId) {
return userData ? $q.when(userData) : $http.get("/users/" + userId).success(function(data) {
userData = data;
return userData;
});
}
}
return {
getUserDetails: getUserDetails
}
});
Wrapping userData into $q.when creates a promise object, which resolves immediately. This is what you need, because service API is now consistent - you always deal with promises.
The usage in both controller then would be:
userDetailService.getUserDetails(userId).then(function(data) {
$scope.userData = data;
});
I am very new with AngularJS. Thank you for answer. My code is as follow:
mainModule.controller('MainController', function($scope, $http) {
$http.get('http://localhost/backend/WebService.php', {params: {entity: 'IndexPageEntity'}}).
success(function(data) {
$scope.intro = data[0].IndexPageContent;
});
$http.get('http://localhost/backend/WebService.php', {params: {entity: 'ExhibitionServiceEntity'}}).
success(function(data) {
$scope.exhibit = data[0].ExhibitionServiceContent;
});
$http.get('http://localhost/backend/WebService.php', {params: {entity: 'ShootingServiceEntity'}}).
success(function(data) {
$scope.shooting = data[0].ShootingServiceContent;
});
});
My html file would be:
<div ng-controller="MainController">
<div>{{intro}}</div>
<div>{{exhibit}}</div>
<div>{{shooting}}</div>
</div>
I believe there must be some ways to improve the above code in order to reduce repetition. What I want is to pass entity parameter to the controller on creation.
Using ng-init to pass parameter is discouraged, according to the documentation. Writing custom directive to pass argument to scope does not work since parameters would be overwrittern.
What is the best practice to set params dynamically for use in $http? Thank you.
You should move all the logic to a service and use a directive. I would suggest you to modify your backend to return the same structured data, instead of IndexPageContent, ExhibitionServiceContent, etc. it should be Content or whatever name you want to use. But for now I've added a replace function to get the name of the content from the name of the entity.
mainModule.factory('webService', function($http) {
var apiUrl = 'http://localhost/backend/WebService.php';
function getContent(params) {
var config = {
'params': params
};
return $http.get(apiUrl, config);
};
return {
getContent: function(params) {
return getContent(params)
}
};
});
mainModule.controller('MainController', function($scope, webService) {
var params = {
'entity': $scope.entity
};
var contentName = $scope.entity.replace('Entity', 'Content');
webService.getContent(params).then(function (data) {
$scope.content = data[0][contentName];
});
});
mainModule.directive('EntityContent', function() {
return {
controller: 'MainController',
replace: true,
restrict: 'E',
scope: {
entity: '#entity'
},
template: '<div>{{ content }}</div>'
};
});
<div>
<entity-content entity="IndexPageEntity">
<entity-content entity="ExhibitionServiceEntity">
<entity-content entity="ShootingServiceEntity">
</div>
Create an object data and send the value for the key inside the object at every call.. Also pass the value for key to be set inside the scope..
E.g.
$scope.makeHttpCall = function(data) {
$http.get('http://localhost/backend/WebService.php', {params: data}).
success(function(data) {
$scope[$scope.key] = data[0][$scope.key];
});
};
you can then call this function as
$scope.key = 'IndexPageContent';
data = {
entity : 'yourValueHere'
};
$scope.makeHttpCall(data);
You can set other values as well inside the scope that are dynamic for each request..
I hope this makes sense to you...