Update variable inside ng-include template once a promise is kept - angularjs

I'm in an interesting conundrum.
In my app, I am using a recursive <script> which is referred to in my main page via an ng-include:
<div ng-include="'theTemplate.html'" onload="one=features.items.one"></div>
The data for features.items.one is loaded via external api using $http and is set when the $http promise is kept.
The problem is, that the ng-include loads before the promise is kept, and when the promise does complete, the {{one}} INSIDE the script does not update (because ng-include creates a new scope)
How can I get the variable inside the script to update when the promise completes?

Extending my comment:
<div ng-if="your condition">
<div ng-include="'theTemplate.html'" onload="one=features.items.one"></div>
</div>

Use a controller, and consider refactoring to a service in due course
<div ng-include="'theTemplate.html'" ng-controller="myCtrl"></div>
and then add a controller
xxx.controller('myCtrl', function($scope, Features) {
$http request.then(function(data) {
$scope.one = data.items.one;
})
})

Related

Preload dataset to the controller AngularJS

Hello Angular experts,
I have to preload a certain dataset (factory call to the database) to the controller. I don't use angular views so stateProvider or routeProvider cannot be used to resolve. Basically I need the dataset readily available before loading the controller.
Is there a way to achieve this?
I have a controller and a view. The view also has a widget. The widget has an attribute that expects a dataset. By the time the controller is done fetching data the view is already rendered so the widget input parameters are empty. So I need the widget dataset to be filled much before getting to the controller. By the way the app.run solution doesn't work as there is a promise involved.
You can't say as before loading controller, i correct it with before binding controller
angularjs has app.run and i think you know it, it work just when application run (first time) and every time you refresh it.
var app = angular.module("app", []);
app.run(function($http, $rootScope) {
$http.get("url").then(function(response){
console.log(response.data)
$rootScope.data = response.data; // as global scope
})
})
app.controller('ctrl', function($scope) {
$scope.$watch('data', function(newValue, oldValue) {
if(newValue){
console.log(newValue) // you will get `rootscope.data` when it's ready
}
})
})
You can add factory to the app run too.
please fill free to ask question.
Based on your comment, what you are trying to do shouldn't require any changes to the Controller.
What you are actually trying to do is delay rendering of the widget component until you have data that the widget needs. This is commonly handled by ng-if in your HTML.
For Example (pseudocode):
<div ng-if="myData">
<Widget input="myData"></Widget>
</div>
Angular will not render this div until myData has a value, and all your Controller has to do is make sure myData is added to $scope.

Using $stateParams in a "root" or global controller

I am creating an AngularJS application that is a web planner that consists of 6 steps. Each step has its own view and its own controller. There is some data that should be accessible from all states (There is Order level information that is used in the header/title of the planner). I have put this information in a service, but the problem is in this root controller, it does not have access to the $stateParams which is how I know whether or not API data needs to be fetched.
I could use $scope.$emit() or $scope.$broadcast to tell my root controller when ui.router runs and finds the ID in the URL, but that just seems like bad design. What is the best way to make the $stateParams available to a global controller?
Here is what the HTML looks like.
<div ng-app="MyApp.SubModule.Module" ng-controller="RootController">
<sk-overlay ng-show="overlayVisible"></sk-overlay>
<h2 class="dashTitle"> {{ (Order.CustomerInfo.CompanyName ? '- ' + Order.CustomerInfo.CompanyName : '') }}</h2>
<div id="wpMenuContainer" class="wpMenuContainer">
<ul class="wpMenu clearfix">
<li ng-repeat="chevron in Chevrons" ng-class="{ active : chevron.active, navable : $index < Index }" ng-click="!($index < Index)||navigate(chevron, $index)">
<span>{{ chevron.name }}</span>
</li>
</ul>
</div>
<div ui-view class="do"></div>
</div>
I want RootController to be used to control data that is global to the entire application, but that state does not exist in the $stateProvider, therefore it does not get access to the $stateParams variables.
You could always use a shared service (I think I actually mean factory here) to enable two way communication between different controlers.
This is a hot topic and there are lots of related questions on this:
Share data between AngularJS controllers
Passing data between controllers in Angular JS?
Update
You could actually make that factory global by assigning it to the $rootScope, thus being able to observe it without having to assign it in every controller:
angular.module('app')
.factory('sharedData', function() {
return {};
})
.controller('mainController', function($rootScope, sharedData) {
$rootScope.sharedData = sharedData;
$rootScope.$watch('sharedData', updateMyStuff, true);
})
.controller('subController', function(sharedData) {
sharedData.stateParams = 'inaccessible stuff';
});
$stateParams can be injected into any of your controllers/services/directives using DI, it is a services that is provided by ui-router.
$state can also be injected into any of your controllers/services/directives using DI, it is a services that is provided by ui-router. You can access stateParams through $state via $state.params
Also, ui-router mentions that it is handy to set up $state in $rootScope on run() so that it can be access primarily in templates.

