AngularJS timing issues with controller, AJAX, ng-repeat, and directive - angularjs

I just started a few days ago with AngualrJS (loving it) using the myApp example, but I'm not quite grasping some of the event timing yet.
I have a router that initiates a templateURL and a controller. The templateURL is a partial that uses ng-repeat to loop over JSON that I load with a controller. If the controller contains static JSON it seems to work as I intend but when I load the JSON using $http I start running into the timing issues:
The partial interprets before the $http returns throwing a 404 on things like image paths before they can be replaced -> "{{album.thumbnail}}.jpg"
I also have a directive that fires once after the controller launches the asynchronous $http call and again after the $http call actually completes within the controller (ideally I only want it to fire once after $http completion)
My intention is to use retrieve album JSON data via $http using ng-repeat to loop over a template to build out my gallery. Once it's done looping I'd like to call a final function that gives the gallery a Pinterest-like masonry layout. That's seem like a very typical flow ($http -> ng-repeat -> final function) so I've got to be missing something small at this point.
Looking forward to learning more about AngularJS...
HTML
<!doctype html>
<html lang="en" ng-app="myApp">
<head>
<title>myApp</title>
</head>
<body>
<div ng-view></div>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.0-beta.5/angular.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.0-beta.5/angular-route.min.js"></script>
<script src="//code.jquery.com/jquery-1.11.0.min.js"></script>
<script src="js/modernizr-transitions.js"></script>
<script src="js/jquery.masonry.min.js"></script>
</body>
</html>
Module
angular.module('myApp', [
'ngRoute',
'myApp.filters',
'myApp.services',
'myApp.directives',
'myApp.controllers'
])
.config(['$routeProvider', function ($routeProvider) {
$routeProvider.when('/gallery', {templateUrl: 'gallery.html', controller: 'ctrlGallery'});
$routeProvider.otherwise({redirectTo: '/gallery'});
}]);
Controller
angular.module('myApp.controllers', [])
.controller('ctrlGallery', ['$scope', '$http', function (lcScope, lcHttp) {
lcHttp.get("/services/gallery.php?start=1&count=12")
.success(function(data, status, headers, config) {
lcScope.albums = data.DATA;
})
}]);
Directive
angular.module('myApp.directives', [])
.directive('postGalleryRepeatDirective', function () {
return function (scope, element, attrs) {
if (scope.$last) {
$('#container').masonry({
itemSelector: '.box',
columnWidth: 250,
isAnimated: !Modernizr.csstransitions,
isFitWidth: true
});
}
};
});
gallery.html partial
<div class="masonry" ng-controller="ctrlGallery">
<div class="box masonry-brick" ng-repeat="album in albums" post-gallery-repeat-directive>
<a href="{{ album.link }}">
<img src="/img/{{ album.thumbnail }}.jpg">
</a>
<p>{{ album.name }}</p>
</div>
</div>

It seems like you've forgotten to set $scope.last to true in your http get call? Were you intending to set that to true after the http call returned? If so, you can do a somewhat similar thing for the partial where you set ng-hide to the value of $scope.last. So it would hide the contents of the boxes (and the broken links pre correct interpolation) until the proper values had been set.

Related

Angular JS $location.path() getter not updating

I want to use the $location.path() method to return the URL path so I can write some conditional statements in an ng-hide directive. I created the following in my controller:
$scope.pathLocation = $location.path();
I then inserted {{pathLocation}} in my html just to make sure it was returning the correct path, which it is. The problem comes when I load a different view. The pathLocation doesn't update. If I manually refresh my browser on the new page view, it does. `
Here is an abbreviated version of my code:
Controller:
(function(){
var amApp = angular.module('amApp', ['ngRoute', 'ngCookies','ngAnimate' ]);
amApp.controller('WelcomeController', ['$scope', '$location', function WelcomeController($scope, $location) {
$scope.pathLocation = $location.path();
}]);
})();
Here is the HTML:
<html lang="en" ng-app="amApp">
<body ng-controller="WelcomeController as welcome">
<nav>menu's here to different views in SPA</nav>
{{pathLocation}}
<div ng-view></div>
</body>
As mentioned in my comment, nothing in your code updates your pathLocation property. Try adding this
$scope.$on('$locationChangeSuccess', function() {
$scope.pathLocation = $location.path();
});
Publish the function on scope:
amApp.controller('WelcomeController', ['$scope', '$location', function WelcomeController($scope, $location) {
//$scope.pathLocation = $location.path();
$scope.locationPathFn = $location.path;
}]);
Then execute the function in the HTML:
<html lang="en" ng-app="amApp">
<body ng-controller="WelcomeController as welcome">
<nav>menu's here to different views in SPA</nav>
{{locationPathFn()}}
<div ng-view></div>
</body>
Then on every digest cycle the $location.path() function will be checked and the view kept up to date.

AngularJS - $templateCache is not defined

