I'm creating a set of widgets with AngularJS 1.5's new components. The problem is, when using the same widget multiple times, they somehow share their controller or scope. I thought one of the things about components was that their scope is completely isolated?
My main html template which hold the widgets:
<widget-list
title="Books"
class="col-xs-12 col-md-4">
</widget-list>
<widget-list
title="Movies"
class="col-xs-12 col-md-4">
</widget-list>
<widget-list
title="Albums"
class="col-xs-12 col-md-4">
</widget-list>
My widget template:
<div class="widget widget-list">
<div class="panel b-a">
<div class="panel-heading b-b b-light">
<h5>{{$widget.title}}</h5>
<div class="pull-right">
<button type="button" class="btn btn-default btn-sm" ng-click="$widget.doSomething()">
Do something
</button>
</div>
</div>
<div class="panel-content">
{{$widget.content || 'No content'}}
</div>
</div>
</div>
My widget component:
app.component('widgetList', {
templateUrl: 'template/widget/widget-list.html',
bindings: {
title : '#',
},
controllerAs: '$widget',
controller: function($timeout) {
$widget = this;
console.log('Title on init: ', $widget.title)
$timeout(function() {
console.log('Title after 3 seconds: ', $widget.title)
}, 3000)
$widget.doSomething = function() {
$widget.content = "Something";
}
}
});
When running my code, this is what my console looks like:
Title on init: Books
Title on init: Movies
Title on init: Albums
(3) Title after 3 seconds: Albums
Also after rendering, all three widgets display No content in their template. But, when clicking the doSomething() button in either one of the three widgets, only the content of the last widget updates to Something.
What is happening here? Why are my components not 'isolated'?
Looks like you have a global variable called $widget here, try this:
var $widget = this;
instead of
$widget = this;
It creates a mess since the $widget variable holds a reference to the controller that has been recently initialized, in this case to the controller of the third component.
The problem with your code is that you are declaring the $widget on window scope, that's why your controller prints the last value, bacause it was being overridden every time the controller was getting instantiated. Use a var $widget instead and your code will work fine.
The following snippet solves this issue:
angular.module('app', [])
.component('widgetList', {
templateUrl: 'template/widget/widget-list.html',
bindings: {
title: '#',
},
controllerAs: '$widget',
controller: WidgetListController
});
function WidgetListController($timeout) {
var $widget = this;
console.log('Title on init: ', $widget.title)
$timeout(function() {
console.log('Title after 3 seconds: ', $widget.title)
}, 3000)
$widget.doSomething = function() {
$widget.content = "Something";
}
}
angular.element(document).ready(function() {
angular.bootstrap(document, ['app']);
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.1/angular.min.js"></script>
<widget-list title="Books" class="col-xs-12 col-md-4">
</widget-list>
<widget-list title="Movies" class="col-xs-12 col-md-4">
</widget-list>
<widget-list title="Albums" class="col-xs-12 col-md-4">
</widget-list>
<script type="text/ng-template" id="template/widget/widget-list.html">
<div class="widget widget-list">
<div class="panel b-a">
<div class="panel-heading b-b b-light">
<h5>{{$widget.title}}</h5>
<div class="pull-right">
<button type="button" class="btn btn-default btn-sm" ng-click="$widget.doSomething()">
Do something
</button>
</div>
</div>
<div class="panel-content">
{{$widget.content || 'No content'}}
</div>
</div>
</div>
</script>
Related
I recently had a coding challenge that I got rejected for because it was garbage. Didn't have a lot of time so I threw everything together in one giant HTML file/angular controller, so I'm in the middle of rewriting it in templates to try to make it more reusable. So far it's going well, but I'm having some trouble with an html template not being able to access ng-model. Whenever I console.log the ng-model, I get undefined.
Here's the top layer HTML:
<div class="col-md-8 box">
<div class="panel panel-default">
<div class="panel-heading">Companies</div>
<div class="panel-body">
<div ng-repeat="company in companies">
<div class="panel panel-default">
<div class="panel-heading">Name: {{company.name}} <button ng-click="companies[$index].editCompany = !companies[$index].editCompany" class="pull-right">EDIT COMPANY</button></div>
<div class="panel-body" ng-if="!companies[$index].editCompany">
<p>Address: {{company.address}}</p>
<p>Revenue: {{company.revenue}}</p>
<p>Phone Number: {{company.phone}}</p>
<button ng-click="getPeople(companies[$index]._id, $index); companies[$index].viewEmployees = !companies[$index].viewEmployees">People Who Work Here</button>
<div ng-if="companies[$index].viewEmployees">
<show-employees-list></show-employees-list>
</div>
</div>
</div>
<div ng-if="companies[$index].editCompany">
<edit-company-directive></edit-company-directive>
</div>
</div>
</div>
</div>
</div>
And here's the HTML for the directive:
<div class="employee-box" ng-repeat="employee in companies[$index].employees">
<span class="glyphicon glyphicon-edit pull-right" ng-click="companies[$index].editEmployee = !companies[$index].editEmployee; clickEdit()"></span>
<span class="glyphicon glyphicon-remove pull-right" ng-click="deletePerson(employee._id, $index, companies[$parent.$index].employees)"></span>
<div ng-if="!companies[$index].editEmployee">
<div>
<p><b>Name:</b> {{employee.name}}</p>
<p><b>Email:</b> {{employee.email}}</p>
</div>
</div>
<div ng-if="companies[$index].editEmployee" class="form-body">
<form name="editPersonForm" ng-submit="editPerson(employee._id, $parent.$parent.index, $parent.index)">
<input type="text" ng-model="nameEdit" id="nameEdit" placeholder="Employee" class="form-control" required></input>
<input type="text" ng-model="emailEdit" id="emailEdit" placeholder="Email" class="form-control" required></input>
<button type="submit" id="submitButton" class="btn btn-success form-actions">Submit</button>
</form>
</div>
</div>
And here's the directive code:
'use strict';
(function() {
angular
.module('sigFig')
.directive('showEmployeesList', showEmployeesList);
function showEmployeesList(sigFigFactory) {
var directive = {
restrict: 'E',
templateUrl: 'Directives/showEmployeesList/showEmployeesList.html',
scope: '=',
require: '^parentDirective',
link: link
};
return directive;
function link(scope, element, attra, controller) {
scope.deletePerson = function(id, index, employees) {
sigFigFactory.deletePerson(id).then(function(response) {
employees.splice(index, 1);
return response;
})
};
scope.editPerson = function(personId, index1, index2) {
scope.person = {
name: scope.nameEdit,
email: scope.emailEdit
};
console.log('person ', scope.person);
};
}
}
})();
I'm thinking it's some sort of scoping issue that I just don't see, and hoping someone can help. When I console.log that person object I get undefined for both properties.
it's good idea to use angular directive, and also you need to read more about it:
you just define scope as variable but it's object, and there isn't scope.nameEdit to console
app.directive("name", function() {
return {
templateUrl: "your.html", //it's string
restrict: 'E',
scope: { //it's object
param1: "=" //var
param2: "#" //string
param3: "&" //method and etc
},
link: function(scope){ //it's function
//scope.param1
//scope.param2
//scope.param3
}
}
})
<name param1="{foo: 'test'}" param2="hello" param3="callback"></name>
with directive you can pass everything from your basic view (controller) to the directive, you can $watch value on change in your controller and more options.
Title says it all. I created a modal and I want data to pass to the modal from another page.
Here is the button that opens the open the modal. (rollup.html)
<button id="myBtn" ng-click="printDivModal('rollup-tab', test)">Modal Test</button>
Here I setup up the controller and the resolve (rollup.js)
app.controller('Rollup', function($scope, $rootScope, $http, $uibModal, headersvc, locFiltersvc) {
$scope.printDivModal = function(divName,test) {
console.log('opening pop up');
var ModalInstance = $uibModal.open({
scope: $scope,
animation: $scope.animationsEnabled,
templateUrl: 'app/views/modals/stackedModal.html',
size: 'xl',
controller: 'PrintViewCtrl',
backdrop : 'true',
resolve: {
test: function () {
return test;
}
}
});
}
});
app.controller('PrintViewCtrl', function($scope, $http, $rootScope, $uibModalInstance) {
$scope.test = function() {
$scope.regionName;
$scope.groupName;
$scope.mcName;
$scope.districtNumber;
$scope.routeNumber;
$scope.weekEndDate;
};
});
I am not sure if I need to put this in the modal-body (stackedModal.html), or if clicking the button will pass 'test'.
<div class="modal-body">
<p>{{test.regionName}}</p>
</div>
The data I want to pass to the modal is all within Route.html. Here is apart of the page.
<div class="row">
<div class="col-xs-6 col-md-4">
<label>Region:</label>
<span>{{regionName}}</span>
</div>
<div class="col-xs-6 col-md-4">
<label>Group:</label>
<span>{{groupName}}</span>
</div>
<div class="col-xs-6 col-md-4">
<label>MC:</label>
<span>{{mcName}}</span>
</div>
<div class="col-xs-6 col-md-4">
<label>District #:</label>
<span>{{districtNumber}}</span>
</div>
<div class="col-xs-6 col-md-4">
<label>Route #:</label>
<span>{{routeNumber}}</span>
</div>
<div class="col-xs-6 col-md-4">
<label>Week Ending Date:</label>
<span>{{weekEndDate}}</span>
</div>
<div class="col-xs-6 col-md-4">
<label>RSR:</label>
<span style="text-transform: capitalize;">{{rsrName}}</span>
</div>
</div>
Any suggestions to help me accomplish this? I am new this angular js. Thanks!
UPDATE
Printing the data with color and organized V
This is what opens the new tab when you click the printerFriendly button. (rollup.js)
app.controller('Rollup', function($scope, $rootScope, $http, $uibModal, headersvc, locFiltersvc) {
$scope.printDiv = function(divName) {
var topWrapper = "<div class='panel panel-default'><div class='panel-body'>";
var bottomWrapper="</div></div>";
var printContents = document.getElementById(divName).innerHTML;
var links = $(document).find('link');
var scripts = $(document).find('script')
var styles = "";
for (var i = 0; i < links.length; i++) {
styles += links[i].outerHTML;
}
var popupWin = window.open('', '_blank');
popupWin.document.open();
popupWin.document.write('<html><head>' + styles + '</head><body>' + printContents + '</body></html>');
}
}
I want the print preview to contain the color as seen in the main table
You should have a variable 'test' in the dependencies of PrintViewCtrl.
app.controller('PrintViewCtrl', function($scope, $http, $rootScope, $uibModalInstance, test)
Moreover you must have a test variable in the $scope of the "Rollup" controller. When you set "printDivModal('rollup-tab', test)" for the click, printDivModal and test are searched in the scope.
EDIT
test can be an object like
$scope.test = {regionName:..., mcName:..., etc...}
And then in your html use
{{test.regionName}} instead of {{regionName}}
for example.
I have an angular app that consists of a three tabbed form. Each tab is using its own controller and designated service.
When the submit button is pressed on the form, the function found in MainCtrl entitled $scope.postis suppose to post all form field data that was retrieved from each form fields ng-model.
On the server side, I receive the posted object that is formed in the Main Controller. This POST contains all the values for each object property except for work. I receive an empty object as a value for the work property.
Why am I unable to retrieve the work.comments ng-model found in the code for my template? Even when console loggin, I am unable to retrieve this item.
This is my first go at an Angular app and I am really trying to learn how to do things properly. If you happen to notice something inherently stupid, please do let me know.
Controller:
.controller('WorkCtrl', function ($scope, $ionicModal, TaskService, WorkService) {
$scope.work = {};
WorkService.add($scope.work);
});
Service:
.factory('WorkService', function() {
var work = {};
return {
add: function(data) {
work['work'] = data;
//work = data;
},
list: function() {
return work;
}
};
})
Template:
<div class="list" ng-controller="WorkCtrl">
<label class="item item-input item-floating-label">
<button class="button button-clear item-icon-right" ng-click="newTask()">New Task<i class="icon ion-ios-plus-outline"></i></button>
</label>
<label class="item item-input item-floating-label">
<span class="input-label">Comments</span>
<textarea placeholder="Comments" rows="6" ng-model="work.comments"></textarea>
</label>{{work | json}}
</div>
<div ng-controller="TaskCtrl">
<div ng-show="ifTasks()">
<div class="row header">
<div class="col tableRow">Task</div>
<div class="col tableRow">Edit</div>
<div class="col tableRow">Remove</div>
</div>
<div class="row" ng-repeat="task in tasks track by task.id">
<div class="col tableRow">{{task.choice}}</div>
<div class="col tableRow" ng-controller="WorkCtrl"><button class="button button-small button-balanced" ng-click="editTask({{task.id}})">Edit</button></div>
<div class="col tableRow"><button class="button button-small button-assertive" ng-click="del({{task.id}})">Delete</button></div>
</div>
</div>
</div>
Main Controller:
.controller('MainCtrl', function($scope, $http, SiteService, TaskService, WorkService, LabourService) {
$scope.toFetch = [];
var obj = {
site: SiteService.list(),
tasks: TaskService.list(),
work: WorkService.list(),
labour: LabourService.list()
};
$scope.post = function() {
console.log(obj);
$http({
url: 'http://localhost:1818/send/report',
method: 'POST',
data: obj,
headers: {'Content-Type': 'application/json'}
})
.then(function(data) {
console.log(data);
});
};
});
I have a custom directive,and also have a controller to bind the data to directive.
I get the data from the server part and bind it to directive.However,I found the data of directive on page do not update when I change scope variable
Here is my code
directive:
angular.module('MyApp')
.directive('stats',function() {
return {
templateUrl:'scripts/directives/dashboard/stats/stats.html',
restrict:'E',
replace:true,
scope: {
'comments': '#',
'number': '#',
'name': '#',
'colour': '#',
'details':'#',
'type':'#',
'goto':'#'
},
link : function($scope,element,attr){
$scope.$watch('number', function(oldValue,newValue) {
console.log(attr);
}, true);
}
}
});
directive template:
<div class="col-lg-3 col-md-6">
<div class="panel panel-{{colour}}">
<div class="panel-heading">
<div class="row">
<div class="col-xs-3">
<i class="fa fa-{{type}} fa-5x"></i>
</div>
<div class="col-xs-9 text-right">
<div class="huge">{{number}}</div>
<div>{{comments}}</div>
</div>
</div>
</div>
<a href="{{goto}}">
<div class="panel-footer">
<span class="pull-left">查看详情</span>
<span class="pull-right"><i class="fa fa-arrow-circle-right"></i></span>
<div class="clearfix"></div>
</div>
</a>
</div>
controller:
'use strict';
angular.module('MyApp',['ngResource'])
.controller('MainCtrl', function($scope,$state,MyService) {
$scope.result = {};
var names = MyService.get({classtype:'getNames',start:'',end:''},function(){
$scope.pages = names.data;
if (typeof($scope.pages[0]) === 'undefined'){
$scope.selectedItem = 'loading...';
}else{
$scope.selectedItem = $scope.pages[0].name;
}
var res = MyService.get({classtype:'getLastRes',seriesName:$scope.selectedItem},function(){
$scope.result = res;
});
});
$scope.dropboxitemselected = function(item){
$scope.selectedItem = item;
var result = MyService.get({classtype:'getLastRes',seriesName:item},function(){
$scope.result = result;
});
//$scope.result = {};
};
});
HTML:
<div class="row" ng-controller="MainCtrl">
<stats number="{{result.score}}" comments="score" colour="primary" type="heartbeat" goto="#/res/{{result._id}}"></stats>
<stats number="{{result.totalSize}}" comments="size" colour="primary" type="file-code-o" goto="#/res/{{result._id}}"></stats>
<stats number="{{result.count}}" comments="count" colour="red" type="file-text" goto="#/res/{{result._id}}"></stats>
</div>
there is a dropdown box on my page,I need refresh data in directive when I change item by function dropboxitemselected in controller,How can I do?
I think it is because of the scope bindings, you should use '=' for two way binding instead of '#'.
scope: {
'number': '=',
},
And in your HTML remove the brackets from number.
<stats number="result.score" comments="score" colour="primary" type="heartbeat" goto="#/res/{{result._id}}"></stats>
I just started learning so I'm creating a todo app.
I'm trying to show a div when I click on the edit button to edit my task.
this is my html
<div ng-controller='TasksCtrl'>
<div ng-repeat='(key, task) in tasksList' class='task-list'>
<div class='easy'>
<div class='div-list-style'></div>
<div id='task-{{key}}' style='cursor:pointer;z-index:5'
ng-click='editTask()' data-key='{{key}}' class='options
pull-right glyphicon glyphicon-pencil'>
</div>
<div class='task-desc' ng-bind='task.description'></div>
<div ng-hide='taskEdit = true'>FORM</div>
</div>
</div>
</div
This is my controller
todoApp.controller('TasksCtrl',['$scope', 'saveTaskService', function($scope, saveTaskService){
$scope.editTask= function(){
todoApp.directive('taskEdit', function(){
return function(scope, element){
//so I guess over here I need do ngHide = 'false' ? //
alert(element.attr('data-key'));
};
});
};
}]);
Here is a quick solution, you have a scope variable called taskShow that will tell ng-hide when to show it, then on ng-click you will trigger a function that will toggle that value:
<div ng-controller='TasksCtrl'>
<div ng-repeat='(key, task) in tasksList' class='task-list'>
<div class='easy'>
<div class='div-list-style'></div>
<div id='task-{{key}}' style='cursor:pointer;z-index:5'
ng-click='toggleHide(key)' data-key='{{key}}' class='options
pull-right glyphicon glyphicon-pencil'>
</div>
<div class='task-desc' ng-bind='task.description'></div>
<div ng-show='taskShow[key]'>FORM</div>
</div>
</div>
</div>
Controller:
todoApp.controller('TasksCtrl',['$scope', 'saveTaskService', function($scope, saveTaskService){
$scope.taskShow = {};
$scope.toggleHide = function (key) {
$scope.taskShow[key] = !$scope.taskShow[key];
};
}]);
Edit: switched ng-hide to ng-show so the divs are hidden by default and only shown when the other is clicked.