How to update model asynchronously? - angularjs

This code is invoking multiple get requests and updating model
from result of each get request.
http.get is asynchronous, does this extend to when the UI model is updated ?that if the get request returns data out of order (the 3'rd get request returns data before the 1'st request) then the third value for statusViewer directive
will be updated first on UI ? If not how can modify to update UI model when data is returned from get request ?
plnkr :
https://plnkr.co/edit/BjETLN7rvQ1hNRIm51zG?p=preview
plnkr src :
http-hello1.html:
{ "content" : "divContent" , "id" : "r1" }
http-hello2.html:
2. http-hello2.html
http-hello3.html:
3. http-hello3.html
index.html :
<!doctype html>
<html ng-app="app">
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.6/angular.min.js"></script>
<script src="script.js"></script>
</head>
<body>
<div ng-controller="FetchCtrl">
<status-viewer ng-repeat="sourceUrl in sourceUrls" url="sourceUrl"></status-viewer>
</div>
</body>
</html>
mytemplate.html:
<!--<h1>{{url}}</h1>-->
<div>
<p>{{model}}</p>
</div>
script.js :
var myapp = angular.module('app', []).controller('FetchCtrl', FetchCtrl)
myapp.directive('statusViewer', function ($http) {
return {
restrict: 'E',
templateUrl: 'mytemplate.html',
scope: {
url: '='
},
link: function (scope, elem, attrs, ctrl) {
$http.get(scope.url).success(function (data) {
scope.model = JSON.stringify(data);
});
}
};
});
function FetchCtrl($scope, $http, $q , $parse) {
$scope.sourceUrls = [
'http-hello1.html',
'http-hello2.html',
'http-hello3.html'
];
}
Update : The expected behavior is that the UI model will be updated in same order as 'success' callback is invoked, is my expected behavior assertion valid ?

Related

appending html from another file to div using directive

