I am facing a problem with showing a counter value inside a directive.
I am working on an cart application where I have a header directive which is loading only once, when the app loads. I have list of products with 2 links/buttons for adding in wishlist and adding in cart.
Scenario:
When I am adding a product in wishlist, I am getting that product object from my controller and sending it in a service where I am pushing that object in a service wishlist array. Then I am running a loop to to count the number of objects inside the wishlist array and caching it in a service variable (I am not using $rootScope). I am using another service function to return this updated wishlist array count. Inside the directive I am using this service function to get the updated count.
Problem:
The wishlist array count is updating each time I am adding a product inside the wishlist array. But it is not showing the updated value in the view.
See if you want use service then use its reference variable as below
Jsfiddle link
js Code
var app = angular.module('myApp', []);
app.controller('ctrl', function($scope, myservice) {
$scope.data = myservice.data;
$scope.add = function() {
myservice.add('test');
}
});
app.controller('ctrl2', function($scope, myservice) {
$scope.cart = myservice;
});
app.directive('myDir', function() {
return {
restrict: 'EA',
template: '<span>my dir Count {{cart.data.length}}</span>'
}
});
app.directive('myDir1', function(myservice) {
return {
restrict: 'EA',
template: '<span>my dir1 Count {{data.get().length}}</span>',
scope: {},
link: function(scope) {
scope.data = myservice;
}
}
});
app.service('myservice', function() {
this.data = [];
this.add = function(item) {
this.data.push(item);
};
this.get = function() {
return this.data;
};
return this;
});
HTML code
<div ng-app='myApp'>
<div ng-controller='ctrl'>
CTRL
<button ng-click='add()'>
Add to wishlist
</button>
Data :{{data}}
</div>
<hr>
<div ng-controller='ctrl2'>
Count {{cart.data.length}}
<br> Count {{cart.get().length}}
</div>
</div>
This way you can get service data any where in app
Related
I have a directive as follows
<div ng-controller=prdController as prd>
<my-dir data=prd.data ng-click=stateChanged()></my-dir>
</div>
where prd.data is an object. In my directive I did the following
app.directive('myDir',function(){
return {
scope:{
data:'=data'
},
templateUrl: './templates/testtemplate.html',
controllerAs:'bd',
controller:function($scope,$attrs){
this.stateChanged = function (value) {
$attrs.data = { 'fd','sdfs'};
}
}
});
I am unable to modify the data value within the controller how do I proceed thanks in advance.
I think the problem could be in the way you trying to get data object in your controller.
You should try to get it by $scope.data. All directive inputs getting attached to your internal scope.
$attrs - is a hash object with key-value pairs of normalized attribute names and their corresponding attribute values. According to AngularJS docs.
Try this
var app = angular.module("myApp", []);
app.controller("prdController", function($scope) {
$scope.data = "krupesh";
$scope.stateChanged = function() {
$scope.data = "kotecha";
}
});
app.directive('myDir', function() {
return {
restrict: 'E',
template: "<div>{{data}}</div>"
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp" ng-controller=prdController as prd>
<my-dir data="prd.data" ng-click=stateChanged()></my-dir>
</div>
I currently have an AngularJS controller that is basically getting some JSON asynchronously through a $http.get() call, then linking the obtained data to some scope variable.
A resumed version of the controller code:
mapsControllers.controller('interactionsController', ['$http', function($http) {
var ctrlModel = this;
$http.get("data/interactionsPages.json").
success(function(data) {
ctrlModel.sidebar = {};
ctrlModel.sidebar.pages = data;
}).
error(function() {...});
}]);
Then, I have a custom directive which receives those same scope variables through a HTML element.
A resumed version of the directive code:
mapsDirectives.directive('sidebar', function() {
return {
restrict : 'E',
scope : {
pages : '#'
},
controller : function($scope) {
$scope.firstPage = 0;
$scope.lastPage = $scope.pages.length - 1;
$scope.activePage = 0;
//...
},
link : function(scope) {
console.log(scope.pages);
},
templateURL : 'sidebar.html'
}
});
A resumed version of the HTML:
<body>
<div ng-controller='interactionsController as interactionsCtrl'>
<mm-sidebar pages='{{interactionsCtrl.ctrlModel.sidebar.pages}}'>
</mm-sidebar>
</div>
</body>
The problem is, since the $http.get() is asynchronous, the directive is being badly initialised (e.g: $scope.pages.length - 1 is undefined).
I couldn't find anything that solved this problem for me, although there are some presented solutions that would seem to solve the case. Namely, I tried to watch the variables, only initialising the variables after detected changes, as suggested in many other posts. For testing, I used something like:
//... inside the directive's return{ }
link: function() {
scope.$watch('pages', function(pages){
if(pages)
console.log(pages);
});
}
I've tested it, and the $watch function wasn't called more than once (the logged value being undefined), which, I assume, means it isn't detecting the change in the variable value. However, I confirmed that the value was being changed.
So, what is the problem here?
Move the declaration for the sidebar object in the controller and change the scope binding to =.
mapsDirectives.controller("interactionsController", ["$http", "$timeout",
function($http, $timeout) {
var ctrlModel = this;
ctrlModel.sidebar = {
pages: []
};
/*
$http.get("data/interactionsPages.json").
success(function(data) {
//ctrlModel.sidebar = {};
ctrlModel.sidebar.pages = data;
}).
error(function() {});
*/
$timeout(function() {
//ctrlModel.sidebar = {};
ctrlModel.sidebar.pages = ["one", "two"];
}, 2000);
}
]);
mapsDirectives.directive('mmSidebar', [function() {
return {
restrict: 'E',
scope: {
pages: '='
},
controller: function() {},
link: function(scope, element, attrs, ctrl) {
scope.$watch("pages", function(val) {
scope.firstPage = 0;
scope.lastPage = scope.pages.length - 1;
scope.activePage = 0;
});
},
templateUrl: 'sidebar.html'
};
}]);
Then match the directive name and drop the braces.
<mm-sidebar pages='interactionsCtrl.sidebar.pages'>
</mm-sidebar>
Here's a working example: http://plnkr.co/edit/VP79w4vL5xiifEWqAUGI
The problem appears to be your html markup.
In your controller you have specified the ctrlModel is equal to this.
In your html markup you have declared the same this to be named interactionsController.
So tacking on ctrlModel to interactionsController is incorrect.
<body>
<div ng-controller='interactionsController as interactionsCtrl'>
<!-- remove this -->
<mm-sidebar pages='{{interactionsCtrl.ctrlModel.sidebar.pages}}'>
<!-- replace with this -->
<mm-sidebar pages='{{interactionsCtrl.sidebar.pages}}'>
</mm-sidebar>
</div>
</body>
I'm trying to create an AngularJS directive that calls a service created using $resource with the value of the attribute in the template and makes the result available in the scope of the element.
I have a very simple service:
services.factory('Concept', ['$resource', function($resource) {
return $resource('http://example/api/:id');
}]);
And a directive:
directives.directive('anConcept', ['Concept', function(Concept) {
return {
scope: {
anConcept: '#'
},
restrict: 'A',
controller: function($scope, $element) {
$scope.results = Concept.get({id: concept});
}
}
}]);
I then try to invoke this in my view:
<div an-concept="1">
{{results}}
</div>
Looking at the network pane in my browser's debugger I can see the requests getting made, but the result isn't being made available in the scope of the element.
I'm sure I'm making a very basic mistake but I can't figure out what it is.
Note that when you're working with a $resource, you're receiving back a promise. Additionally, you may be running into some trouble with the results property of your $scope if the directive's scope is isolated.
angular.module('myApp', ['ngResource'])
.factory('Concept', ['$resource',
function($resource) {
return $resource('https://api.stackexchange.com/2.2/info');
}
])
.directive('anConcept', ['Concept',
function(Concept) {
return {
restrict: 'A',
link: function($scope, $element, $attrs, $controllers) {
Concept.get({
site: $attrs.anConcept
}, function(results) {
$scope.results = results.items[0].total_users;
});
}
};
}
]);
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular-resource.js"></script>
<div ng-app='myApp'>
<div an-concept="stackoverflow">
Total stackoverflow users: {{results}}
</div>
</div>
It's because the result of that get is a promise. You need to do something like:
Concept.get({id: concept}).success(function(data) {
$scope.results = data;
};
EDIT
Sorry I think I made a mistake, $resource works a bit different then $http :
Concept.get({id: concept}, function(data) {
$scope.results = data;
};
If you want to see that value out of directive,I will say that you will not see it as a simple.because your directive is ISOLATED-SCOPE directive.It means your values in that scope will be isolated from parent scope and you will not see that value out of directive(There is a way to get that isolated scope).But if you want to see it in directive,I will say your code is correct code.If response has been accepted,you result will be defined.
How can I use a different sets of initialization variables for each instance of controller in my app?
in view:
<div ng-controller="showProjectList">
{{project_list}}<!--user 1-->
</div>
<div ng-controller="showProjectList">
{{project_list}}<!--user 2-->
</div>
in controller
myapp.controller('showProjectList',function($http)
{ $scope.project_list= <Here I have a http request with argument user_id to fetch project_list>
}
Now how do I initialize each controller with a different user_id? One solution I have readon stackexchange & on google-groups is the use of ng-init.(link google-grp: https://groups.google.com/forum/#!topic/angular/J6DE8evSOBg) .However the use of ng-init is cautioned against in the same threads. So how do you initialize a controller with data then ?
You could use a combination of a controller, a directive and a service for that matter.
The controller is holding the user id's.
The directive is rendering the project list.
The service is responsible for fetching the data from the server. You could implement a cache and/or use $resource in here.
Here is the template code:
<div ng-controller="Projects">
<!-- here you can put an input element with
ng-model="users" to modify the user list on the fly -->
<div ng-repeat="user in users">
<project-list user="user" />
</div>
</div>
The controller:
myapp.controller('Projects', ['$scope', function($scope) {
$scope.users = [1, 2, 3];
}]);
The directive:
myapp.directive('projectList', ['UserService', function(UserService) {
return {
restrict: 'E',
scope: {
user: "="
},
templateUrl: 'project-list.html',
link: function($scope, $element, $attrs) {
UserService.getUserProject($scope.user).then(function(response) {
$scope.userProjects = response;
});
}
};
}]);
The service:
myapp.factory('UserService', ['$http', function($http) {
var getUserProject = function(user) {
var promise = $http.get('users/' + user + '/project');
return promise;
}
return {
getUserProject: getUserProject
}
}]);
I have an application that uses multiple tabs with a grid in each tab. I have multiple pages using this same configuration.
I have a single controller that works for all grids. I would like to reuse this controller throughtout my application.
My problem is that I am trying to lazy load the data for each tab until the tab is clicked. What I can't figure out is how to tell the controller which scope belongs to which tab and ONLY get the data for that grid. I know internally AngularJs does this because if I just load all the data for each tab at once I can click on my paginator, search, etc. for each tab and only that scope is updated.
I have ng-click setup for each tab and I can get my controller to fire when a tab is clicked. However, the controller calls all instantiated scopes to load data for their respective grids.
My approach may not be the best but it seems silly to create seperate controllers that do exactly the same thing.
Note: Angular UI tabs with bootstrap is not an option.
My view
<div ng-app="TabsApp">
<div tabs>
<div class="tabs">
<a ng-click="clickTab(0)" ng-class="{selected: selectedTab==0}">Localized Text From Server</a>
<a ng-click="clickTab(1)" ng-class="{selected: selectedTab==1}">Localized Text From Server</a>
<a ng-click="clickTab(2)" ng-class="{selected: selectedTab==2}">Localized Text From Server</a>
</div>
<div class="tab-content">
<div ng-show="selectedTab==0" ng-init="init(#Model.UserId, 'useraddresses')" ng-controller="ListCtrl">#Html.Partial("_Grid0")</div>
<div ng-show="selectedTab==1" ng-init="init(#Model.UserId, 'userphones')" ng-controller="ListCtrl">#Html.Partial("_Grid1")</div>
<div ng-show="selectedTab==2" ng-init="init(#Model.UserId, 'usernotes')" ng-controller="ListCtrl">#Html.Partial("_Grid2")</div>
</div>
</div>
</div>
My app and factory url
var TabsApp = angular.module("TabsApp", ['ngResource', 'ngRoute']);
TabsApp.factory('Api', function($resource){
return $resource('/api/user/:userId/:ctrl', { userId: '#userId', ctrl: '#ctrl'});
});
My controller - child scope(s)
var ListCtrl = function ($scope, $location, Api){
$scope.search = function () {
Api.get({
userId: $scope.userId,
ctrl: $scope.ctrl,
q: $scope.query
//etc.
},
function(data){
$scope.items = data.items;
//other mapping
});
};
$scope.init = function(userId, ctrl){
$scope.userId = userId;
$scope.ctrl = ctrl;
};
$scope.reset = function(){
$scope.items = [];
$scope.search();
};
//kind of a hack but broadcasting was the only way I could figure out to listen for tab changes
$scope.tabModelChange = { 'isChanged': false };
$scope.$on('tabModelChange', function(event, args) {
$scope.tabModelChange.isChanged = true;
var activeTab = args.val[$scope.selectedTab];
if (!activeTab.isLoaded) {
$scope.reset();
}
});
//filtering, sorting, pagination, etc.
};
My directive: Parent scope
TabsApp.directive('tabs', function () {
return {
controller: function ($scope, $element) {
$scope.selectedTab = 0;
$scope.tabModel = [];
//I use localized text and any number of tabs on my views from the server so the client wont know how many tabs each view will have
var tabs = angular.element($element.children()[1]).children();
angular.forEach(tabs, function (value, key) {
$scope.tabModel.push({'isLoaded' : false, 'isActive': false});
});
$scope.clickTab = function(tab) {
$scope.selectedTab = tab;
$scope.tabModel[$scope.selectedTab].isActive = true;
};
$scope.$watch('tabModel', function(newVal, oldVal) {
if (newVal !== oldVal) {
$scope.$broadcast('tabModelChange', { 'val': newVal });
}
}, true);
}
};
});
I suspect that when my controller receives a broadcast that the tab model has changed it calls $scope.reset with $scope being a parent scope which then traverses the child scopes looking for 'reset'. Because there are 3 instances of ListCtrl, parent scope finds 3 child scopes with 'reset'. Hence, all the data gets loaded at once.
How can I get $scope.reset to match the tab that was clicked? Thanks.
With minimal change to your existing code, here's a plunker that does what you want, I think.
http://plnkr.co/edit/TVwWQehgWJay7ngA8g6B?p=preview
Basically I made a second directive called tab that accepts an argument which is a function to evaluate when that tab is switched to.
TabsApp.directive('tab', function () {
return {
require: '^tabs',
link: function (scope, element, attrs, tabsCtrl) {
tabsCtrl.add({
'isLoaded' : false,
'isActive': false,
'changed': function () { scope.$eval(attrs.tab); }
});
}
};
});
Here's what we do (i'm a bit lazy and going to pull use our code), We load when tab is clicked.
Here's the tabs
<ul class='nav nav-pills'>
<li ng-class="{ active : mode == 'incomplete'}"><a tabindex="-1" href="#/incomplete" ng-click='mode="incomplete"'>Incomplete orders</span></a></li>
<li ng-class="{ active : mode == 'completed'}"><a tabindex="-1" href="#/completed" ng-click='mode="completed"'>Completed orders</a></li>
</ul>
We setup routes
.config(['$routeProvider', '$env', function ($routeProvider, $env) {
$routeProvider.when("/completed", {
templateUrl: $env.urlRoot + "content/partials/orders/completed.htm", controller: 'CompletedOrdersCtrl'
});
$routeProvider.when("/incomplete", {
templateUrl: $env.urlRoot + "content/partials/orders/incomplete.htm", controller: 'IncompleteOrdersCtrl'
});
$routeProvider.otherwise({
templateUrl: $env.urlRoot + "content/partials/orders/incomplete.htm", controller: 'IncompleteOrdersCtrl'
});
} ]);
We then have the two controllers IncompleteOrdersCtrl and CompleteOrdersCtrl that call the service to get the correct data. You could possibly consolidate into one controller with a parameter passed in the route config.
Hopefully this works for you
--dan