AngularJS, Loading spinner directive with transclude? - angularjs

I want to make a directive with shows a spinner when $http request is executing.
I thought this would be working (e.g. load users):
Main Controller
app.controller('UserListController', function($scope, $http) {
$scope.users = [];
$scope.loading = true;
$http.get('/users').then(function(response) {
$scope.users = response.data;
}).finally(function() { $scope.loading = false; });
});
Template using the directive
<div request-loading="loading">
<ul>
<li ng-repeat="user in users">{{ user.name }}</li>
</ul>
</div>
request-loading-directive.js
app.directive('requestLoading', function() {
return {
scope: {
requestLoading: '='
},
transclude: true, // I think this is not Ok
templateUrl: 'request-loading-directive.html'
};
});
request-loading-template.html
<div ng-show="requestLoading" class="super-cool-spinner">
Loading text with the spinner...
</div>
// This is what is not working
// I thought the content of the users (ul and li) would be render in this div
// when the request is Ok.
<div ng-transclude ng-show="! requestLoading"></div>
Any help would be nice!
Thx.

Related

ui-bootstrap: accessing modal instance from within directive controller

Noob alert!
The question here is exactly the one that I have: angularjs - Accessing ui-bootstrap modal dismiss and close function from another controller
However, I don't understand the answer that was chosen as correct! I understand the words but don't understand how to share the controller.
I have an app controller that opens a modal, and within the modal's template, I have a directive. I would like to be able manipulate the modal instance from within the directive's controller.
Here's my markup:
<script type="text/ng-template" id="settingsModal">
<div class="modal-header">
<h3 class="modal-title">Confirm update!</h3>
</div>
<div class="modal-body">
<calendar-settings cid="calendarId"/>
</div>
<div class="modal-footer">
<!-- I want these buttons inside the directive instead -->
<button class="btn btn-primary" ng-click="ok()">OK</button>
<button class="btn btn-warning" ng-click="cancel()">Cancel</button>
</div>
</script>
In the body of the modal, I am calling the calendarSettings directive, and I'd like to be able to use the .dismiss and .close methods of the modal instance from within my directive.
Here's what the open method looks like:
var modalInstance = $modal.open({
templateUrl: 'settingsModal',
controller: 'ModalInstanceController',
resolve: {
item: function() {
return $scope.sEntry;
},
cid: function() {
return id;
}
},
reject: {
item: function() {
return $scope.sEntry;
},
cid: function() { return null; }
}
});
And here's the ModalInstanceController:
calendarsApp.controller('ModalInstanceController', function($scope, $modalInstance, item, cid){
$scope.item = item;
$scope.calendarId = cid;
console.log(item);
$scope.ok = function () {
$modalInstance.close($scope.item);
};
$scope.cancel = function () {
$modalInstance.dismiss('cancel');
};
});
I think what I want to do is easy, but I'm not sure how to expose the modal instance controller to the directive, or if there is a different approach I should be thinking about.
NOTE: I didn't paste the code from the directive in here. But I'm trying to access the close and dismiss methods of the modal instance from within my directive's controller.
Thanks for any help!
So
var modalInstance = $modal.open({
templateUrl: 'settingsModal',
controller: 'ModalInstanceController',
resolve: {
item: function() {
return $scope.sEntry;
},
cid: function() {
return id;
}
},
reject: {
item: function() {
return $scope.sEntry;
},
cid: function() { return null; }
}
//This comes from ModalInstanceController result is
//equivalent to whatever u send back
}).result.then(function(result){
console.log(result); //Press f12 on chrome and go to console.
});
calendarsApp.controller('ModalInstanceController', function($scope, $modalInstance, item, cid){
$scope.item = item;
$scope.calendarId = cid;
console.log(item);
$scope.ok = function () {
//You are sending back $scope.item but if you want everything
//send back the $scope which is equivalent to sending back //ModalInstanceController
// $modalInstance.close($scope.item);
$modalInstance.close($scope);
};
$scope.cancel = function () {
$modalInstance.dismiss('cancel');
};
});

