I am building a notecard application and for some reason my ng-change is not firing at all. I cannot figure out what the issue is. I tried breakpoints in the JS to verify that it is not actually firing. I'm positive it's got to be a something small I missed. I just need a second pair of eyes on it.
Here is the JS
var app = angular.module('catalyst', ['faye']);
app.factory('Faye', [
'$faye', function($faye) {
return $faye("http://localhost:9292/faye");
}
]);
app.directive('stickyNote', function(Faye) {
var linker = function(scope, element, attrs) {
element.draggable({
stop: function(event, ui) {
Faye.publish('/ui/board', {
id: scope.note.id,
x: ui.position.left,
y: ui.position.top
});
}
});
Faye.subscribe('/ui/board', function(data) {
// Update if the same note
if(data.id == scope.note.id) {
element.animate({
left: data.x,
top: data.y
});
}
});
// Some DOM initiation to make it nice
element.css('left', '10px');
element.css('top', '50px');
element.hide().fadeIn();
};
var controller = function($scope) {
// Incoming
Faye.subscribe('/ui/board', function(data) {
// Update if the same note
if(data.id == $scope.note.id) {
$scope.note.title = data.title;
$scope.note.body = data.body;
}
});
// Outgoing
$scope.updateNote = function(note) {
Faye.publish('/ui/board', note);
};
$scope.deleteNote = function(id) {
$scope.ondelete({
id: id
});
};
};
return {
restrict: 'A',
link: linker,
controller: controller,
scope: {
note: '=',
ondelete: '&'
}
};
});
app.controller('MainCtrl', function($scope, Faye) {
$scope.notes = [];
// Incoming
Faye.subscribe('/ui/board', function(data) {
$scope.notes.push(data);
});
Faye.subscribe('/ui/board', function(data) {
$scope.handleDeletedNoted(data.id);
});
// Outgoing
$scope.createNote = function() {
var note = {
id: new Date().getTime(),
title: 'New Note',
body: 'Pending'
};
$scope.notes.push(note);
Faye.publish('/ui/board', note);
};
$scope.deleteNote = function(id) {
$scope.handleDeletedNoted(id);
Faye.publish('/ui/board', {id: id});
};
$scope.handleDeletedNoted = function(id) {
var oldNotes = $scope.notes,
newNotes = [];
angular.forEach(oldNotes, function(note) {
if(note.id != id) newNotes.push(note);
});
$scope.notes = newNotes;
}
});
Here is the ui/board.html.haml
%body{"ng-controller" => "MainCtrl"}
%nav.top-bar{"data-topbar" => ""}
%ul.title-area
%li.name
%h1
%a{:href => "#"} AngularJS CollabBoard
%li.toggle-topbar.menu-icon
%a{:href => "#"}
%span Menu
%section.top-bar-section
%ul.right
%li
%a#createButton{"ng-click" => "createNote()"} Create Note
.alert-box.success.radius.sticky-note{"ng-repeat" => "note in notes track by $index", :note => "note", :ondelete => "deleteNote(id)", "sticky-note" => ""}
%button.close{"ng-click" => "deleteNote(note.id)", :type => "button"} ×
%input.title{"ng-change" => "updateNote(note)", "ng-model" => "note.title", :type => "text"}
%textarea.body{"ng-change" => "updateNote(note)", "ng-model" => "note.body"} {{note.body}}
There is a lot to look at here, I believe you have scoping issue. For one, you are not using transclude on your directive and therefore your child elements would not be included in your compiled directive. I noticed also that you have deleteNote on your main controller and are delegating upwords to the maincontroller but then put updateNote on just the directive. I imagine your delete is working. You are using ng-repeat which does create a child scope for each "note".
Related
I am new to AngularJS. I have created the following controller that display a list of results and that opens a modal when a specific button is clicked:
angular.
module('panelList')
.component('panelList', {
templateUrl: '/panel-list/panel-list.template.html',
controller: ['Panel', 'PanelSelection', '$scope', '$location', '$uibModal',
function PanelListController(Panel, PanelSelection, $scope, $location, $uibModal) {
$scope.maxAbv = 2;
$scope.minAbv = 12;
$scope.maxIbu = 0;
$scope.minIbu = 100;
this.allPanelsRetrieved = (index, before, filterParams) => {
let allPanels = before;
const params = Object.assign({},
{ page: index, per_page: 80 },
filterParams);
Panel.query(params).$promise.then(data => {
if (data.length > 0) {
allPanels.push(...data);
return this.allPanelsRetrieved(index+1, allPanels, filterParams);
} else {
return allPanels;
}
});
return allPanels;
};
$scope.getPanels = () => {
const filterParams = {};
filterParams.abv_lt = $scope.minAbv;
filterParams.abv_gt = $scope.maxAbv;
filterParams.ibu_lt = $scope.minIbu;
filterParams.ibu_gt = $scope.maxIbu;
$scope.currentPagePanels = this.allPanelsRetrieved(1,[], filterParams);
};
$scope.showDetails = (panelSelected) => {
PanelSelection.setPanelSelected(panelSelected);
$uibModal.open({
component: "panelDetail",
scope: $scope,
bindToController: true,
})
};
}]
});
The controller for the modal is specified here:
angular.
module('panelDetail').
component('panelDetail', {
templateUrl: '/panel-detail/panel-detail.template.html',
controller: ['PanelSelection', '$scope','$uibModal',
function PanelDetailController(PanelSelection, $scope, $uibModal, $uibModalInstance) {
$scope.ok = () => {
$uibModalInstance.close();
};
let panelSelected = PanelSelection.getPanelSelected();
$scope.panel = panelSelected;
console.log(panelSelected);
$scope.foodPairings = panelSelected.food_pairing.join(", ");
$scope.allIngredients = this.getFormattedIngredients(panelSelected.ingredients);
$scope.method = this.getFormattedMethod(panelSelected.method);
this.getFormattedIngredients = (ingredients) => {
const listOfIngredients = [];
Object.keys(ingredients).forEach(key => {
if(Array.isArray(ingredients[key])){
for(let ingredient of ingredients[key]){
listOfIngredients.push(
`- ${ingredient.name} ${key} (${ingredient.amount.value} ${ingredient.amount.unit})`
.concat(ingredient.add != undefined ? ', added in the '+ingredient.add:'',
ingredient.attribute != undefined ? ', attribute: '+ingredient.attribute:'','.')
);
}
}else{
listOfIngredients.push(`- ${ingredients[key]} ${key}.`);
}
});
return listOfIngredients;
};
$scope.getFormattedMethod = (method) => {
const listOfMethodProcedures = [];
Object.keys(method).forEach(key => {
if(Array.isArray(method[key])){
for(let methodProcedure of method[key]){
listOfMethodProcedures.push(
`- ${key} at ${methodProcedure.temp.value} ${methodProcedure.temp.unit} `
.concat(methodProcedure.duration != undefined ? 'for '+methodProcedure.duration +' min.' : '.')
);
}
}else{
listOfMethodProcedures.push(`- ${key}.`);
}
});
return listOfMethodProcedures;
};
}
]
});
The modal is open correctly but the values inside are not taken from the scope, as they should, but they are displayed as {{value}}. In few words, the $scope passed doesn't act as scope. Moreover I get the following error:
TypeError: this.getFormattedIngredients is not a function
at new PanelDetailController
Where the error may be? How to pass successfully a scope from one controller to another for modal?
Instead of scope: $scope pass values using
resolve: { scope: $scope }
You are calling the getFormattedIngredients function before it gets declared. So this is not a $scope issue. You need to declare the function before it gets called. One way to solve such an issue is going with the angular Styleguide provided by John Papa. Angular Styleguide
Assign your function at the top of your Component/Controller/Service and use function expressions instead of function declarations.
function PanelDetailController(PanelSelection, $scope, $uibModal,$uibModalInstance) {
this.getFormattedIngredients = getFormattedIngredients;
// You can call your function from here without getting an error
// Other Code..
function getFormattedIngredients() {}
I have two modules "core" and "ui".
The ui module depends on core. This is the code for my core.js :
var core = angular.module('core', [ 'ngRoute' ]);
//Services
core.service('httpInformationService', function() {
this.requestCount = 0;
this.responseCount = 0;
this.incrementRequest = function() {
this.requestCount++;
console.log('incrementRequest:' + this.requestCount);
};
this.incrementReponse = function() {
this.responseCount++;
}
this.decrementRequest = function() {
this.requestCount--;
console.log('decrementRequest:' + this.requestCount);
};
this.decrementResponse = function() {
responseCount--;
}
this.getRequestCount = function() {
return requestCount;
}
this.getResponseCount = function() {
return responseCount;
}
});
//Service provider
core.provider("httpServiceInformationProvider", function() {
var provider = {};
provider.$get = ['httpInformationService', function( service ) {
return service;
}];
return provider;
});
//HTTP Interceptor
core.factory('coreHttpInterceptor' ,function( httpInformationService ){
var coreHttpInterceptor = {
request: function(config) {
httpInformationService.incrementRequest();
return config;
},
response: function(response) {
httpInformationService.decrementRequest();
return response;
}
}
return coreHttpInterceptor;
});
var config = {
base_url: enviromnent_url,
}
core.value('config', config);
core.config(function( $interpolateProvider ) {
$interpolateProvider.startSymbol( "[[" ).endSymbol( "]]" );
});
core.config(function( $httpProvider ) {
$httpProvider.interceptors.push('coreHttpInterceptor');
});
This is my ui.js code:
var ui = angular.module('ui',[ 'core' , 'ui.bootstrap' ]);
ui.directive( "shLoadify" , function( httpServiceInformationProvider ){
return {
restrict: "AE",
link: function(scope, element, attrs) {
element.bind( "click", function() {
element.text("Loading...");
element.prop( "disabled", true );
});
},
controller: function($scope) {
$scope.$watch('httpServiceInformationProvider', function(oldValue, newValue){
console.log(oldValue + ' ' + newValue);
}, true);
}
}
});
As you can see i am trying to access requestCount property of httpInfomationService from within my controller using $scope.watch.
The problem is newValue and oldValue is always null. Why is that so?
Approach 1
If you want to perform some action whenever your requestCount variable gets changed which is part of service, you need to broadcast/emit which then you can listen through on. But in this case you need to pass the scope in your service which is not recommended.
var app = angular.module('app',['app1']);
app.service('myService',function($rootScope){
this.requestCount=1
this.incrementRequestCount=function(){
this.requestCount++
$rootScope.$broadcast('requestCountChanged', { message: this.requestCount });
}.bind(this)
})
app.controller('myController',['$scope','myService',function($scope,myService){
$scope.$on('requestCountChanged', function(event, args) {
// You will find the updated requestCount in args
});
$scope.click= myService.incrementRequestCount;
}])
var app1 = angular.module('app1',[]);
app1.controller('mySecondController',['$scope','myService',function($scope,myService){
$scope.$on('requestCountChanged', function(event, args) {
// You will find the updated requestCount in args
});
}])
Approach 2
Without passing scope in the service
var app = angular.module('app',['app1']);
app.service('myService',function(){
this.requestCount=1
this.incrementRequestCount=function(){
debugger;
this.requestCount++
}.bind(this)
})
app.controller('myController',['$scope','myService','$rootScope',function($scope,myService,$rootScope){
$scope.click=function(){
myService.incrementRequestCount();
$rootScope.$broadcast('requestCountChanged', { message: myService.requestCount });
}
}])
var app1 = angular.module('app1',[]);
app1.controller('mySecondController',['$scope','myService',function($scope,myService){
$scope.$on('requestCountChanged', function(event, args) {
// You will find the updated requestCount in args
});
}])
Approach 3
You can only attach watch to those properties which are actually in the scope otherwise you cannot have watch for those properties. So just add requestCount on you scope than you can easily detect its changes using watch and then use broadcast/emit approach.
var app = angular.module('app',['app1']);
app.service('myService',function(){
this.requestCount=1
this.incrementRequestCount=function(){
debugger;
this.requestCount++
}.bind(this)
})
app.controller('myController',['$scope','myService','$rootScope',function($scope,myService,$rootScope){
$scope.requestCount=myService.requestCount
$scope.$watch('requestCount',function(n,o){
debugger;
if(n!=o)
{
$rootScope.$broadcast('requestCountChanged', { message: n });
}
})
$scope.click=function(){
myService.incrementRequestCount();
$scope.requestCount=myService.requestCount
}
}])
var app1 = angular.module('app1',[]);
app1.controller('mySecondController',['$scope','myService',function($scope,myService){
$scope.$on('requestCountChanged', function(event, args) {
// You will find the updated requestCount in args
});
}])
I'm working on a angular fullstack project which uses Babel and Angular 1.5.0.
The issue is that when I'm constructing an array (this.events = []) I cannot target this array on $onInit() where I'm supposed to populate data to be displayed on ui-calendar. I do this on this.awesomeTesting = [];
So I need to populate this.events[] array with data from $http.get('/api/calendars/') inside $onInit() but I don't understand why I cannot target the array so I can populate the data.
What am I doing wrong?
Here is my code:
'use strict';
(function() {
class MainController {
constructor($http, $scope, socket, uiCalendarConfig) {
this.$http = $http;
this.socket = socket;
this.awesomeThings = [];
this.awesomeTesting = [];
this.events = [];
this.events.splice(0, this.events.length);
this.eventSources = [this.events];
console.log(this.eventSources);
/* config object */
$scope.uiConfig = {
calendar:{
height: 450,
editable: true,
header:{
left: 'month',
center: 'title',
right: 'today prev,next'
},
dayClick: $scope.alertEventOnClick,
eventDrop: $scope.alertOnDrop,
eventResize: $scope.alertOnResize
}
};
$scope.$on('$destroy', function() {
socket.unsyncUpdates('thing');
})
}
$onInit() {
this.$http.get('/api/things').then(response => {
this.awesomeThings = response.data;
this.socket.syncUpdates('thing', this.awesomeThings);
});
this.$http.get('/api/calendars').then(response => {
this.awesomeTesting = response.data;
this.awesomeTesting.forEach(function (objectItem) {
console.log('displays property in each object: ', objectItem.title);
this.events.push({
title: objectItem.title,
start: objectItem.start
});
});
this.socket.syncUpdates('calendar', this.awesomeTesting);
});
}
addThing() {
if (this.newThing) {
this.$http.post('/api/things', { name: this.newThing });
this.newThing = '';
}
}
deleteThing(thing) {
this.$http.delete('/api/things/' + thing._id);
}
}
angular.module('myApp')
.component('main', {
templateUrl: 'app/main/main.html',
controller: MainController
});
})();
I fixed the issue by making a for loop instead of .forEach() but I still would like to know the answer how it could be done with .forEach()?
The solution with for loop:
for (var i = 0; i < this.awesomeTesting.length; i++) {
this.events.push({
title: this.awesomeTesting[i].title,
start: this.awesomeTesting[i].start
});
}
Try to remove this.events.splice(0, this.events.length);
hope this helps.
Try This:
var that = this;
and than replace this.events -> that.events
`$onInit() {
*var that = this;*
this.$http.get('/api/things').then(response => {
this.awesomeThings = response.data;
this.socket.syncUpdates('thing', this.awesomeThings);
});
this.$http.get('/api/calendars').then(response => {
this.awesomeTesting = response.data;
this.awesomeTesting.forEach(function (objectItem) {
console.log('displays property in each object: ', objectItem.title);
*that.events.*push({
title: objectItem.title,
start: objectItem.start
});
});
this.socket.syncUpdates('calendar', this.awesomeTesting);
});
}`
I'm trying to send an event when an item gets selected, from directive to controller using $emit. I've two update functions for organizations and another for people. My directive should specify which event should emit.
Here is my update functions:
// For organization
$scope.updateOrgs = function(selectedVal) {
}
// For people
$scope.updatepeople = function(selectedVal, type) {
}
When it is people my directive should raise an emit event for updatepeople (), if it was org it should raise updateorg().
My directive looks like:
.directive('search', function ($timeout) {
return {
restrict: 'AEC',
scope: {
model: '=',
searchobj: '#',
},
link: function (scope, elem, attrs, index) {
scope.handleSelection = function (selectedItem) {
scope.model = selectedItem;
scope.searchModel="";
scope.current = 0;
scope.selected = true;
$timeout(function () {
scope.onSelectupdate();
}, 200);
};
scope.Delete = function (index) {
scope.selectedIndex = index;
scope.delete({ index: index });
};
scope.Search = function (searchitem,event,searchobj) {
// alert('item entered'+name)
scope.searching = searchitem;
scope.searchobject = searchobj;
scope.onSearch({ searchitem: searchitem , searchobj:searchobj});
};
scope.current = 0;
scope.selected = true;
scope.isCurrent = function (index) {
return scope.current == index;
};
scope.setCurrent = function (index) {
scope.current = index;
};
},
controller: ['$scope','$element','$rootScope','SearchOrg', function($scope,$element,$rootScope,SearchOrg) {
$scope.searchItem = function(filter,searchobj){
//alert('search'+searchobj);
SearchOrg().fetch({'filter': filter, 'searchType': searchobj}).$promise.then(function(value){
$scope.searchData = value.data;
console.info($scope.searchData);
},
function(err) {
});
}
}],
templateUrl: TAPPLENT_CONFIG.HTML_ENDPOINT[0] + 'home/genericsearch.html'
}
});;
HTML snippet
<search searchobj=“tei-org” selectedItems=“arrayofIds” search-id=”someidtoIdentify”/>
How can I do this both functions are in different controllers, and also I need to send parameters from directive to the controller using $emit?
Working with $scope.$emit and $scope.$on
I'm guessing that your other controllers are not parents, so look at the second option using $broadcast.
var app = angular.module('app', []);
app.controller('firstController', function($scope) {
$scope.selectedOrgs = []
$scope.$on('updateorgs', function(evt, data) {
$scope.selectedOrgs.push(data);
});
});
app.controller('secondController', function($scope) {
$scope.selectedPeople = []
$scope.$on('updatepeople', function(evt, data) {
$scope.selectedPeople.push(data);
});
});
app.directive('someDirective', function($rootScope) {
return {
scope: {},
link: function(scope) {
scope.options = [{
id: 1,
label: 'org a',
type: 'org'
}, {
id: 2,
label: 'org b',
type: 'org'
}, {
id: 3,
label: 'person a',
type: 'person'
}, {
id: 4,
label: 'person b',
type: 'person'
}];
scope.changed = function() {
if (scope.selected) {
var updatetype = scope.selected.type;
if (updatetype === 'person') {
$rootScope.$broadcast('updatepeople', scope.selected);
} else if (updatetype === 'org') {
$rootScope.$broadcast('updateorgs', scope.selected);
}
}
};
},
template: '<select ng-change="changed()" ng-model="selected" ng-options="option.label for option in options"><option value="">Select</option></select>'
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular.min.js"></script>
<div ng-app='app'>
<some-directive></some-directive>
<div ng-controller='firstController'>
<div>ORGS:</div>
<div>
{{ selectedOrgs }}
</div>
</div>
<div ng-controller='secondController'>
<div>PEOPLE:</div>
<div>
{{ selectedPeople }}
</div>
</div>
</div>
I have a widget like directive called waComments, it loads components via a RESTful service and displays them. In my view I'm using ng-repeat to loop over them and to render them with a button that if pressed Shows a new reply to form. This his handled by the waCommentsReply directive. One waComments widget has many child directives of type waCommentsReply. When the form is filled and submitted I want to add the new comment on top of my comments list. So both directives have to share the comments data.
I've tried to implement this here Sharing data between directives but without much success, the comment data is not updated when I add a new comment. I see that the RESTful API calls work and the data is returned, so this is not an issue.
Why is my implementation of Sharing data between directives not working in my case?
waCommentsReply directive:
waFrontend.directive('waCommentsReply', ['$rootScope', 'Comment', 'WaFormValidation', 'WaCommentStore', function($rootScope, Comment, WaFormValidation, WaCommentStore) {
return {
restrict: 'E',
templateUrl: '/stubs/comment-form.html',
transclude: true,
scope: {
replyTo: '#replyTo',
replyFormList: '=replyFormList',
loggedIn: '#loggedIn',
model: '#model',
id: '#id',
cancelButton: '#cancelButton'
},
controller: function($scope) {
$scope.comments = WaCommentStore;
if ($scope.cancelButton == undefined) {
$scope.cancelButton = true;
} else {
$scope.cancelButton = false;
}
$scope.comment = $scope.commentForm = {
Comment: {
author_name: '',
body: '',
model: $scope.model,
foreign_key: $scope.id,
parent_id: $scope.replyTo
}
};
$scope.$watch('replyFormList', function (newValue, oldValue) {
if (newValue) {
$scope.replyFormList = newValue;
}
});
if ($scope.loggedIn == undefined) {
$scope.loggedIn = false;
}
/**
* Handles the submission and response of a reply
*
* #return void
*/
$scope.reply = function() {
Comment.add($scope.comment).then(function(result) {
if (result.status == 'fail' || result.validation != undefined) {
$scope.validationErrors = result.validation;
WaFormValidation.validate(result.validation, $scope.commentForm);
} else if (result.status == 'success') {
//$scope.$parent.comments.unshift(result.data.comment);
//$scope.comments.unshift(result.data.comment);
$scope.comments.comments.unshift(result.data.comment);
//WaCommentStore.append($scope.model, $scope.id, result.data.comment);
$scope.comments, $scope.id, result.data.comment
$scope.comment = {};
$scope.replyFormList[$scope.replyTo] = false;
}
});
};
$scope.close = function() {
$scope.comment = {};
if ($scope.replyFormList[$scope.replyTo] != undefined) {
$scope.replyFormList[$scope.replyTo] = false;
}
}
}
};
}]);
WaCommentStore directive:
waFrontend.factory('WaCommentStore', function() {
return {
comments: []
};
});
waComments directive:
waFrontend.directive('waComments', ['$rootScope', 'Comment', 'WaCommentStore', function($rootScope, Comment, WaCommentStore) {
return {
restrict: 'E',
templateUrl: '/stubs/comments.html',
scope: {
model: '#commentModel',
id: '#commentFk'
},
controller: function($scope) {
$scope.comments = WaCommentStore;
$scope.loaded = false;
$scope.loadedMore = true;
$scope.currentPage = 1;
$scope.loggedIn = false;
$scope.paging = {};
$scope.replyFormList = {};
Comment.comments($scope.model, $scope.id).then(function(result) {
$scope.comments.comments.push.apply($scope.comments.comments, result.data.comments);
$scope.loggedIn = result.data.loggedIn;
$scope.paging = result.paging.Comment;
$scope.loaded = true;
});
$scope.loadMore = function() {
$scope.loadedMore = false;
if ($scope.paging.nextPage == false) {
//return false;
}
var options = {
page: $scope.paging.page + 1
};
Comment.comments($scope.model, $scope.id, options).then(function(result) {
$scope.comments.comments.push.apply($scope.comments.comments, result.data.comments);
$scope.paging = result.paging.Comment;
$scope.loadedMore = true;
});
};
$scope.submitComment = function() {
//alert($scope.author_name + $scope.body);
};
$scope.reply = function(replyId) {
$scope.replyFormList[replyId] = true;
}
}
};
}]);
since in both directive you defined scope: {} basically it means you defined those directives to use isolated scope.
with isolated scope, a scope/directive can't see what is in the parent scope.
however parent scope, can be affected by the child scope changes with 2 way binding definition.
https://docs.angularjs.org/guide/scope
try changing the shared data like this
waFrontend.factory('WaCommentStore', function() {
var comments = [];
var getComments = function() { return comments; }
var setComments = function(data) { comments = data; }
return {
getComments : getComments ,
setComments : setComments
};
});
I wanted to put it as a comments, but it would have been difficult to understand for you.
Please let me know if this works, else I will delete this answer.