angularjs compile json with template

I am new to angularjs, and I have an jquery background.
I want to compile json from the server into an element with an template.
What I now have for so far is:
The template:
<script type="text/ng-template" id="/tiles.html">
<div ng-repeat="tile in tiles">
{{tile.name}}<img ng-src="tile.src" />
</div>
</script>
The button for displaying the content:
<button ng-click="imageOptions.addFromList()">+ Add Image from list</button>
The function:
$scope.imageOptions.addFromList = function (){
$http
.get('/json/Tiles/get')
.success(function(data){
$scope.tiles = data;
console.log(data);
})
.error(function(data){
console.log("something did go wrong");
});
$(".prompt").html('<div ng-include src="/tiles.html"></div>');
};
The placeholder:
<div class="prompt"></div>
The placeholder will be used many times with also other content.
So I can not just type the html from the .html() argument. Like this:
<div class="prompt"><div ng-include src="/tiles.html"></div></div>
When I inspect the .prompt div it will stay uncompiled
The first thing you should do is remove jQuery library from your app while you get familiar with angular methodology.
There is no need to use html() method when all you need to do is include your template through a variety of different ways in your html source.
If the data isn't already available for ng-repeat it will simply fail quietly and do nothing. Then when the data is available it will respond automatically.
You could simply do:
<div class="prompt" ng-include src="/tiles.html"></div>
Or you could make a simple directive that will accomplish the same thing .
app.directive('prompt', function() {
return {
restrict: 'C',/* use for "class" */
templateUrl: '/tiles.html'
}
});
Simply change this
<div ng-include src="/tiles.html">
to this
<div ng-include src="'/tiles.html'">
While coding your single page application in angularjs, ideally there should not be any need for you to first get a reference to an element and then perform some action on it (You may think of this as the first step of switching from a jquery background to angularjs domain).
To achieve complete separation of model, view and controller you should just define your templates and controllers accordingly. These mappings and references should be managed by angularjs on its own.
As correctly mentioned above you should not be using .html() method of jquery. If you have included jquery in your document, it will be internally used by angularjs, but, including jquery should not be mandatory for using angularjs.
ng-repeat and ng-include also create a separate scope, so you may want to take care of those as well in future.
For your query, you may reference the template by including extra quotes in ng-include as:
<div class="prompt">
<div ng-include src="'tiles.html'"></div>
</div>
http://jsfiddle.net/PKKp8/

Render string templates via ng-include

I am trying to render a piece of html, available on a dynamic route, the route is fetched via a $http.get() call, it returns a piece of html,
Just to give an example I try to load this html partial:
<h1>{{ pagetitle }}</h1>
this is a simple page example
I made a small fiddle, to mock the problem, but for the simplicity i left the http call out, and just added the html in a string on the scope.
The controller is:
function Ctrl($scope) {
$scope.data = {
view: "<h1>whaaaaa</h1>"
};
}
The page html is this:
<div ng-app="">
<div ng-controller="Ctrl">
<div ng-include src="data.view"></div>
</div>
</div>
The problem is that it does not add the string into the html file (ng-include), but it makes a http call to a url made of that string aparently.
So Is it not possible to just enter a string into an include? if not, what is the proper way to make an http call to a dynamic url, and enter the returned url into the page.
You can play with it in JSFiddle.
You can add static hooks to the template cache, so let's say you have a service that gets the template:
myApp.service('myTemplateService', ['$http', '$templateCache', function ($templateCache) {
$http(/* ... */).then(function (result) {
$templateCache.put('my-dynamic-template', result);
});
/* ... */
}]);
Then, you can do:
<div include src=" 'my-dynamic-template' "></div>
NOTE reference name must be wrapped in single quotes, this always bytes me.... NOTE
Do note that you'll have to have assigned it to the template cache before angular tries to resolve the url.
EDIT:
If the dynamic URL logic is simple enough, you can also use a conditional in the ng-include, e.g.
<div ng-include="isTrue() && 'template1.html' || 'template2.html'"
You can't do that with ng-include; reference:
ngInclude|src – {string} – angular expression evaluating to URL. If
the source is a string constant, make sure you wrap it in quotes, e.g.
src="'myPartialTemplate.html'".
Use directly ng-bind-html-unsafe like this:
<div ng-bind-html-unsafe="data.view"></div>
Demo
Creates a binding that will innerHTML the result of evaluating the
expression into the current element. The innerHTML-ed content will not
be sanitized! You should use this directive only if ngBindHtml
directive is too restrictive and when you absolutely trust the source
of the content you are binding to.
You can use ng-bind-html-unsafe
If you do not trust the html which comes from your ajax request, you can also use ng-bind-html which needs the $sanitize service to work (DEMO).
<div ng-bind-html="data.view"></div>
To use that you need to include an extra js file .
angular.module('app', ['ngSanitize']);
And of course add the app to ng-app:
<div ng-app="app">
ng-include expects a URL to an external HTML fragment, not an HTML string.
You want ng-bind-html-unsafe:
Updated demo.