I am trying to load a template file in an AngularStrap popover, however I am having trouble using $templateCache. I seem to be a step further back than the other SO questions, hence this seemingly double one.
Following the API docs I added a <script type="text/ng-template" id="popoverTemplate.html"></script> right before the closing </body> tag. When I use <div ng-include="'popoverTemplate.html'"></div> on my page, I get nothing. If I try using console.log($templateCache.get("popoverTemplate.html")) I get "$templateCache is not defined", which leads me to assume I am missing a crucial step. However, I can't find how to do it in the docs or other SO questions.
EDIT:
Injecting the service was the missing link. However, when I inject the service, the controller's other function no longer works, but if you inject al the function's parameters the working code becomes:
(function() {
"use strict";
angular.module("app").controller("managerController", ["$scope", "imageHierarchyRepository", "$templateCache", function ($scope, imageHierarchyRepository, $templateCache) {
imageHierarchyRepository.query(function(data) {
$scope.hierarchies = data;
});
var template = $templateCache.get("popoverTemplate.html");
console.log(template);
}]);
})();
To use the template script tag . You have to insert it inside the angular application. That is inside the element with the ng-app attribute or the element used to bootstrap the app if you don't use the ng-app tag.
<body ng-app="myapp">
<div ng-template="'myTemplate.html'"></div>
<script type="text/ng-template" id="myTemplate.html">
// whate ever
</script>
</body>
If you want to retrieve the template on a component of the application then you need to inject the service where you want to consume it:
controller('FooCtrl', ['$templateCache', function ($templateCache) {
var template = $templateCache.get('myTemplate.html');
}]);
Or
controller('FooCtlr', FooCtrl);
FooCtrl ($templateCache) {};
FooCtrl.$inject = ['$templateCache'];
EDIT
Do not register two controllers with the same name because then you override the first one with the last one.
(function() {
"use strict";
angular.module("app").controller("managerController",["$scope", "imageHierarchyRepository", "$templateCache", function ($scope, imageHierarchyRepository, $templateCache) {
var template = $templateCache.get("popoverTemplate.html");
console.log(template);
imageHierarchyRepository.query(function(data) {
$scope.hierarchies = data;
});
}]);
})();
Small addition: Although there are few ways to achieve your goals, like wrapping your whole HTML in <script> tags and all that, the best approach for me was to add the $templateCache logic into each Angular directive. This way, I could avoid using any external packages like grunt angular-templates (which is excellent but overkill for my app).
angular.module('MyApp')
.directive('MyDirective', ['$templateCache', function($templateCache) {
return {
restrict: 'E',
template: $templateCache.get('MyTemplate').data,
controller: 'MyController',
controllerAs: 'MyController'
};
}]).run(function($templateCache, $http) {
$http.get('templates/MyTemplate.html').then(function(response) {
$templateCache.put('MyTemplate', response);
})
});
Hope this helps!

using angular-ui-router with express/node, file structure

