How to restart angular app without page reload? - angularjs

I would like to reset the state of my angular app without forcing a page refresh. What's the idiomatic way to do this?

I don't know if there's an idiomatic way, but if you use manual bootstrap (ie, get rid of ng-app), then to reset you could
Remove the element that you bootstrapped angular into
Replace it with a clean copy of the element
Run bootstrap again
Example HTML:
<div id="myid" ng-controller="MyCtrl">
<button ng-click="i = i+1">Add 1</button>
<span>i = {{i}}</span>
<button ng-click="reset()">RESET</button>
</div>
Example controller/JS (with jQuery included as well), which would be run on onload:
var $cleanCopy = $("#myid").clone();
function bootstrap() {
angular.bootstrap(document.getElementById('myid'), ['mymodule']);
}
angular.module('mymodule', []).controller('MyCtrl', function($scope) {
$scope.i = 1;
$scope.reset = function() {
// You need this second clone or angular bootstraps
// into the original clone!
$("#myid").replaceWith($cleanCopy.clone());
bootstrap();
};
});
bootstrap();
Here is a working JSFiddle: http://jsfiddle.net/zd377/2/

Related

angularjs ng-click "How can I use javascript's setAttribute() to create ng-click attribute with some function"

I would like to be able to select a button using querySelector and set an attribute of "ng-click=doSomething()"
I have tried selecting the button and then setAttribute("ng-click", "doSomething()") but its not working
my DOM:
<body>
<div ng-app="myApp" ng-controller="mainCtrl">
<button id="myBtn">click Me</button>
</div>
<script src="./js/app2.js"></script>
</body>
my javascript:
(function() {
"use strict";
angular.module("myApp", []).controller("mainCtrl", mainCtrl);
/** #ngInject */
function mainCtrl($scope) {
init();
function init() {
$scope.doSomething = () => {
console.log("doing something");
}
let btn = document.querySelector('#myBtn');
btn.setAttribute("ng-click", "doSomething()");
}
}
})();
when I click the button it should console log something.
Generally speaking, if you dynamically add "AngularJS-ified" stuff to a document after it's created - such as dynamically creating <button> elements and then adding ng-click attributes to them - those elements will neither be tracked by watchers, nor be part of the normal digest cycle. So, for example, consider the following simple example:
const myApp = angular.module('stuff', [])
.controller('stuff-cont', function($scope) {
const targ = document.querySelector('#target');
for (let i = 0; i < 10; i++) {
let newBtn = document.createElement('button');
newBtn.setAttribute('ng-click', 'sayRandNum()');
newBtn.innerText = `Button ${i}`
targ.append(newBtn);
}
$scope.sayRandNum = () =>{
alert('Your random number is '+Math.ceil(Math.random()*100));
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.7.5/angular.min.js"></script>
<div ng-app='stuff' ng-controller='stuff-cont'>
<div id='target'>
</div>
The buttons above are clickable, they have an appropriately "structured" ng-click, but they <i>don't trigger</i>!
</div>
Here, we're (for some reason...) creating 10 nearly-identical buttons. However, because of when we built these ng-click'ed buttons (namely, after the initial compilation phase), and specifically when we added the ng-click attributes (also after the initial compilation phase), the buttons are effectively not "known" to the AngularJS cycle".
Looked at another way, when AngularJS is first "loaded" on a page, it first walks through the HTML on that page, and looks for any databinds ({{likeThis}}; we'll ignore these for now) or directives. ng-click, ng-repeat, and other Babbys First AngularJS stuff are just standardized directives, so they're part of that "looking for directives" procedure. When AngularJS finds said directives, it says "Okay, you've got an ng-click on this element; I'll keep an eye on that".
To actually add new AngularJS-ified elements - or add AngularJS behavior to existing elements, as I believe is more the case with you - you'll need to use the $compile function, which basically says "hey, AngularJS! I made a new thing and want you to watch it!"
This SO answer -- Working with $compile in angularjs has a pretty decent explanation of how to use the $compile function.
(function() {
"use strict";
var btn = document.querySelector('#myBtn');
btn.setAttribute("ng-click", "doSomething()");
angular.module("myApp", []).controller("mainCtrl", mainCtrl);
function mainCtrl($scope){
$scope.doSomething = function(){
alert('abc');
}
}
angular.bootstrap(document, ['myApp']);
})();
Please check the JSFiddle , the difference is you have to modified the html before angular bootstrapped so your modified html and js code can be compiled properly. Here is a AngularJS Developer Guide - Bootstrap with more infomation of angularjs bootstrap

AngularJS - How to change another image when principal image change

I'm trying to develop a solution through AngularJS where when the user changes the profile image, all other photos based on their profile are also changed.
How can I start developing this feature?
Thanks in advance!
[Solution]
With the help of the #wolfman6377, I can understand the functionality and develop the solution for what I need. I'll leave an example in CodePen.
CodePen
var myApp = angular.module('myApp',[]);
myApp.controller('myController', function() {
this.myVar = 'https://robohash.org/asd'
this.updatePicture = function() {
this.myVar = 'https://angular.io/resources/images/logos/angular/angular.png';
};
});
Thank you, #wolfman6377.
You are using a jQuery-like strategy. jQuery element selector will not be able to change the value of myVar.
I would recommend going through some basic tutorials on angular1. But in a nutshell, you need to:
Do not grab elements and change attributes
add a module to the ng-app value
Use ng-controller in the template
Add ng-click to the button to update the value of myVar
template
<body ng-app="myApp">
<div id="var" ng-controller="myController as vm">
<img ng-src={{vm.myVar}} />
<button ng-click="vm.updatePicture()">Click me</button>
</div>
</body>
controller:
angular
.module('myApp', [])
.controller('myController', function() {
this.myVar = 'https://www.w3schools.com/angular/pic_angular.jpg'
this.updatePicture = function() {
this.myVar = 'https://angular.io/resources/images/logos/angular/angular.png';
};
});

How to hide <img> using md-switch

angular-material newbie here. I'm trying to hide some elements in our site using a switch, as instructed by our client. This led me to angular-material's md-switch.
So I tried incorporating it like so...
<md-switch md-no-ink aria-label="switchView" ng-model="switchView">{{switchView}}</md-switch>
And called the value of the switch in my element like this:
<img ng-src="{{photoPath}}" class="profilePic" ng-hide="switchView"/>
After testing it though, it didn't hide my <img> even though my switchView changed its value. Am I missing something here?
Other methods I've tried:
Adding ng-change to my md-switch, which called a function that would equate another variable (e.g. $scope.toggleView = $scope.switchView) with switchView's value. $scope.toggleView would then be used in my ng-hide.
ng-hide = "switchView == true".
Any advice would be very much appreciated. Thank you.
UPDATE 1: To test it, I tried hiding the <div> beside my <md-switch> and it worked perfectly. However it's still not working with my <img>.
Further checking revealed that it was inside a <nav> element. However they're both using the same controller. I wonder if that's the problem? I assumed that it shouldn't be a problem because of this.
The structure is like this:
<nav ng-controller="MainController">
<!-- other navigation elements here -->
<img ng-src="{{photoPath}}" class="profilePic" ng-hide="switchView"/>
</nav>
<div ng-controller="MainController">
<div>Toggle Switch</div>
<md-switch md-no-ink aria-label="switchView" ng-model="switchView">{{switchView}}</md-switch>
</div>
UPDATE 2: I've added the following code in my JS file because there are plans to hide elements in other pages. It still didn't work.
$scope.onChange = function(value) {
$scope.$broadcast("view_mode", $scope.switchView);
}
$scope.$on("view_mode", function(event, switchValue) {
$scope.viewThis= switchValue;
});
My HTML now looks like this:
<img ng-src="{{photoPath}}" class="profilePic" ng-hide="viewThis"/>
As for the controller, ngMaterial was called in a separate JS file (our main), together with all our dependencies and configs. Hence, this wasn't called inside MainController.
mainApp.js
var app = angular.module('myAppModule', [
// other references here
'ngMaterial'
]);
mySample.js
angular
.module('myAppModule')
.controller('MainController', ['$scope', function ($scope) {
// functions etc. go here
Hope this helps clear things up. Thank you.
Try something like this, its a trivial example but hope it helps. Here's a link to working codepen.
There seems to be a couple ways you could handle this according to the docs:
Angular Material- MD-Switch
function exampleController($scope) {
$scope.secondModel = false;
$scope.onChangeEvent = function(value) {
$scope.imgSource = (value) ? 'http://www.fillmurray.com/300/200' : 'http://www.fillmurray.com/g/155/300';
};
// alternatively: you could set the ternary to empty string value here.
}
angular
.module('BlankApp', ['ngMaterial'])
.controller('exampleController', exampleController);
<md-switch ng-model="switchValue" ng-change="onChangeEvent(switchValue)">
<img ng-src="{{imgSource}}" alt="" />
</md-switch>
<md-switch ng-model="secondModel">
<img src="http://www.fillmurray.com/300/200" alt="" ng-hide="secondModel" />
</md-switch>
Thank you to everyone who gave their inputs. After some research, I managed to solve this problem using factories (source).
I'm sharing my solution so that it may help others who experience the same problem.
HTML:
<nav ng-controller="MainController">
<!-- other navigation elements here -->
<img ng-src="{{photoPath}}" class="profilePic" ng-hide="switchView"/>
</nav>
<div ng-controller="MainController">
<div>Toggle Switch</div>
<md-switch md-no-ink aria-label="switchView" ng-model="switchView" ng-change="onChange(switchView)">{{switchView}}</md-switch>
</div>
JS:
angular
.module('myAppModule')
.controller('MainController', ['$scope', 'ViewModeFactory', function ($scope, ViewModeFactory) {
// functions etc. go here
// For client presentation mode
$scope.onChange = function(value) {
ViewModeFactory.setViewMode($scope.switchView);
}
$scope.$watch(function (){
$scope.switchView = ViewModeFactory.getViewMode();
});
}])
.factory('ViewModeFactory', function () {
var data = {isViewMode: ''};
return {
getViewMode: function () {
return data.isViewMode;
},
setViewMode: function(value) {
data.isViewMode = value;
}
};
});
I used factories so that other controllers in our site can use the value passed by md-switch.
Hope this helps.

ng-scrollbar is not working with ng-repeat

In my app I want to use a custom scrollbar for a div. So I used ng-scrollbar, it is working fine with static data. But whenever I get the data using ng-repeat it is not working. Please help me in this regard. Thanks in advance.
myFile.html
<style>
.scrollme {
max-height: 300px;
}
</style>
<div ng-app="myapp">
<div class="container" ng-controller="myctrl">
<button class="btn btn-info" ng-click="add();">add</button>
<button class="btn btn-warning" ng-click="remove();">remove</button>
<div class="well" >
<div class="scrollme" ng-scrollbar bottom rebuild-on="rebuild:me">
<h1>Scroll me down!</h1>
<p ng-repeat="mi in me">{{mi.name}}</p>
</div>
</div>
</div>
</div>
myCtrl.js
var myapp = angular.module('myapp', ["ngScrollbar"]);
myapp.controller('myctrl', function ($scope) {
$scope.me = [];
for(var i=1;i<=20;i++){
$scope.me.push({"name":i});
}
var a = $scope.me.length;
$scope.add = function(){
$scope.me.push({"name":$scope.me.length+1});
$scope.$broadcast('rebuild:me');
}
$scope.remove = function(){
$scope.me.pop();
}
});
Try adding the broadcast call to the end of your controller so it fires on controller load. If that doesn't work, try adding:
$timeout(function () {
$scope.$broadcast('rebuild:me');
}, 0);
// 0 optional, without it the time is assumed 0 which means next digest loop.
at the end of your controller code, not inside the add function. If this works but the previous approach doesn't then that means ngRepeat didn't finish rendering it's dynamic content in time for the ngScrollbar to properly update.
UPDATE: in general, you might have to wrap the broadcast inside of the add() function in a timeout as well. The reason I say this is that I suspect what's going on is that you add data to the scope variable and then broadcast all in the same function call. What might be happening is that the broadcast event is caught and scrollbar recalculates before ngRepeat sees the updated scope data and adds its extra DOM elements. Btw, if you want to recalculate the scrollbar on add(), then you also want to do this on remove() as well.
So your add function would become:
$scope.add = function(){
$scope.me.push({"name":$scope.me.length+1});
// wait until next digest loop to send event, this way ngRepeat has enough time to update(?)
$timeout(function () {
$scope.$broadcast('rebuild:me');
});
}
please try ng-scroll... another plugin, but without need of manual adjust.
mentioned on:
AngularJS with ng-scroll and ng-repeat
If you use jQuery, you can try jQuery Scrollbar - it has more options and fully CSS customizable.
Example with ng-repeat is here
JavaScript
var demoApp = angular.module('demoApp', ['jQueryScrollbar']);
demoApp.controller('SimpleController', function($scope){
$scope.me = [];
for(var i=1;i<=20;i++){
$scope.me.push({"name":i});
}
$scope.add = function(){
$scope.me.push({"name":$scope.me.length+1});
}
$scope.remove = function(){
$scope.me.pop();
}
$scope.jqueryScrollbarOptions = {
"onUpdate":function(container){
setTimeout(function(){
// scroll to bottom. timeout required as scrollbar restores
// init scroll positions after calculations
container.scrollTop(container.prop("scrollHeight"));
}, 10);
}
};
});
HTML
<div data-ng-app="demoApp">
<div data-ng-controller="SimpleController">
<button class="btn btn-info" ng-click="add();">add</button>
<button class="btn btn-warning" ng-click="remove();">remove</button>
<div class="scrollbar-dynamic" data-jquery-scrollbar="jqueryScrollbarOptions">
<h1>Scroll me down!</h1>
<p ng-repeat="mi in me">{{mi.name}}</p>
</div>
</div>
</div>
CSS
.scrollbar-dynamic {
border: 1px solid #FCC;
max-height: 300px;
overflow: auto;
}
This might be a bit late.
The problem is even though you have added the content to scope variable, angular has not finished adding p tags to your DOM. If you try a simple console log like
console.log($('.well').find('p').length);
After pushing content to $scope.me, you will understand what is happening. (Need jQuery at least to debug)
The solution is far more complicated than you can imagine.
STEP 1:
Add a ng-controller to your ng-repeat (Yes. It is allowed)
<p ng-repeat="mi in me" ng-controller="loopController">{{mi.name}}</p>
STEP 2: Define loopController
demoApp.controller('loopController', function($scope) {
$scope.$watch('$last', function(new_val) {
new_val && $scope.$emit('loopLoaded', $scope.$index);
});
});
This controller function is triggered whenever ng-repeat manipulates DOM. I'm watching $last which is a scope variable for ng-repeat. This will be set to true whenever, ng-repeat loads last element in DOM. When $last is set to true I emit one event loopLoaded. Since you are pushing values into $scope.me using a loop, this event will be triggered for every push.
STEP 3: Event handling
In your SimpleController (not simple anymore, eh?)
$scope.$on('loopLoaded', function(evt, index) {
if (index == $scope.me.length-1) {
$scope.$broadcast('rebuild:me');
}
});
Once all the p elements are loaded, index sent to event will be equal to $scope.me.length-1. So you call scroll rebuild. That's it.
Here's a reference I used - AngularJS - Manipulating the DOM after ng-repeat is finished

Dynamically add existing controller into another controller with AngularJS

in my app I have a wrapper controller that handles some properties dynamically based on other-controllers within it. everything works like a charm if the other-controllers are present/static on load, but as soon as I'm trying to make them dynamic, they stop working.
It was my understanding that the $rootScope is available from everywhere within the app, is that not true?
my JS looks like this:
var webApp = angular.module("webApp",[]);
webApp.controller("ControllerA", function($scope, $rootScope){
$rootScope.cnt = 0;
$rootScope.cntPlusPlus = function(){
$rootScope.cnt++;
};
$rootScope.controllerBs = [];
var template = $(".controller-b").html();
$scope.addControllerB = function(){
$rootScope.controllerBs.push(template);
};
});
webApp.controller("ControllerB", function($scope, $rootScope){
$scope.cntPlusPlus = function(){
console.log("overwrite plus plus");
}
});
Full example: http://plnkr.co/edit/tAcv1F9y7t9q9XsQ1EFL?p=preview
I know that this would be probably better with directives, but is there any way to make it work with Controllers?
thanks for the help
Don't try to access the DOM from controller code. Never. It is very bad practice which breaks AngularJS conventions and eventually provides you with bad architecture. This also means you should not create any DOM elements manually from a controller.
Better to manipulate with the scope itself, not with its visual representation. You can add new models to scope on your button's click, which will be translated to new elements by ng-repeat directive, each with its own controller (remember controllers are instances, not singletons, so that they have separated life cycles).
You might want to make use of <script type="text/ng-template"> and ng-include here instead of hidden divs.
Try to avoid using $rootScope when possible - it is global state which can be dangerous.
It might look like this then (plunker):
HTML:
<div class="controller-a" ng-controller="ControllerA">
Controller A
<div>
<button ng-click="cntPlusPlus()">cnt++</button> CNT: {{cnt}}
</div>
<button ng-click="addB()">Add B</button>
<div ng-repeat="B in Bs">
<div ng-include="'b-template'"></div>
</div>
</div>
<script type="text/ng-template" id="b-template">
<div ng-controller="ControllerB">this is controller b: <button ng-click="cntPlusPlus()">cnt++</button></div>
</script>
JS:
var webApp = angular.module("webApp",[]);
webApp.controller("ControllerA", function($scope){
$scope.cnt = 0;
$scope.cntPlusPlus = function(){
$scope.cnt++;
};
$scope.Bs = [];
$scope.addB = function(){
$scope.Bs.push({});
};
});
webApp.controller("ControllerB", function($scope){
$scope.cntPlusPlus = function(){
console.log("overwrite plus plus");
$scope.$parent.$parent.$parent.cnt++; //should be moved to service
}
});
</script>

Resources