I am quite new to angularjs, I am trying to append html from another html file using my directive. I only want to do this only if I get a success callback from the server from my http request.
Currently I have my directive, I am not sure if this is the way to do it. I have also attached my directive to the div I want to append to.
host.directive('newHost', function(){
return {
restrict: 'E',
link: function(scope, element, attr){
scope.newBox = function(){
templateUrl: 'newHostTemplate.html';
};
}
}
});
I then call $scope.newBox() on my success callback which at that point I want to append to the div.
I have followed the answer below, and tried to adapt it to my scenario, however I am getting the error $scope.newBox is not a function, here is my current implementation.
host.directive('newHost', function(){
return {
restrict: 'E',
template: '<div ng-include="getTemplateUrl()"></div>',
link: function(scope, element, attr){
scope.newBox = function(){
console.log('newBox');
scope.getTemplateUrl = function(){
return 'newHostTemplate.html'
};
};
}
}
});
//controller, this does all the routing on client side
host.controller('hostsController', function($scope, $http, $window, $rootScope){
$rootScope.$on("$routeChangeError", function (event, current, previous, rejection) {
console.log("failed to change routes");
});
$scope.newHost = {};
$scope.addNewHost = function() {
$http({
method : 'POST',
url : 'http://192.168.0.99:5000/newHost',
data : JSON.stringify($scope.newHost), // pass in data as strings
})
.success(function(data) {
console.log(data);
$scope.newBox()
//on success we want to close the modal and reset the data
$scope.newHost = {}
$scope.dismiss()
});
};
});
Here are two examples:
Using ng-include and a function that points to the external file
I created a plunker showing you a working example. As soon as you click the 'process data' link, it will call NewBox() that will append the content from an external file. This link simulates your callback.
In the directive, the template is defined as template:
<div ng-include="getTemplateUrl()"></div>
And in the link function, I setup getTemplateUrl() once newBox() is called... the getTemplateUrl() function returns the name of the external file (e.g. template.html):
link: function(scope, element, attr) {
scope.newBox = function() {
console.log('new Box');
scope.getTemplateUrl = function() {
return 'template.html';
}
}
}
The full JS file is:
angular.module('components', [])
.directive('newHost', function() {
return {
restrict: 'E',
template: '<div ng-include="getTemplateUrl()"></div>',
link: function(scope, element, attr) {
scope.newBox = function() {
console.log('new Box');
scope.getTemplateUrl = function() {
return 'template.html';
}
}
}
}
});
angular.module('HelloApp', ['components'])
.controller('MyCtrl', ['$scope', function($scope) {
$scope.name = 'This is the controller';
$scope.go = function() {
console.log('Processing...');
$scope.newBox();
}
}]);
index.html is:
<!doctype html>
<html ng-app="HelloApp" >
<head>
<meta charset="utf-8">
<title>AngularJS Plunker</title>
<link rel="stylesheet" href="style.css">
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.5/angular.js"></script>
<script src="app.js"></script>
</head>
<body>
<div ng-controller="MyCtrl">
<new-host></new-host>
<br/>
<a ng-href='#here' ng-click='go()'>process data</a>
</div>
</body>
</html>
And template.html is a simple example:
<div>
This is some content from template.html
</div>
If you look in plunker, once you press 'process data', the template.html content is then added using the newBox() function. Now, you would replace that link with your callback.
Using ng-show and a boolean to hide/show the content
One way which is going in a slightly different direction than you is to use ng-show and hide the template until newBox() is called.
I created a JSFiddle that shows an example of how to do this.
The new-host tag is hidden at the start using ng-show:
<div ng-controller="MyCtrl">
<new-host ng-show='displayNewHost'></new-host>
<br/>
<a ng-href='#here' ng-click='go()' >process data</a>
</div>
The link process data is to simulate your success callback, so when you click on it, it will call $scope.newBox()
Here is the main JS file:
angular.module('components',[])
.directive('newHost', function() {
return {
restrict: 'E',
link: function(scope, element, attr) {
scope.newBox = function() {
console.log('new Box');
scope.displayNewHost = true;
};
}
}
})
angular.module('HelloApp', ['components'])
function MyCtrl($scope) {
$scope.displayNewHost = false;
$scope.name = 'This is the controller';
$scope.go = function() {
console.log('Processing...');
$scope.newBox();
}
}
angular.module('myApp', ['components'])
As you see, in the controller we set displayNewHost to false hiding the directive. Once one clicks on the process data link, the newBox function sets displayNewHost to true and then the content appears.
In your example, you would replace the 'template' by 'templateUrl' pointing to your file.
That is another solution.
Editing my answer to answer the follow-up question / newBox is not a function error
Just reading your code (without checking using plunker so I may be wrong), I am guessing that the problem is that once you are in the success function $scope points to another scope. Try to change your code to this... please note that I put $scope in a variable 'vm' and then use that in both the core function and the success callback.
host.controller('hostsController', function($scope, $http, $window, $rootScope){
$rootScope.$on("$routeChangeError", function (event, current, previous, rejection) {
console.log("failed to change routes");
});
var vm = $scope;
vm.newHost = {};
vm.addNewHost = function() {
$http({
method : 'POST',
url : 'http://192.168.0.99:5000/newHost',
data : JSON.stringify(vm.newHost), // pass in data as strings
})
.success(function(data) {
console.log(data);
vm.newBox()
//on success we want to close the modal and reset the data
vm.newHost = {}
vm.dismiss()
});
};
});'

Custom angular directive : how to watch for scope changes

I am writing a custom directive with a single field in its scope. This field is a dictionary with arrays in values.
I want the directive to react to any change made on this field : new entry, additional value in list, etc...
I'm just trying to figure out why :
my directive does not react when I change values in the dictionary.
directive is not even initialized with the initial dictionary.
Here is a simplified version of my script, where I only perform some logging in the sub-directive.Nothing happens when the button is clicked on the dictionary is modified :
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Test</title>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script>
angular.module("myApp", [])
.controller("myCtrl", function($scope) {
$scope.dico = {};
$scope.dico["Paris"] = [];
$scope.dico["Paris"].push("Tour Eiffel");
$scope.dico["Paris"].push("Champs Elysees");
$scope.dico["London"] = [];
$scope.dico["London"].push("British Museum");
$scope.addPOI = function() {
$scope.dico["Wellington"] = [];
$scope.dico["Wellington"].push("Botanic Garden");
console.log($scope.dico);
};
})
.directive('subdirective', function() {
return {
restrict: 'E',
template: '<div><span ng-repeat="key in myDico">{{key}}</span></div>',
link: function(scope, element, iAttrs) {
console.log("test");
scope.$watch("myDico", function(newVal, oldVal) {
console.log("watch!");
console.log(newVal);
//update values...
}, true);
},
scope: {
myDico: '='
}
};
});
</script>
</head>
<body ng-app="myApp">
<div ng-controller="myCtrl">
<button ng-click="addPOI()">
Add POI
</button>
<div>
<subdirective myDico="dico"></subdirective>
</div>
</div>
</body>
</html>
I have tried to use $watch, $watchCollection, deep watch, but it does not seem to do the job.
You are missing scope binding definition in your Directive Definition Object.
scope: {
myDico: '='
}