Wait until scope variable is loaded before using it in the view in angular.js

I've seen this and this but it seems like there might be a simpler way.
In my view I have several menu options that are controlled through permissioning - i.e., not everyone can see a "Dashboard" view. So in my menu option in my view I have something like the following:
<li ng-show="validatePermission('Dashboard')">Dashboard</li>
In my controller I have a validatePermission method defined where it is looking at the permissions of the current user. For example:
$scope.validatePermission = function(objectName) {
if $scope.allPermissions......
Also in my controller I'm loading those permissions via an $http call:
$http.get('permissions/' + userid + '.json').success(function(data) {
$scope.allPermissions = data;....
The issue is that $scope.allPermissions doesn't get loaded before the view makes the call to validatePermission. How can I wait for allPermissions to be loaded before the view renders?
You ask:
How can I wait for allPermissions to be loaded before the view renders?
To prevent the entire view from rendering, you must use resolve. You don't have to use the promise library though, since $http returns a promise:
var app = angular.module('app');
app.config(function ($routeProvider) {
$routeProvider
.when('/', {
templateUrl : 'template.html',
controller : 'MyCtrl',
resolve : MyCtrl.resolve
});
});
function MyCtrl ($scope, myHttpResponse) {
// controller logic
}
MyCtrl.resolve = {
myHttpResponse : function($http) {
return $http({
method: 'GET',
url: 'http://example.com'
})
.success(function(data, status) {
// Probably no need to do anything here.
})
.error(function(data, status){
// Maybe add an error message to a service here.
// In this case your $http promise was rejected automatically and the view won't render.
});
}
}
But if you simply want to hide the dashboard <li>, then do as Joe Gauterin suggested. Here's a very simple example plunkr if you need it.
Have the validatedPermission function return false when allPermissions hasn't been loaded. That way the element with your ng-show won't be displayed until allPermissions has been loaded.
Alternatively, put an ng-show="allPermissions" on the enclosing <ul> or <ol>.
You can also specify on your routecontroller a resolve object that will wait for that object to resolve prior to rendering that route.
From the angular docs: https://docs.angularjs.org/api/ngRoute/provider/$routeProvider
resolve - {Object.=} - An optional map of dependencies which should be injected into the controller. If any of these dependencies are promises, they will be resolved and converted to a value before the controller is instantiated and the $routeChangeSuccess event is fired. The map object is:
key – {string}: a name of a dependency to be injected into the controller.
factory - {string|function}: If string then it is an alias for a service. Otherwise if function, then it is injected and the return value is treated as the dependency. If the result is a promise, it is resolved before its value is injected into the controller.
A google group reference as well: https://groups.google.com/forum/#!topic/angular/QtO8QoxSjYw
I encountered an similar situation, you might also want to take a quick look at
http://docs.angularjs.org/api/ng/directive/ngCloak
if you're still seeing a "flicker" effect.
As per the angularjs documentation:
The ngCloak directive is used to prevent the Angular html template from being briefly displayed by the browser in its raw (uncompiled) form while your application is loading. Use this directive to avoid the undesirable flicker effect caused by the html template display.
Wrapping the code in ng-if fixed the issue for me:
<div ng-if="dependentObject">
<!-- code for dependentObject goes here -->
</div>

Resources