Bootstrapping the same app multiple times in Angular - angularjs

I'm working on a project where a lot of the UI elements are separate widgets that could be used multiple times on a page. One of those widgets is a shopping cart, where I have a large view (that's present on only one page) and a small view (that's present on every page).
The page where both the large and small view of the cart is visible breaks. I've narrowed it down in Angular to the fact that both apps are bootstrapped (using angular.bootstrap()) but two different scopes are created. This causes loading errors.
I've reproduced the situation here. Even though the apps are bootstrapped, the $scope of one app isn't equal to the other. This is good, but is there a way that I can reference a module in angular.bootstrap so that two ngApps will share the same $scope?
The test HTML
<div data-app="multipleApps">
<div ng-controller="testCtrl">
<p ng-bind="testVar">a</p>
<input type="text" size="25" ng-model="testVar" />
</div>
</div>
<div data-app="multipleApps">
<div ng-controller="testCtrl">
<p ng-bind="testVar">a</p>
<input type="text" size="25" ng-model="testVar" />
</div>
</div>
The Javascript
angular.module("testApp", []).controller("testCtrl", function ($scope) {
$scope.testVar = "Ready...";
});
angular.bootstrap(document.querySelectorAll("[data-app='multipleApps']")[0], ["testApp"]);
I already tested using the injector returned from angular.bootstrap(), but this raised an a.unshift is not a function error from AngularJS.
var els = document.querySelectorAll("[data-app='multipleApps']");
if (els.length > 1) {
var injector = angular.bootstrap(els[0], ["testApp"]);
for (var i = 1; i < els.length; i++) {
angular.bootstrap(els[i], injector);
}
}

Related

AngularJS Scope not updating in view after async call

I am having trouble updating my scope on the front-end while making a request to an API. On the backend I can see that the value of my $scope variable is changing but this is not being reflected in the views.
Here is my controller.
Controllers.controller('searchCtrl',
function($scope, $http, $timeout) {
$scope.$watch('search', function() {
fetch();
});
$scope.search = "Sherlock Holmes";
function fetch(){
var query = "http://api.com/v2/search?q=" + $scope.search + "&key=[API KEY]&format=json";
$timeout(function(){
$http.get(query)
.then(function(response){
$scope.beers = response.data;
console.log($scope.beers);
});
});
}
});
Here is a snippet of my html
<div ng-if="!beers">
Loading results...
</div>
<p>Beers: {{beers}}</p>
<div ng-if="beers.status==='success'">
<div class='row'>
<div class='col-xs-8 .col-lg-8' ng-repeat="beer in beers.data track by $index" ng-if="beer.style">
<h2>{{beer.name}}</h2>
<p>{{beer.style.description}}</p>
<hr>
</div>
</div>
</div>
<div ng-if="beers.status==='failure'">
<p>No results found.</p>
</div>
I've tried several solutions including using $scope.$apply(); but this just creates the common error
Error: $digest already in progress
The following post suggested to use $timeout or $asyncDefault
AngularJS : Prevent error $digest already in progress when calling $scope.$apply()
The code I have above uses $timeout and I have no errors but still the view is not updating.
Help appreciated
I you are using AngularJS 1.3+, you can try $scope.$applyAsync() right after $scope.beers = response.data; statement.
This is what Angular documentation says about $applyAsync()
Schedule the invocation of $apply to occur at a later time. The actual time difference varies across browsers, but is typically around ~10 milliseconds. Source
Update
As others have pointed out, you should not (usually) need to trigger the digest cycle manually. Most of the times it just points to a bad design (or at least not an AngularJS-friendly design) of your application.
Currently in the OP the fetch method is triggered on $watch. If instead that method was to be triggered by ngChange, the digest cycle should be triggered automatically.
Here is an example what such a code might look like:
HTML
// please note the "controller as" syntax would be preferred, but that is out of the scope of this question/answer
<input ng-model="search" ng-change="fetchBeers()">
JavaScript
function SearchController($scope, $http) {
$scope.search = "Sherlock Holmes";
$scope.fetchBeers = function () {
const query = `http://api.com/v2/search?q=${$scope.search}&key=[API KEY]&format=json`;
$http.get(query).then(response => $scope.beers = response.data);
};
}
As the comments suggest, you shouldn't need to use $timeout to trigger a digest cycle. As long as the UX that elicits the change is within the confines of an angular construct (e.g. controller function, service, etc.) then it should manifest within the digest cycle.
Based on what I can infer from your post, you are probably using a search input to hit an API with results. I'd recommend changing the logic up such that you are triggering your search on an explicit event rather than the $watcher.
<input ng-model="search" ng-change="fetch()">
Remove the $watch logic and the $timeout wrapper.
function fetch(){
var query = "http://api.com/v2/search?q=" + $scope.search + "&key=[API KEY]&format=json";
$http.get(query)
.then(function(response){
$scope.beers = response.data;
console.log($scope.beers);
//it's a good habit to return your data in the promise APIs
return $scope.beers;
});
}
The reasons I make this recommendation is:
You have finer control of how the ng-change callback is triggered using ng-model-options. This means you can put a delay on it, you can trigger for various UX events, etc.
You've maintained a clearer sequence of how fetch is called.
You have possibly avoided performance and $digest issues.
Hey guys I solved the issue but I'm not sure exactly why this changed anything. Rearranging my code on JS Fiddle I just put all my partials into the index.html file like so and the requests and scope variables updated smoothly. Is was there perhaps a controller conflict with my html above?
<body ng-app="beerify" ng-controller='searchCtrl'>
<nav class="navbar navbar-inverse navbar-fixed-top">
<div class="container"><!-- nav bar code -->
</div>
</nav>
<!-- Main jumbotron for a primary marketing message or call to action -->
<div class="jumbotron">
<div class="container">
<h1>Title</h1>
<form ng-submit="fetch()">
<div class="input-group">
<input type="text" ng-model="search"
class="form-control" placeholder="Search the name of a beer" name="srch-term" id="srch-term">
<div class="input-group-btn">
<button class="btn btn-default" type="submit"><i class="glyphicon glyphicon-search"></i></button>
</div>
</div>
</form>
</div>
</div>
<div class="container">
<div ng-if="!beers">
Loading results...
</div>
<div ng-if="beers.status==='success'">
<div class='row'>
<div class='col-xs-8 .col-lg-8' ng-repeat="beer in beers.data track by $index" ng-if="beer.style">
<!-- ng-if will make sure there is some information being displayed
for each beer -->
<h2>{{beer.name}}</h2>
<h3>{{beer.style.name}}</h3>
<p>AbvMin: {{beer.abv}}</p>
<p>AbvMax: {{beer.ibu}}</p>
<p>{{beer.style.description}}</p>
<hr>
</div>
</div>
</div>
<div ng-if="beers.status==='failure'">
<p>No results found.</p>
</div>
</body>

AngularJS (ng-repeat directive) with Meteor

I have a question regarding the directive ng-repeat. Is it possible to perform manipulation to each item in a collection?
<div ng-repeat = "item in items">
</div>
Is there any possible way to call a Meteor method to perform some sort of manipulation to each item?
I'll be more specific. What I'm doing is using the meteor twitter accounts and every time a user logs in, it stored in a database.
index.ng.html :
<div ng-controller = "TweetsCtrl">
<div ng-repeat = "t in twitterAccs">
<img src = "{{t.services.twitter.profile_image_url_https}}">
<br>
<i>#{{t.services.twitter.screenName}}</i>
<br>
<br>
</div>
</div>
JS file :
if (Meteor.isClient) {
angular.module('twitter-example',['angular-meteor']);
angular.module('twitter-example').controller('TweetsCtrl', ['$scope','$meteor', function($scope,$meteor) {
$scope.twitterAccs = $meteor.collection(Meteor.users);
}]);
}
Right now, its able to get the image and the twitter handle. What I want to do is extract the number of followers and friends but that requires OAUTH from Twitter. Since those require special keys from Twitter, I know I have to put that information in the if (Meteor.isServer) scope.

Angularjs use same ng-app multiple times on page

I'm dealing with a content management system that needs to "inject" a reusable component into a page.
I want to inject the following component (html and javascript).
<script type="text/javascript">
if(typeof angular == 'undefined') {
document.write(unescape("%3Cscript type='text/javascript' src='/resources/scripts/lib/angular.min.js'%3E%3C/script%3E"));
document.write(unescape("%3Cscript type='text/javascript' src='/resources/scripts/pricing/app.js'%3E%3C/script%3E")); }
}
</script>
<div ng-app="pricing" ng-controller="PriceController as pc" ng-init="pc.getPrices('myprod', 'PER')">
Some text {{ pc.prices.msg["startdat tarief"] | jsDate }} .
More text {{ pc.prices.msg["einddat product"] | jsDate }}.
</div>
The component must be able to be injected multiple times on the page.
The problem is that the controller works fine, but only for the first injection.
This probably has something to do with that I am using the same app multiple times.
I am fairly new to angular.
How can I inject the same component multiple times?
Note that I am not able to init the app on a higher level. Because this would require the content manager to edit all pages, we juist want to inject a HTML component with javascript (i.e. the code snippet).
If you are able to add a unique ID to the module div you can manually bootstrap your angular app as follows:
function bootstrapAngular(id) {
angular.bootstrap(document.getElementById('module-' + id), ['app']);
}
angular.module('app', []).controller('sample', function ($scope) {
$scope.foo = 'bar';
});
bootstrapAngular(1);
bootstrapAngular(2);
bootstrapAngular(3);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div id="module-1">
<div ng-controller="sample">
{{ foo }}
</div>
</div>
<div id="module-2">
<div ng-controller="sample">
{{ foo }}
</div>
</div>
<div id="module-3">
<div ng-controller="sample">
{{ foo }}
</div>
</div>
You cannot have multiple ng-App directives on a single page. From the Angular.js documentation:
Only one AngularJS application can be auto-bootstrapped per HTML document. The first ngApp found in the document will be used to define the root element to auto-bootstrap as an application. To run multiple applications in an HTML document you must manually bootstrap them using angular.bootstrap instead. AngularJS applications cannot be nested within each other.
If placing ng-App higher in the tree isn't an option, you will have to re-write the components so that each component gets a unique angular.module() and when the component is injected, it will need to fire angular.bootstrap().