Custom directive not rendering

In this directive I'm attempting to display the result of multiple get requests :
plnkr : https://plnkr.co/edit/BjETLN7rvQ1hNRIm51zG?p=preview
src :
http-hello1.html :
{ "content" : "divContent" , "id" : "r1" }
http-hello2.html :
2. http-hello2.html
http-hello3.html :
3. http-hello3.html
index.html :
<!doctype html>
<html ng-app>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.6/angular.min.js"></script>
<script src="script.js"></script>
</head>
<body>
<div ng-controller="FetchCtrl">
<source-viewer ng-repeat="sourceUrl in sourceUrls" url="sourceUrl"></source-viewer>
</div>
</body>
</html>
mytemplate.html :
<h1>{{url}}</h1>
<div>
<p>{{model.address}}</p>
</div>
script.js :
// Example of how to call AngularJS $http service and
var myapp = angular.module('app', []).controller('FetchCtrl', FetchCtrl)
myapp.directive('sourceViewer', function ($http) {
return {
restrict: 'E',
templateUrl: 'mytemplate.html',
scope: {
url: '='
},
link: function (scope, elem, attrs, ctrl) {
$http.get(scope.url).success(function (data) {
$scope.model = data.data;
});
}
};
});
function FetchCtrl($scope, $http, $q , $parse) {
$scope.sourceUrls = [
'http-hello1.html',
'http-hello2.html',
'http-hello3.html'
];
}
But nothing is being rendered. Am I defining the directive correctly ?
Use ng-include in mytemplate.html
https://plnkr.co/edit/gPoRn89anqn7uLRMYVSG?p=preview

How To Get Data Back From Angular Directive

i hope somebody could help me with a small example, because angular is driving me crazy :(
I'm making by first time a Formular that should follow this structure:
Angular APP
mainController
---->smallController1
-------->otherElements
---->smallController2
-------->otherElements
---->Directive1 (instance 1)
---->anotherSmallController
---->Directive1 (instance 2)
The Directive1 receives many attributes, and each instance will allow the selection of many options, and the result of the user interaction should be stored in an object, and that object should be accessed from mainController for each instance separately.
Does anyone have an example that work like that?
Thanks in advance,
John
The best way to get data back from directive is to attach a model to directive's self scope.
var app = angular.module('app', []);
app.controller('mainController',
[
'$scope',
function($scope){
$scope.myObj = "Initial Value";
}
]);
app.directive('dirName', [
function(){
return {
restrict : 'A',
scope : {
obj : "=ngModel"
},
link : function(scope, element, attrs){
scope.obj = attrs.newValue;
}
};
}
]);
<!DOCTYPE html>
<html>
<head>
<script src="//code.jquery.com/jquery-2.1.1.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.2/angular.min.js"></script>
<meta charset="utf-8">
<title>JS Bin</title>
</head>
<body ng-app="app" ng-controller="mainController">
<input dir-name ng-model="myObj" new-value="Final Value">
</body>
</html>
You can check this bin as well : http://jsbin.com/fuqujo/1/edit?html,js,output
Use emit to send data to parent controller.
It may be receiver because listening to event.
Read about on emit and broadcast.
In your child controller or directive use:
$scope.$emit('myEvent', object);
This sends object to all parent controllers.
In parent controller use:
$scope.$on('myEvent', function(event, obj) { console.log(obj); });
To listen to emitted object.
Try this code out, it might help you:
http://plnkr.co/edit/L1NwUnNePofyRAQ6GVIg?p=preview
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope) {
$scope.name = 'World';
$scope.masterData = [
{
entry: 'dunno'
},
{
entry: 'stuff'
}
]
})
app.directive('vessel', function() {
return {
replace: true,
scope: {
data: '=',
speciality: '#'
},
link: function(scope) {
scope.updateData = function() {
scope.data.entry = scope.speciality;
}
},
template: '<div>{{data.entry}} <button ng-click="updateData()">update</button></div>'
}
});
And the template:
<body ng-controller="MainCtrl">
<p>Master data {{masterData | json}}</p>
<div vessel data="masterData[0]" speciality="eggs"></div>
<div vessel data="masterData[1]" speciality="bacon"></div>
</body>
So, we have separate data models for each directive which are updated on user input, fitting your requirements. Right?