Ng-show in a directive template

I'm trying to make a simple app with Angularjs and MongoDB. I have some articles where it's possible to add a comment and display them.
The comments section is in a template defined on a Directive, who is himself in the Home template with another Controller.
To be clear (or maybe not?) : HomeTemplate [with MainCtrl] own a COMMENT element [with his directive].
Here is my directive
app.directive('Comment', function(){
return{
templateUrl: '/comments.html',
scope:{
post:'=datapost'
},
controller: ['$scope', 'posts', 'auth', function($scope, posts,auth){
$scope.isLoggedIn = auth.isLoggedIn;
$scope.addComment = function(){
if($scope.commentToAdd === '')
return;
$scope.isLoading = true;
posts.addComment($scope.post._id, {
body: $scope.commentToAdd,
author: auth.currentUser()
}).success(function(comment){
$scope.post.comments.push(comment);
});
$scope.commentToAdd = '';
$scope.isLoading = false;
};
}]
}
});
Auth & Posts directives are defined later
Here it's my MainCtrl where I'm supposed to trigger the event :
$scope.showComments = function(){
$scope.showingComments = true;
};
And here my HTML :
<div ng-show="showingComments =!showingComments">
<div comment datapost="post" ></div>
</div>
I'm not too sure what your question here is but:
<div ng-show="showingComments =!showingComments">
should be
<div ng-show="showingComments">
=! is not a valid operator, and you're comparing the same variable to itself
use ng-show="showingComments" to show if true, or ng-show="!showingComments" to show if false

AngularJS getting data from controller to directive

I am trying do display data retrieved from database in controller to directive. I am using Controller as syntax, but when i try to display my data in directive it is undefined. I am getting right value for name and example variable, but not for items variable.
TaskCtrl.js
app.controller('TasksCtrl', ['$scope', 'TaskService', function ($scope, TaskService) {
// initialize function
this.newTask = true;
this.name = "My name is Nedim";
this.example = "Example";
this.templates = {
new: "views/task/addTask.html",
view: "views/task/viewTask.html"
};
// load all available tasks
TaskService.loadAllTasks().then(function (data) {
this.items = data.tasks;
});
$scope.$on('newTaskAdded', function(event, data){
this.items.concat(data.data);
});
return $scope.TasksCtrl = this;
}]);
taskList.html
<ul class="list-group">
<li ng-repeat="item in taskCtrl.items" class="list-group-item">
<a ng-click="openItem()">
<span class="glyphicon glyphicon-list-alt" aria-hidden="true"></span>
<span>{{item.name}}</span>
<span class="task-description">{{item.description}}</span>
</a>
</li>
</ul>
<div>{{taskCtrl.name}}</div>
<div>{{taskCtrl.example}}</div>
<div>{{taskCtrl.items}}</div>
entityTaskList directive
app.directive('entityTaskList', function(){
return {
restrict: 'E',
templateUrl: 'views/task/taskList.html',
scope: {
items: '='
},
bindToController: true,
controller: 'TasksCtrl as taskCtrl',
link: function(scope, element, attrs){
console.log("items" + scope.items);
scope.openItem = function(){
var ctrl = scope.taskCtrl;
ctrl.newTask = false;
};
}
};
});
task.html
<div class="container-fluid">
<div class="row">
<div class="col-sm-6">
<entity-task-list items="items"></entity-task-list>
</div>
<div class="col-sm-6" ng-controller="TaskDetailCtrl as taskDetailCtrl">
<!-- form for adding new task -->
<div ng-show="taskCtrl.newTask" ng-include="taskCtrl.templates.new"></div>
<!-- container for displaying existing tasks -->
<div ng-show="!taskCtrl.newTask" ng-include="taskCtrl.templates.view"></div>
</div>
</div>
</div>
Looks like you have forgotten to use the controller name for referencing the variable.
So in your directive you have:
controller: 'TasksCtrl as taskCtrl'
Meaning this should be:
<entity-task-list items="taskCtrl.items">
The problem might be this. When you use functions like:
TaskService.loadAllTasks().then(function (data) {
this.items = data.tasks;
});
$scope.$on('newTaskAdded', function(event, data){
this.items.concat(data.data);
});
then this in these functions' body refers to the function's scope rather than the controller's scope, so you're setting properties on the wrong this object.
To avoid this problem you can do one of the following:
option 1:
Use bind. For example:
TaskService.loadAllTasks().then(function (data) {
this.items = data.tasks;
}.bind(this));
$scope.$on('newTaskAdded', function(event, data){
this.items.concat(data.data);
}.bind(this));
option 2:
A common practice in Angular controllers is to save this in a local variable, and then use that variable instead. Example:
app.controller('TasksCtrl', ['$scope', 'TaskService', function ($scope, TaskService) {
// initialize function
var vm = this;
vm.newTask = true;
vm.name = "My name is Nedim";
vm.example = "Example";
vm.templates = {
new: "views/task/addTask.html",
view: "views/task/viewTask.html"
};
// load all available tasks
TaskService.loadAllTasks().then(function (data) {
vm.items = data.tasks;
});
$scope.$on('newTaskAdded', function(event, data){
vm.items.concat(data.data);
});
return $scope.TasksCtrl = this;
}]);

