for some reason unknown to me, I can't get the controller scope to update the view,
even though apply is running and the scope itself has been updated.
I know that because when I console.log(data) I see its been updated.
userList directive should assign data.activeUser property on controller's scope.
its working, but the view is not updating accordingly
Link to the demo: http://jsbin.com/mojemiki/6/edit
var app = angular.module('app', []);
app.controller('myCtrl', function ($scope) {
$scope.data = {
users : [{
name : 'bob'
},{
name : 'koko'
}],
activeUser : {}
};
$scope.selectUser = function (user) {
// console is showing user data
console.log(user);
$scope.data.activeUser = user;
console.log($scope.data.activeUser);
// data.activeUser has been updated, but the view is not. why is that?
// $scope.$apply() - is not helping
// because its aleady running
};
});
app.directive('userList', function () {
return {
scope : {
users : '=',
onSelect : '&'
},
template : '<h3>in directive scope</h3>' +
'<button ng-repeat="u in users" ng-click="onSelect({ user : u })">Set Active User: {{u.name}}</button>' +
'<br/><code>{{users}}</code>'
};
});
The problem is that you are including Angular.js twice (version 1.2.1 from Cloudflare CDN and version 1.2.14 from Google CDN) and this is causing some kind of conflict. Remove one of them and it will work.
<script src="http://cdnjs.cloudflare.com/ajax/libs/angular.js/1.2.1/angular.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.14/angular.js"></script>
Here is your fiddle.
Related
I am still learning angular and in my example projekt I have a problem on updating the view.
Got this in my header ....
<meta charset="UTF-8">
<title>{{ name }}</title>
And this in my body:
<body ng-controller="BodyController as body">
<input type="button" ng-click="changeTitle()" name="changeNameButton" value="change name"/>
This is my head controller:
myApp.controller('HeadController',
['$scope', 'ApplicationService', 'DataService', 'UserService', function ($scope, ApplicationService, DataService, UserService) {
var self = this;
$scope.name = ApplicationService.getTitle();
}]
);
And here is my body controller:
myApp.controller('BodyController', ['$scope', 'ApplicationService', function ($scope, ApplicationService) {
$scope.text = 'Hello, Angular fanatic.';
$scope.changeTitle = function () {
console.log('change the title');
ApplicationService.setTitle('test');
}
}]);
This is my application service
myApp.service('ApplicationService', ['ConfigurationService', function(ConfigurationService){
this.title = '';
this.setTitle = function (newTitle) {
console.log('new title (setter): ' + this.title);
this.title = newTitle
}
this.getTitle = function () {
if(this.title==''){
this.title = ConfigurationService.title + ' | ' + ConfigurationService.subtitle;
}
console.log('new title (getter): ' + this.title);
return this.title;
}
}]);
So far so good and sorry that I do not use codepen, etc. But it was not working in it, ...
My Problem: It is setting the title on initial load of the website, but not on pressing the button. The new name is set to ApplicationService.title, but header controller does not update it. Whats is wrong in this case? How can I update the title in the view...?
Regards
n00n
see the codepen for it: https://codepen.io/n00n/pen/bqaGKY
What you're doing is the equivalent of the following simple code:
//in the header controller
var name = service.getTitle();
// in the header template
display(name);
// later, in the body
service.setTitle('test');
// in the header template
display(name);
You see that this can't work: the variable name in the header controller has been initialized when the controller was created, and assigning a new value to the title stored in the service can't magically change the value of the name variable in the header controller. What you want is to display the title in the service:
<title>{{ getTitle() }}</title>
$scope.getTitle = function() {
return ApplicationService.getTitle();
};
That didn't work because you're calling getTitle method when title wasn't set. So that's it is referring to older title('undefined'). You can change your binding to
$scope.getTitle = ApplicationService.getTitle;
And then change HTML to
{{getTitle()}}
So title will get fetch from service and updated on the page on each digest cycle.
Other thing which I'd like to mention is, don't use(mix) $scope when you are using controllerAs, so then remove $scope from controller and bind data to below
var vm = this;
vm.getTitle = ApplicationService.getTitle;
It's two page user registeration process depending on the role the second page could be different but the first page will always remain the same. what I want I that user can go forward and backwards on both screens with persistent data. I trying a static page at start and then hide it and add the second template from route.
This is my angular app controller.
app.controller('addlandlordController' , function($scope , $http , $route ,API_URL , $routeParams , uploadService ){
$scope.API_URL = API_URL;
$scope.landVisible = true;
$scope.IsVisible = true;
if( $routeParams.test)
{
scope.$apply(function () {
$scope.IsVisible = false;
});
alert( $routeParams.test);
}
$scope.adduser = function($route){
var data = $.param({
fName: $scope.firstName,
lName: $scope.lastName,
role: 'landlord',
email: $scope.email,
linkId: $scope.linkId,
password: $scope.password,
});
var config = {
headers : {
'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8;'
}
}
$http.post(API_URL + 'user' , data , config)
.then(
function(response){
//$scope.IsVisible = false;
//alert('success');
},
function(response){
// failure callback
alert('fail');
}
);
}
});
I have a div in html like this,.
<div id="content" class="container1" ng-controller='addlandlordController' >
<div ng-show = "IsVisible">
And following is my route in config,.
app.config(function($routeProvider){
$routeProvider.when('/landlord' , {
templateUrl : 'template/addlandlord.html',
controller : 'addlandlordController',
resolve: {
test: function ($route) { $route.current.params.test = true; }
}
})
});
What I want is that when the user click on the following button.
Create an Account</button>
On click that button #/landlord will be added to the url and the route config code will run and add the other template in ng-view which is happening. Now next step is to hide the old div above in such a way that when user go back one sten only the previous code should show and when user goes again into the next screen only the next template should be visible and mean while data should remain same for the both views.
Issues I am facing is
Css is for template view is missing although the css files are already in the commen header. But appears when a place css in the style within template.
if I hide the first div in the response of adduser then if user go back it still hidden. it doesn't appears unless I refresh the page.
But if went to hide it through route config the value turn false but div never hides.
Please check this
scope.$apply(function () {
$scope.IsVisible = false;
});
You are using $apply on scope, but not in $scope.
And $applyAsync is preferable method to trigger digest without risking of error "$digest already in progress"
$applyAsync example:
$element.on('click', ()=>{
$scope.model.testValue = 'I have been updated not from angular circle';
$scope.$applyAsync();
});
Link to the docs
Nice article to read
I am creating an ionic project and I am trying to integrate with Algolia autocomplete.js. I managed to make the search system work, however I added a ng-click on my search results and this function is not working as presented in this codepen that I did as example below:
http://codepen.io/marcos_arata/pen/VKVOky
Inside my algolia's result template:
<a ng-click="add_name({{{ name }}})">
Function that should be run when clicked:
$scope.add_name = function(name) {
alert('User added!');
console.log(name);
}
I tried to inject the results inside the scope but didn't work as well:
autocomplete('#search_name', { hint: false, debug: true, openOnFocus: true },[{
source: index.ttAdapter({ hitsPerPage: 15 }),
templates: {
header: '',
suggestion: function(hit) {
$scope.hit = hit;
return template.render(hit);
}
}
}]);
http://codepen.io/marcos_arata/pen/VKVOky
---- SOLVED ----
Instead of creating a ng-click function inside your templates, you can handle the event click of your search inside your "autocomplete:selected" function and use the dataset and suggestion results.
.on('autocomplete:selected', function(event, suggestion, dataset) {
$scope.name = suggestion.name;
console.log($scope.name);
## create any functions with the suggestion and dataset results inside
});
EDITING THE ANSWER:
Here is the codepen:
Apparently the suggestion keep the name clicked, so you dont need an extra function:
.on('autocomplete:selected', function(event, suggestion, dataset) {
$scope.name = suggestion.name;
console.log($scope.name);
});
I need a bit of a code review, i'm having trouble getting my ng-change function to trigger and update the value in both controllers, i've created a factory service and have injected it into both controllers but on the second AppCtrl console.log() value prints only once during initialization, and would like to have the ng-change value also update on the second controller and not only on the first.
This is what i have so far:
<ion-radio ng-repeat="rate in rates"
ng-value="rate.id"
ng-change="rateTypeChanged(rate)"
ng-checked="rate.selected"
ng-model="currentRate.selectedRate">
{{ rate.title }}
</ion-radio>
controller for sidebar:
.controller('SidebarCtrl', function($scope, typesOfRates) {
$scope.rates = typesOfRates.rateType;
$scope.currentRate = {
selectedRate: 'hourly'
};
$scope.rateTypeChanged = function(rate) {
console.log("Selected goalType, text:", rate.title, "value:", rate.id);
typesOfRates.setRate(rate.id);
}
In controller 2:
.controller('AppCtrl', function($scope, typesOfRates, $state, $rootScope) {
console.log( typesOfRates.getRate() );
//runs only once, but not again when ng-change event is triggered
my service:
.factory('typesOfRates', function typesOfRates($rootScope) {
var typesOfRates = {};
typesOfRates.myRates = [];
typesOfRates.rateType = [
{ title: "Hourly", id: "hourly", selected: true },
{ title: "Daily", id: "daily", selected: false },
{ title: "Monthly", id: "monthly", selected: false }
];
typesOfRates.currentRate = "hourly";
var setRate = function(currentRate) {
if (typesOfRates.myRates.length > 0) typesOfRates.myRates = [];
typesOfRates.myRates.push(currentRate);
}
var getRate = function() {
return typesOfRates.myRates;
}
return {
rateType: typesOfRates.rateType,
getRate: getRate,
setRate: setRate
}
});
The way you are doing to achieve the objective seems bit out of the box. The second controller will be initialized only once. If you want to access the undated value in the second controller you need to following one of the following approaches.
1) Watch for changes in typesOfRates.myRates in the second controller.
$watch is used to track changes for a model variable in the scope. The
$watch requires $scope, as we have 2 different controllers, the scopes will be different (I feel so unless you have bound the two controllers
in the same html). So it won't be the correct to use $watch in this
situation.
2) Use a broad cast receiver concept
Advantage : It's preferred as there is no continuous watching required, and triggered only when the value changes
Step 1) In the first controller, register a broadcast as:
.controller('SidebarCtrl', function($scope, typesOfRates) {
$scope.rates = typesOfRates.rateType;
$scope.currentRate = {
selectedRate: 'hourly'
};
$scope.rateTypeChanged = function(rate) {
console.log("Selected goalType, text:", rate.title, "value:", rate.id);
typesOfRates.setRate(rate.id);
//$broadcast(name, args); here name you have to give in a file
//which is commonly accessible like constants.js, just create a
//file and include in you index.html, pass your rates as args
$rootScope.$broadcast(constants_config.TYPE_RATES_CHANGED, rate.id);
}
});
Step 2) Create constants.js file and include in your index.html as:
<!-----Constants Classes---->
<script src="Constants.js"></script>
In constants.js add the following code:
var constants_config = {
TYPE_RATES_CHANGED : "TYPE_RATES_CHANGED",
}
Step 3) Register your listener in the second controller as
.controller('AppCtrl', function($scope, typesOfRates, $state, $rootScope) {
// #CallBack
// Description : Callback function for user details fetched
$scope.$on(constants_config.TYPE_RATES_CHANGED, function(args) {
//Assign the value to a global variable or a scope variable so
//that you can access it throughout your controller
$scope.Rates = typesOfRates.getRate();
//Now the console will work
console.log( typesOfRates.getRate() );
});
});
Further Reference:
- Broadcasts : $broadcast
- Listeners : $on
- Watch : $watch
Hi and thanks for reading.
I have a angular app im making and ive stumbled on a problem. set up as so
index.html-
<html ng-app="myApp">
...
<div ng-view></div>
<div ng-include="'footer.html'"></div>
...
</html>
I wont bother putting my routes its pretty simple /home is shows the /home/index.html and so on...
/home/index.html (default view when you come to the site)
<div class="responsive-block1">
<div class="tweet-me">
<h1> tweet me </h1>
</div>
<div class="twitter-box">
<twitter-timeline></twitter-timeline>
</div>
twitter timeline directive
directives.directive("twitterTimeline", function() {
return {
restrict: 'E',
template: '<a class="twitter-timeline" href="https://twitter.com/NAME" data-widget-id="XXXXXXXXXXXXXX">Tweets by #NAME</a>',
link: function(scope, element, attrs) {
function run(){
(!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0],p=/^http:/.test(d.location)?'http':'https';if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src=p+"://platform.twitter.com/widgets.js";fjs.parentNode.insertBefore(js,fjs);}}(document,"script","twitter-wjs"));
console.log('run script');
};
run();
}
};
});
So I have just created a basic twitter directive using the tag from twitter. But when I change the view example to /blog then go back to /home the twitter widget no longer renders at all.
Im also using an $anchorScroll and if i jump to anyway on the page with this the widget also disappears. Any info would be great thanks.
See this post: https://dev.twitter.com/discussions/890
I think that you may be able to get the widget to re-render by calling
twttr.widgets.load().
If you find that this does not work, you will need to wrap this code into $timeout in your controller:
controller('MyCtrl1', ['$scope', '$timeout', function ($scope, $timeout) {
$timeout = twttr.widgets.load();
}])
To build on Sir l33tname's answer:
In services declaration:
angular.module('app.services', []).
service('tweetWidgets', function() {
this.loadAllWidgets = function() {
/* widgets loader code you get when
* declaring you widget with Twitter
* this code is the same for all widgets
* so calling it once will reference whatever
* widgets are active in the current ng-view */
!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0],p=/^http:/.test(d.location)?'http':'https';if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src=p+"://platform.twitter.com/widgets.js";fjs.parentNode.insertBefore(js,fjs);}}(document,"script","twitter-wjs");
};
this.destroyAllWidgets = function() {
var $ = function (id) { return document.getElementById(id); };
var twitter = $('twitter-wjs');
if (twitter != null)
twitter.remove();
};
});
Then in controller declarations:
angular.module('app.controllers', []).
controller('view_1_Controller', tweetWidgets) {
// load them all
tweetWidgets.loadAllWidgets();
}).
controller('view_2_Controller', tweetWidgets) {
// now destroy them :>
tweetWidgets.destroyAllWidgets();
});
Now whenever you leave view #1 to go to view #2, your controller for view #2 will remove the widgets associated with view #1 and when you return to view #1 the widgets will be re-instatiated.
The problem is because when Angular switches views the script tag that was originally inserted is not removed from the document. I fixed this on my own website by removing the Twitter script element whenever my Twitter timeline directive is not in the view. See the code below with comments.
function (scope, el, attrs) {
el.bind('$destroy', function() {
var twitterScriptEl = angular.element('#twitter-wjs');
twitterScriptEl.remove();
});
// function provided by Twitter that's been formatted for easier reading
function (d, s, id) {
var js, fjs = d.getElementsByTagName(s)[0], p = /^http:/.test(d.location) ? 'http' : 'https';
// If the Twitter script element is already on the document this will not get called. On a regular webpage that gets reloaded this isn't a problem. Angular views are loaded dynamically.
if (!d.getElementById(id)) {
js = d.createElement(s);
js.id = id;
js.src = p + "://platform.twitter.com/widgets.js";
js.parentNode.insertBefore(js, fjs);
}
}(document, "script", "twitter-wjs");
}
Basically it's what Loc Nguyen say.
So every time you recreate it you must remove it first.
var $ = function (id) { return document.getElementById(id); };
function loadTwitter() {!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0],p=/^http:/.test(d.location)?'http':'https';if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src=p+"://platform.twitter.com/widgets.js";fjs.parentNode.insertBefore(js,fjs);}}(document,"script","twitter-wjs");}
var twitter = $('twitter-wjs');
twitter.remove();
loadTwitter();
Answer by #b1r3k works without problems :
put this in your controller:
$timeout(function () { twttr.widgets.load(); }, 500);
For those trying to load twttr.widgets.load() inside their controller, you will most likely get an error that twttr is not defined AT SOME POINT in your UX, because the async call to load the twitter script may not be completed by the time you controller instantiates and references twttr.
So I created this TwitterService
.factory('TwitterService', ['$timeout', function ($timeout) {
return {
load: function () {
if (typeof twttr === 'undefined') {
(function() {
!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0],p=/^http:/.test(d.location)?'http':'https';if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src=p+'://platform.twitter.com/widgets.js';fjs.parentNode.insertBefore(js,fjs);}}(document, 'script', 'twitter-wjs');
})();
} else {
$timeout = twttr.widgets.load();
};
}
}
}])
and then call TwitterService.load() inside the controllers that require your widgets. This worked pretty well. It basically just checks if the twttw object exists and if it does, just reload the script... otherwise just reload the script.
Not sure if this is the best implementation, but it seems like all other solutions have edge cases where it will throw an error. I have yet to find one with this alternative.