AngularJS directive in $http json response

I try the same thing as here
I retrieve with Ajax server Data which Json contains the markup with a directive and trying to use $compile to fire up this one but at this point without any luck
here is my controller where I try use $compile
angular.module('blancAppApp')
.controller('SlugCtrl', function ($scope, WpApi, $compile, $filter, ngProgressLite) {
// the Content to be rendered.
$scope.post = [];
//loading animate starts
ngProgressLite.start();
loadRemoteData();
// load remote data from the server.
function loadRemoteData() {
// The WpApiService returns a promise.
WpApi.getContents()
.then(
function( post ) {
applyRemoteData( post );
});
}
// apply the remote data to the local scope.
function applyRemoteData( newContents ) {
var compiled = $compile( newContents )($scope);
console.log(compiled); // this one shows me the object then object.content contains my masonry directive
firebug output: <div class="ng-scope">[object Object]</div>
}
//loading animate ends
ngProgressLite.done();
}).directive('compiled', function($timeout) {
return {
restrict: 'E',
scope: {
compiled: '=compiled'
},
link: function (scope, element, attrs, $timeout) {
element.append(scope.compiled);
}
};
});
the directive which should be called
angular.module('blancAppApp')
.directive('masonry', function(scope, element, attrs, $timeout) {
console.log('asdasd');
var container = element.querySelector('#grid');
var msnry = new Masonry( container, {
// options...
itemSelector: '.masonry-brick',
columnWidth: 200
});
return {
restrict: 'E'
};
});
the generated view
<div ng-repeat ="article in post " id="#main-content">
<div ng-bind-html="article.content | unsafe">
<div ng-controller="ContentCtrl">
{{ article.content }}
</div>
</div>
</div>
Generated Markup with directive call
<div id="grid" masonry="">
<div class="masonry-brick "><img src=""... /></div>
<div class="masonry-brick "><img src=""... /></div>
<div class="masonry-brick "><img src=""... /></div>
</div>
plunker
You use $compile incorrectly. $compile returns "a link function which is used to bind template". So you need to call returned function. Try:
var compiled = $compile($scope.post)($scope);
console.log(compiled);
Resulting element must be then attached somewhere to DOM document for example with directive:
<!doctype html>
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.0/angular.js"></script>
</head>
<body ng-app="plunker">
<div data-ng-controller="SlugCtrl">
<div id="compiled_content" data-compiled="compiled">
</div>
</div>
<script>
var app = angular.module('plunker', []);
app.controller('SlugCtrl', function ($scope, $compile) {
$scope.some_var = 'var content';
var template = '<div>{{some_var}}</div>';
$scope.compiled = $compile(template)($scope);
$scope.some_var = 'var content canged';
}).directive('compiled', function() {
return {
restrict: 'A',
scope: {
compiled: '=compiled'
},
link: function (scope, element, attrs) {
element.append(scope.compiled);
}
};
});
</script>
</body>
</html>
I used element.append(scope.compiled); and not just {{scope.compiled}} because compilation result is object and not a string.
Your applyRemoteData function must be like this:
function applyRemoteData( newContents ) {
$scope.compiled = $compile('<div>' + newContents + '</div>')($scope);
}

Resources