Directive not rendering after $http request

I began working with angularjs and have a problem.
app.js (just the relevant route)
$routeProvider.when('/search', {templateUrl: 'partials/search-results.html', controller: 'SearchController', title: 'Product Search'});
search-results.html
<div product-list></div>
index.html
<div class="page_content_offset p_bottom_0">
<div class="container mh600">
<div ng-view></div>
</div>
</div>
product-list.html
<div ng-repeat="item in items">
<p>{{item.address_components[0].long_name}}</p>
</div>
controller.js just relevant code:
$scope.search = function() {
$scope.loading = true;
$scope.items = {};
ProductSearchService.searchItems($scope.term).then(function(data) {
$scope.items = data;
$scope.loading = false;
});
};
directives.js (just relevant code)
directive('productList', function() {
return {
restrict: 'EA',
templateUrl: 'partials/list/product-list.html'
};
My Problem now is:
The ProductSearchService loads the data. But the directive not rendering as expected.
If i move the directive code from search-results.html to my index.html like this:
<div class="page_content_offset p_bottom_0">
<div class="container mh600">
<div product-list></div>
<div class="clearfix"></div>
</div>
</div>
evrything is rendered nicely. So i think i included my directive wrongly or forgot something important.
I've created a plunkr similar to my setup:
http://plnkr.co/edit/60dvyFnz74yrsK12J2J4?p=preview
Everything works fine in that.
In my local application i changed the "product-list.html" model property access to this
<div ng-repeat="item in $parent.items">
<p>{{item.address_components[0].long_name}}</p>
</div>
Update controllers.js
angular.module('myApp.controllers', [])
.controller('SearchController', ['$scope','$http','ProductSearchService', function($scope, $http, ProductSearchService) {
$scope.items = {};
$scope.search = function() {
$scope.loading = true;
ProductSearchService.searchItems($scope.term).then(function(data) {
$scope.items = data;
$scope.loading = false;
});
};
}]);
Update 2:
I now have a plnkr where the problem can be shown:
http://plnkr.co/edit/QgU1cf3JeJYKu6Fkl9zv?p=preview
You did not set any scope attribute to your directive.
This means that your directive use the defining/containing scope as the directive own scope.
Thus , the scope that is used in product-list.html is the same as the one used by search-result.html (and so by the SearchController).
The only reason , you could have to use the $parent.items would be if you specified scope:{}, in your directive.You can even test it in your plunker (I did).
Can you double check the directive scope attribute ?
Thx
directive('productList', function() {
return {
restrict: 'EA',
replace: true,
templateUrl: '<section data-ng-include=" 'partials/list/product-list.html' "></section>'
};
});
try this one as the directive :)

In AngularJs, how do I identify a specific scope within a controller that is used multiple times on a page?

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

Resources