Angular service not storing data between two controllers - html

I am trying to use a service to set title in controller1 and then access title in controller2.
sharedProperties.setTitle(title) works in controller1, but when I try to get the title in controller2, it gets "title" (the initial value) instead of the new value.
I've also tried storing title in an object but it didn't work.
app.service('sharedProperties', function () {
var title = "title"
return {
getTitle: function () {
return title;
},
setTitle: function (val) {
title = val;
}
}
});
app.controller('controller1', ['$scope', 'sharedProperties', function ($scope, sharedProperties) {
$('body').on("click", "button[name=btnListItem]", function () {
// gets the title
var title = $(this).text();
// sets the title for storage in a service
sharedProperties.setTitle(title);
});
}]);
app.controller('controller2', ['$scope', 'sharedProperties', function ($scope, sharedProperties) {
$scope.sharedTitle = function() {
return sharedProperties.getTitle();
};
}]);
And in my view, I have {{ sharedTitle() }} which should, as I understand it, update the title text with the new title.
Also, in case this is relevant: the two controllers are linked to two different html pages.
What am I doing wrong?
EDIT
Updated button listener:
$('body').on("click", "button[name=btnListItem]", function () {
// gets the text of the button (title)
var title = $(this).text();
sharedTitle(title);
alert(sharedProperties.getTitle());
document.location.href = '/nextscreen.html';
});
$scope.sharedTitle = function (title) {
sharedProperties.setTitle(title);
};

It seems to be correct in your sample code. I setup jsfiddle and it seems work correctly. Finding out a difference between my jsfiddle and your actual code would help you to find the problem you should solve.
Javascript:
angular.module('testapp', [])
.service('sharedProperties', function(){
var title = 'title';
return {
getTitle: function(){
return title;
},
setTitle: function(val){
title = val;
}
};
})
.controller('controller1', function($scope, sharedProperties){
$scope.change_title = function(newvalue){
sharedProperties.setTitle(newvalue);
};
})
.controller('controller2', function($scope, sharedProperties){
$scope.sharedTitle = function(){
return sharedProperties.getTitle();
};
})
Html:
<div ng-app="testapp">
<div ng-controller="controller1">
<input ng-model="newvalue">
<button ng-click="change_title(newvalue)">Change Title</button>
</div>
<div ng-controller="controller2">
<span>{{sharedTitle()}}</span>
</div>
</div>
My jsfiddle is here.

You have to print console.log(sharedProperties.getTitle()); Dont need return from controller.
So your code of controller2 is $scope.sharedTitle = sharedProperties.getTitle();

You need to use the $apply so that angular can process changes made outside of the angular context (in this case changes made by jQuery).
$('body').on("click", "button[name=btnListItem]", function () {
// gets the title
var title = $(this).text();
// sets the title for storage in a service
$scope.$apply(function() {
sharedProperties.setTitle(title);
});
});
See plunker
That said, this is BAD PRACTICE because you're going against what angular is meant for. Check “Thinking in AngularJS” if I have a jQuery background?. There are cases when you need to use $apply like when integrating third party plugins but this is not one of those cases.

Related

What is the angular way for cloning buttons?