Why is my ng-if not working? It's not hiding the div

In my Angular app, I want a certain div to appear if a variable is true, and to disappear if it is false.
However, it is not working. See my Fiddle
Can anyone help me understand why?
HTML
<div ng-controller="MyCtrl">
<div id="newProjectButton" class="project" ng-click="newProjectActive()" ng-if="!creatingNew">
<h1> + </h1>
<h3> New Project </h3>
</div>
<div id="newProjectActive" class="project" ng-if="creatingNew">
<form>
<input name="name" ng-model="newProjectName" type="text"></input>
<button ng-click="newProject()" type="submit" class='btn btn-primary'>Save</button>
</form>
</div>
</div>
JS
var myApp = angular.module('myApp', []);
function MyCtrl($scope) {
$scope.name = 'Superhero';
$scope.creatingNew = false;
$scope.newProjectActive = function () {
$scope.creatingNew = true;
}
$scope.newProject = function () {
alert($scope.newProjectName);
}
}
your angular version is 1.0.1. directive ng-if is not in this version of angular.
its introduce in angular 1.1.5
check this article
and
check it in angular under Directives topic change log
AngularJS first added the ngIf directive in 1.1.5
please update the angular version
here is the Demo
your controller should be like
myApp.controller("MyCtrl" , function($scope) {
because the global controllers are not supported by default in angular 1.3... check this one
First, when I looked at your fiddle, there was older version of your example there,
Second, which actually may be a reason, is in that example you were using angular in version 1.0.1, and i believe that version didn't implement ng-if. Updating to latest version will fix your problem

how to create reusable component in angularJS?

I have two controllers at the moment, one is a form where a user inputs a value and the other is a graph that renders based on an api search. I would need to refresh or re run the second controller based on the input from the first. I am almost certain this is bad design on my part but have not found the ideal way to do it.
HTML code:
<html>
<body>
<!-- this is the graph generator-->
<div ng-controller="GraphAppCtrl"></div>
<!-- this is th form submittal section-->
<div ng-controller="FormCtrl" id="FormCtrl">
<form novalidate class="simple-form">
Name: <input type="text" ng-model="tech"/><br />
<button ng-click="submit(tech)">Submit</button>
</form>
</div>
</body>
</html>
Angular Code:
var app = angular.module('testApp', []);
app.controller('FormCtrl', function ($scope) {
$scope.submit = function(tech){
// Need to be able to call the function that generates graph and pass it the tech value
}
}
app.controller('GraphAppCtrl', function ($scope) {
//here I do a bunch of stuff to generate graph, like http request from third party api etc.
}
What is happening now since I have a reference to the graph controller in the my index.html page it renders the first time based on default parameters, but I would need it to render based on the submit.
I have looked Directives and experimented with the answer in this question but it seems as Directives more of a solution to pass data between controllers rather than what I am trying to do.
You should create an Angular Service
https://docs.angularjs.org/guide/services

Resources