I am following this tutorial, and I have an app structure like this. I've tried to show only the relevant bits as it is sort of a lot of code.
/app
/views
index.ejs
/config
express.js
/public
/external_libs
angular.js
angular-ui-router.js
/js
app.js
controllers.js
/partials
home.html
server.js
Inside my express.js (relevant bit)
app.use(express.static('./public');
I am able to set up my angular controllers, so I know this directory is being hit. For example, my index.ejs
<html ng-app="myapp">
<head>
<script src="external_libs/angular.js"></script>
<script src="external_libs/angular-ui-router.js"></script>
<script src="js/app.js"></script>
<script src="js/controllers.js"</script>
</head>
<body ng-controller= "MainCtrl"> <!-- an alert in my controller fires, so I know the public directory is accessible, at least the js folder-->
<div ui-view></div>
</body>
</html>
In my app.js
var app = angular.module('myapp', ['ui.router']);
app.config([
function($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise('/home');
$stateProvider
.state('home', {
url: '/home',
templateUrl: 'partials/home.html'
});
}
]);
In controllers.js
var app = angular.module('myapp', []);
app.controller('MainCtrl', [
'$scope', function($scope) {
alert("This alert is indeed alerted");
}
]);
home.html
<h1> This is a test to see if the view on index.ejs is being populated </h1>
I have tried many different combinations for the "templateUrl" inside app.js, including
"partials/home.html"
"/partials/home.html"
"../partials/home.html"
None of these result in home.html being placed inside the div ui-view element on my index.ejs page. I realize I have posted a somewhat limited amount of code, but the fact that I am able to hit my controllers and see an alert message leads me to believe that I am almost there. I am using server side routing to render the initial index.ejs, but other than that I want to handle things client side. Does anyone know how to make angular-ui-router locate my partial with this set up?
The problem is with your controller declaration. Rather than referencing the module you recreate it (and override the existing module) by including the square brackets.
If you rewrite your controller as below it should work:
var app = angular.module('myapp');
app.controller('MainCtrl', [
'$scope',
function($scope) {
alert("This alert is indeed alerted");
}
]);
For more info, check out "Creation versus Retrieval" at https://docs.angularjs.org/guide/module

Angular Scope Directives

Angular Structural Question
I am new to angular.js and am just wondering how to go about performing a certain situation.
So basically, what I have got is a container:
<div ng-controller="ContainerController">
<container></container>
</div>
And the container controller and directives.
<script type="text/javascript" src="ContainerController.js"></script>
<script type="text/javascript" src="ContainerDirectives.js"></script>
Now the directives replaces the <container> tag with an example html: <example>{{ data }}</example>
Now within the scope of the ContainerController I have defined data as a string. (This is all example purposes). However when the directive accesses replaces it, it is unable to find the variable, due to scope.
The reason that this happens is because the ContainerDirective script's scope is not within the ContainerController scope. Meaning it is unable to access the variable.
Im just not sure on structure practices for these kinds of situations. Where do I put everything so the ContainerDirective can access the ContainerController scope.
I hope i have explained everything good enough
EDIT:
Test.js
(function(){
angular.module('test', []);
})();
TestController.js
(function(){
angular
.module('test')
.controller('TestController', [
'$scope',
TestController
]);
function TestController($scope) {
$scope.test = 'test';
}
})();
TestDirective.js
(function(){
angular.module('test').directive('test', function () {
return {
replace: true,
templateUrl: 'src/test/view/test.html',
link: function (scope, element, attrs) {
}
};
});
})();
test.html
<example>ClickMe</example>
index.html - body
<body ng-app="App" layout="row" ng-controller="TestController as page">
<test></test>
<script src="src/test/Test.js"></script>
<script src="src/test/TestController.js"></script>
<script src="src/test/TestDirective.js"></script>
<script type="text/javascript">
angular
.module('App', ['test']);
</script>
</body>
For reasons I have renamed certain variables and deleted a lot of data, but this is the core, and I am struggling to see anything wrong with this.
Error: [$interpolate:noconcat] Error while interpolating: abc/{{test}}
Strict Contextual Escaping disallows interpolations that concatenate multiple expressions when a trusted value is required.
So I figured out what was wrong in the end. Basically angular wont allow iframe of another location to be printed unless you first:
Give them the full url.
Then allow external url as a trusted website.
TO do this I had to basically add:
in the Test.js
angular.module('test', []).config(function($sceDelegateProvider) {
$sceDelegateProvider.resourceUrlWhitelist($sceDelegateProvider.resourceUrlWhitelist().concat([
'http://www.test.com/**'
]));
});
This basically took my whitelist and concatinated the new url to it.
Then inside test.html:
<example><iframe ng-src={{src}}></iframe></example>

Possible bug in UI-Router v 0.2.11 : Template injected in ui-view multiple times for each state

During working on an angularJS app using Ui-Router I noticed that the the $scope method in the controller are called multiple times when a view is loaded. After some investigation it came down to the Ui-Router itself and it seemed like for every state the template is injected in ui-view multiple times.
To confirm that I created the simplest ui-router based navigation app with 2 templates and one controller and was able to reproduce the same problem.
I created a Plunk based on this test that you can see and try it here
Here is the source of index.html
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.0/angular.min.js"></script>
</head>
<body ng-app="app">
<div><a ui-sref="page1">Page 1</a> <a ui-sref="page2">Page 2</a></div>
<div ui-view></div>
<script src="angular-ui-router.min.js"></script>
<script src="app.js"></script>
</body>
</html>
app.js
(function () {
'use strict';
angular.module('app', [
'ui.router'
])
.config(function ($stateProvider) {
$stateProvider
.state('page1', {
url: '/1',
templateUrl: 'page1.html',
controller: 'mainCtrl'
})
.state('page2', {
url: '/2',
templateUrl: 'page2.html',
controller: 'mainCtrl'
})
}
)
.controller('mainCtrl', function ($scope) {
$scope.log = log;
function log(msg) {
console.log(msg);
return true;
}
});
})();
page1.html
<h1>Page 1</h1>
<div ng-if="log('page 1')"></div>
page2.html
<h1>Page 2</h1>
<div ng-if="log('page 2')"></div>
When running this sample open your browser's console and then click on Page1 and Page 2. You will see each state change logs a message 2 or 3 times.
I've searched on the web but seems like no one else has reported this issue so it might be some wrong doing on my end. I would appreciate any help on this.
The issue you experience is not related to the UI-Router. Let me use few cites from this post (and you can find many more others about $apply() and $digest()):
Understanding Angular’s $apply() and $digest() by Sandeep Panda
$apply and $digest Explored
...When you write an expression ({{aModel}}), behind the scenes Angular sets up a watcher on the scope model...
And this is exactly what happened on your view here:
<div ng-if="log('page 1')"></div>
The ng-if directive is now part of the digest cycles and is checked if its value did not change. When it happens? How often?:
How Many Times Does the $digest Loop Run?
...The answer is that the $digest loop doesn’t run just once. At the end of the current loop, it starts all over again to check if any of the models have changed. This is basically dirty checking, and is done to account for any model changes that might have been done by listener functions. So, the $digest cycle keeps looping until there are no more model changes, or it hits the max loop count of 10...
And that's it. You've triggered some action (click on the link, with directive ui-sref). The angular environment has started to do its job. No error... No issue in UI-Router
You've just not selected the right place to expect to be evaluated only once

Resources