I have a follow button for a particular user that should change its text to followed after it's clicked and vice versa. This follow button can show up in different modules on the page. When it's clicked, the follow button for this particular users should update in all of these modules. However, the buttons are in different scopes. What is the angular way of making sure the cloned buttons are in the same state?
My current solution is to use an universal jQuery selector to update all the buttons on click.
You should store the state in a service.
example:
app.factory('SharedService', function() {
this.buttonState = null;
this.setButtonState= function(value) {
this.buttonState = value;
}
this.getButtonState= function() {
return this.buttonState ;
}
return this;
});
Read: AngularJS Docs on services
or check this Egghead.io video
You can use $rootScope.$broadcast to do this. when any of button gets clicked you fire an event using $rootScope.$broadcast and then listen to it using $scope.$on and toggle the status of buttons. and you can also update state inside the service too, so you can fetch current value later if needed.
See the below example:
var app = angular.module('app', []);
app.controller('ctrl1', function($scope) {
$scope.label1 = "First Button";
});
app.controller('ctrl2', function($scope) {
$scope.label2 = "Second Button";
});
app.controller('ctrl3', function($scope) {
$scope.label3 = "Third Button";
});
// updating state in service too.
app.service('fButtons', function($rootScope) {
var buttonState = false;
this.getCurrentState = function() {
return buttonState;
};
this.updateCurrentState = function() {
buttonState = !buttonState;
};
});
app.directive('followButton', function($rootScope, $timeout, fButtons) {
return {
restrict: 'E',
scope: {
label: '='
},
template: '<button ng-click="buttonClick()" ng-class="{red: active}">{{label}}</button>',
controller: function($scope) {
$scope.$on('button.toggled', function() {
$scope.active = !$scope.active;
});
$scope.buttonClick = function() {
fButtons.updateCurrentState();
$rootScope.$broadcast('button.toggled');
console.log(fButtons.getCurrentState());
}
}
};
});
.red {
background-color: red;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app">
<div ng-controller="ctrl1">
<follow-button label="label1"></follow-button>
</div>
<hr/>
<div ng-controller="ctrl2">
<follow-button label="label2"></follow-button>
</div>
<hr/>
<div ng-controller="ctrl3">
<follow-button label="label3"></follow-button>
</div>
</div>
see console for service state.
$broadcast docs

TinyMCE and AngularJS - not loading after NgSwitch

I hope I am clear enough with this request for assistance, as it is hard to explain and I can't post all the code here. I have downloaded code to enable TinyMCE to be used in a NgRepeat with AngularJS:
angular.module('ui.tinymce', [])
.value('uiTinymceConfig', {})
.directive('uiTinymce', ['uiTinymceConfig', function (uiTinymceConfig) {
uiTinymceConfig = uiTinymceConfig || {};
var generatedIds = 0;
return {
require: 'ngModel',
link: function (scope, elm, attrs, ngModel) {
var expression, options, tinyInstance;
// generate an ID if not present
if (!attrs.id) {
attrs.$set('id', 'uiTinymce' + generatedIds++);
}
options = {
// Update model when calling setContent (such as from the source editor popup)
setup: function (ed) {
ed.on('init', function (args) {
ngModel.$render();
});
// Update model on button click
ed.on('ExecCommand', function (e) {
ed.save();
ngModel.$setViewValue(elm.val());
if (!scope.$$phase) {
scope.$apply();
}
});
// Update model on keypress
ed.on('KeyUp', function (e) {
ed.save();
ngModel.$setViewValue(elm.val());
if (!scope.$$phase) {
scope.$apply();
}
});
},
mode: 'exact',
elements: attrs.id
};
if (attrs.uiTinymce) {
expression = scope.$eval(attrs.uiTinymce);
} else {
expression = {};
}
angular.extend(options, uiTinymceConfig, expression);
setTimeout(function () {
tinymce.init(options);
});
ngModel.$render = function () {
if (!tinyInstance) {
tinyInstance = tinymce.get(attrs.id);
}
if (tinyInstance) {
tinyInstance.setContent(ngModel.$viewValue || '');
}
};
}
};
}]);
var gwApp = angular.module('gwApp', ['ui.tinymce']);
I don't really understand this code, but it works fine initially. My page starts with a list of Posts. I click on 'Show Reply' for the first post, and using NgSwitch the multiple replies become visible (nested NgRepeat). I submit a new reply message (the reply text is entered using tinymce) using a RESTful API service and a http call (too much code to post here). Then after clicking the submit button for the new reply message, the NgSwitch kicks in again unexpectedly to make the replies no longer visible. When I expand the replies again, the tinymce is just a regular textarea again, and the proper editor is gone.
I know this is not very clear, but I'm hoping someone can make sense of what I've written and can help me solve this problem..
I was having the same problem using ng-switch and ng-show so i added:
scope.$watch('onHidden()',function(){ tinymce.editors = [] });
after the setTimeout function.
Also replace the
ed.on('init',function(args){ ngModel.$render(); });
with
ed.on('init',function(args){ ed.setContent(ngModel.$viewValue); });
and remove the $render function.
This is the link to the working code in JsFiddle

Adding an angularJS directive based on a parameter value

TL;DR: Is there a way to dynamically set a directive based on a parameter value? Something similar to ng-class for setting css elements, but a way to set the directive based on the value in the scope. I would have the value in the scope so I could call:
<div class="data.directiveType"></div>
When
data.directiveType = "my-directive"
the div would become
<div class="my-directive"></div>
and myDirective would be invoked?
Detailed Question:
What I am trying to do is allow the user to add elements to the web application and I wanted the directive for each element to be added based on what the user clicks.
I have the following Directives:
app.directive("mySuperman", function(){
//directive logic
});
app.directive("myBatman", function(){
//directive logic
});
app.directive("myWonderWoman", function(){
//directive logic
});
I have the following controller
app.controller("ParentCtrl", function($scope){
$scope.superHeros = [];
var superman = {directiveType: "my-superman"};
var batman = {directiveType: "my-batman"};
var wonderWoman = {directiveType: "my-wonder-woman"}
$scope.addBatman = function()
{
var batmanInstance = {}
angular.copy(batman, batmanInstance);
$scope.superHeros.push(batmanInstance);
}
$scope.addSuperman = function()
{
var supermanInstance = {}
angular.copy(superman, supermanInstance);
$scope.superHeros.push(supermanInstance);
}
$scope.addWonderWoman = function()
{
var wonderwomanInstance = {}
angular.copy(wonderWoman, wonderwomanInstance);
$scope.superHeros.push(wonderwomanInstance);
}
});
In the index.html I have
<body ng-controller="ParentCtrl>
<a ng-click="addBatman()">Add Batman</a>
<a ng-click="addSuperman()">Add Superman</a>
<a ng-click="addWonderWoman()">Add WonderWoman</a>
<div ng-repeat="hero in superHeros">
<!-- The following doesn't work, but it is the functionality I am trying to achieve -->
<div class={{hero.directiveType}}></div>
<div>
</body>
The other way I thought of doing this was just using ng-include in the ng-repeat and adding the template url to the hero object instead of the directive type, but I was hoping there was a cleaner way that I could make better use of the data binding and not have to call ng-include just to call another directive.
You can create a directive that takes the directive to add as a parameter, adds it to the element and compiles it. Then use it like this:
<div ng-repeat="hero in superHeros">
<div add-directive="hero.directiveType"></div>
</div>
Here is a basic example:
app.directive('addDirective', function($parse, $compile) {
return {
compile: function compile(tElement, tAttrs) {
var directiveGetter = $parse(tAttrs.addDirective);
return function postLink(scope, element) {
element.removeAttr('add-directive');
var directive = directiveGetter(scope);
element.attr(directive, '');
$compile(element)(scope);
};
}
};
});
Demo: http://plnkr.co/edit/N4WMe8IEg3LVxYkdjgAu?p=preview

AngularJS : get back data from a json array with an id

I have a json file where i am stocking informations from all the people in my database. I actually use it to display first name, last name in a web page and i want to add the possibility to display the details of every person.
To do so i'm using the id of the person like this :
.when('/people/:id', {templateUrl: 'partials/people-detail.html'})
It works pretty well, i have a page generated for every person, which is nice. But now I would like to get the information of the person back.
The easiest way would have been to have a json file for every person, but i don't particularly like the idea of having so much file.
So my actual idea is to iterate through the people.json file to find the good one and using it but it's not working.
Here's my controller :
var PeopleController = angular.module ('PeopleController', []);
PeopleController.controller('PeopleDetailCtrl', ['$scope', '$routeParams', '$http',
function($scope, $routeParams, $http) {
$scope.search = function() {
var url = 'data/people.json';
$http.get(url).success(httpSuccess).error(function() {
alert('Unable to get back informations :( ');
});
}
httpSuccess = function(response) {
$scope.persons = response;
}
function getById(arr, id) {
for (var d = 0, len = arr.length; d < len; d += 1) {
if (arr[d].id === id) {
return arr[d];
}
}
}
$scope.search();
$scope.person = getById($scope.persons,$routeParams.id);
}]);
Well, maybe my solution is bad since it doesn't work, but i didn't find another way to do so.
Now i'm all yours :)
Thanks for reading.
The problem is that your $scope.search method contains $http.get() which is asynchronous.
What that means is your next line (the one that sets $scope.person) executes before the json file has been read. As such, $scope.persons is empty at the time it is executed.
You can take advantage of the fact that $http.get() returns a chainable promise here.
So if you change your search() function to return that promise, you can then use then() to populate person when everything has been successful:
$scope.search = function() {
var url = 'data/people.json';
return $http.get(url).success(httpSuccess).error(function() {
alert('Unable to get back informations :( ');
});
}
(note the return statement).
Then change the person population to take advantage of this:
$scope.search().then(function(){
$scope.person = getById($scope.persons,$routeParams.id);
});
I hope getting a person is a whole different event like on click. You can try grep:
$scope.person = function(_id) {
return $.grep($scope.persons, function(item){
return item.id == _id
})[0];
}
Assuming you have all persons available otherwise this logic has to move inside the part of the success callback for the http call.
I used ECMAScript 5 filter to get person by id and moved your search by id to success method since we are dealing with ajax call.
Example:
app.controller('PeopleDetailCtrl', ['$scope', '$routeParams', '$http',
function($scope, $routeParams, $http) {
$scope.search = function() {
var url = 'data.json';
$http.get(url).success(httpSuccess).error(function() {
alert('Unable to get back informations :( ');
});
}
httpSuccess = function(response) {
$scope.persons = angular.fromJson(response);
$scope.person = $scope.persons.filter(function(item){
return item.id==routeParams.id //check for undefined;
});
}
$scope.search();
}]);
Live Example: http://plnkr.co/edit/jPT6aC5UqLdHGJ1Clfkg?p=preview

Twitter Bootstrap Row Filter / Search Box

I'm having trouble finding a tutorial on how to create a simple search query, or row filter, for Twitter Bootstrap. I've tried many, I'm not sure if I'm doing something wrong or the plugins are not compatible with Bootstrap. Please help if you can.
I've tried:
$(document).ready(function() {
//Declare the custom selector 'containsIgnoreCase'.
$.expr[':'].containsIgnoreCase = function(n,i,m){
return jQuery(n).text().toUpperCase().indexOf(m[3].toUpperCase())>=0;
};
$("#search").keyup(function(){
$("#tabela").find("tr").hide();
var data = this.value.split(" ");
var jo = $("#tabela").find("tr");
$.each(data, function(i, v){
//Use the new containsIgnoreCase function instead
jo = jo.filter("*:containsIgnoreCase('"+v+"')");
});
jo.show();
}).focus(function(){
this.value="";
$(this).css({"color":"black"});
$(this).unbind('focus');
}).css({"color":"#C0C0C0"});
});
Nothing with this... Maybe I'm missing any "id" on my table or search box, I'm new with this.
Here's what I use:
$('input.filter').live('keyup', function() {
var rex = new RegExp($(this).val(), 'i');
$('.searchable tr').hide();
$('.searchable tr').filter(function() {
return rex.test($(this).text());
}).show();
});
To use it, you just create a table, with a tbody with the class "searchable" and then an input with class "filter" somewhere on your page (I prefer to put them in a Bootstrap Popup behind a search icon).
This is live example of solution provided by Filipp Lepalaan. Many thanks for this small and perfect code.
Example
$(document).ready(function () {
(function ($) {
$('#filter').keyup(function () {
var rex = new RegExp($(this).val(), 'i');
$('.searchable tr').hide();
$('.searchable tr').filter(function () {
return rex.test($(this).text());
}).show();
})
}(jQuery));
});