How to pass data from controller to directive from ajax call AngularJS - javascript

I need to send data to directive when call is successful... Here is my ajax call from my controller:
$scope.items ={
avatar: ""
};
$scope.addComment = function(segment) {
commentFactory.saveComment($scope.form.comment,segment,0,0)
.success(function(data){
$scope.items.avatar = data.avatar;
})
.error(function(data){
console.log(data);
});
// Reset the form once values have been consumed.
$scope.form.comment = "";
};
And here is 2 directive first use to submit form and ajax req, second use to update content on client side. I need in second directive to load content form ajax... Problem now is directive not wait for ajax to finish call...
.directive("addcomment", function(){
return {
restrict: "E",
template: '<input type="submit" addcomments class="btn btn-default pull-right" value="Send" />'
};
})
.directive("addcomments", function($compile){
return {
link: function (scope, element, attrs) {
var html = '<div>'+scope.items.avatar+'</div>';
element.bind("click", function(){
angular.element(document.getElementById('space-for-new-comment'))
.append($compile(html)(scope));
})
}
};
});
Any solution for this?

I just want to show you another way of writing this:
You want to put some comments, ok in html:
<div class="smartdivforcomments">
<div ng-repeat="comment in newComments">
{{comment.avatar}}
</div>
</div>
In controller: $scope.newComments = [];
Function for adding comments:
commentFactory.saveComment($scope.form.comment,segment,0,0)
.success(function(data){
$scope.newComments.push({avatar : data.avatar});
})
.error(function(data){
console.log(data);
});
Answer to your comment to previous question: You bind to click event that is not angular, so you need to use scope.apply to correctly update your view.

Use a watch in the addcomments directive and wait for the controller scope variable items.avatar to be defined.
.directive("addcomments", function($compile){
return {
link: function (scope, element, attrs) {
scope.$watch('items.avatar', function(newVal, oldVal) {
// wait for async to finish
if(scope.items.avatar === undefined) return;
// loaded, do work now
var html = '<div>'+scope.items.avatar+'</div>';
element.bind("click", function() {
angular.element(document.getElementById('space-for-new-comment'))
.append($compile(html)(scope));
});
});
}
};
});

Related

Why is my directive updating as a result of changes in another instance of the same directive?

I created a simple directive wrapper around the HTML file input to make angular binding work. Here's my directive:
angular.module('myApp').directive('inputFile', InputFileDirective);
function InputFileDirective() {
var bindings = {
selectLabel: '#',
};
return {
restrict: 'E',
require: ['inputFile', 'ngModel'],
scope: true,
controllerAs: 'inputFileCtrl',
bindToController: bindings,
controller: function () {
},
template: `<input class="ng-hide" id="input-file-id" type="file" />
<label for="input-file-id" class="md-button md-raised md-primary">{{ inputFileCtrl.getButtonLabel() }}</label>`,
link: link
};
function link(scope, element, attrs, controllers) {
if (angular.isDefined(attrs.multiple)) {
element.find('input').attr('multiple', 'multiple');
}
var inputFileCtrl = controllers[0];
var ngModelCtrl = controllers[1];
inputFileCtrl.getButtonLabel = function () {
if (ngModelCtrl.$viewValue == undefined || ngModelCtrl.$viewValue.length == 0) {
return inputFileCtrl.selectLabel;
}
else {
return ngModelCtrl.$viewValue.length + (ngModelCtrl.$viewValue.length == 1 ? " file" : " files") + " selected";
}
};
element.on('change', function (evt) {
ngModelCtrl.$setViewValue(element.find('input')[0].files);
ngModelCtrl.$render();
});
}
};
And here's the HTML
<body ng-app="myApp" ng-controller="MyController as ctrl">
<form name="ctrl.myForm">
<input-file select-label="Select Attachment" ng-model="ctrl.attachment1"></input-file>
<input-file select-label="Select Attachment" ng-model="ctrl.attachment2"></input-file>
</form>
</body>
It's pretty simple and it works - if only one is on the page. As soon as I add a second one, I notice that only the first one ever updates. If I select a file with the second one, the label updates on the first one. My suspicions are that the require ['inputFile'] is pulling in the controller for the first directive instance into the link function or something (which shouldn't happen). Even now as I type this, that doesn't really make sense to me. So what's going on here and how do I fix it?
Here's a codepen for you guys to play with and try to figure it out: http://codepen.io/astynax777/pen/PzzBRv
Your problem is not with your angular... is with you html.
You are assigning the same id twice.
Change your template to this:
template: `<label class="md-button md-raised md-primary">{{ inputFileCtrl.getButtonLabel() }}<input class="ng-hide" type="file" /></label>`

How to add new record to database with AngularJS and Laravel 5 on right way

I found a guide on how to write AngularJS the right way: https://github.com/johnpapa/angular-styleguide#factories
In Laravel, I created my POST route /api/addnew and now I need to pass params from Angular to Laravel.
This is my form:
<form id="1" class="dd animated slideInDown ng-pristine ng-valid">
<textarea class="form-control" placeholder="Vas Komentar" id="rep_text_0"></textarea>
Send
</form>
And my factory:
angular.module('commentsApp')
.factory('commentFactory', dataService);
dataSerive.$inject();
function dataService() {
var someValue = '';
var service = {
save: save,
someValue: someValue
};
return service;
////////////
function save() {
/* */
};
}
Click directive:
angular
.module('commentsApp')
.directive('addComment', addNewComment);
function addNewComment() {
var directive = {
link: link,
restrict: 'A'
};
return directive;
function link(scope, element, attrs) {
/* */
}
}
I'm stuck on how to pass details from the form to Angular and then make a post request on /api/addnew with the params?
Is there any way to use the click directive on a button to send params to the factory?
I don't think is necessary a directive for a click...
instead use ng-click and manage data (model) from the controller.
<div ng-controller="yourController">
<form id="1" class="dd animated slideInDown ng-pristine ng-valid">
<textarea class="form-control" placeholder="Vas Komentar" id="rep_text_0" ng-model="yourCommentModel"></textarea>
Send
</form>
</div>
In your controller you can use $http or your own factory.
yourApp.controller('yourController', function($scope, $http, yourFactory){
$scope.sendText = function(data){
//http code...
//or yourFactory code..
};
});

Submit form on page load in Angular

I would like to submit a search form on page load if search terms were specified in the route. The problem is that searchForm isn't defined yet when search() runs. I've seen others get this to work by putting ng-controller right on the form element, but I have multiple forms on this page, so the controller has to be a parent of the forms.
How can I ensure the form is defined when I call search()?
myModule.controller('MyController', ['$scope', '$routeParams',
function($scope, $routeParams){
$scope.model = {searchTerms: ""};
$scope.search = function(){
if($scope.searchForm.$valid){
...
}
};
if($routeParams.terms !=""){
$scope.model.searchTerms = $routeParams.terms;
$scope.search();
}
}]);
View:
<div ng-controller="MyController">
<form name="searchForm" ng-submit="search()">
...
</form>
<form name="detailForm" ng-submit="save()">
...
</form>
</div>
This seems to work:
$scope.$on('$routeChangeSuccess', function () {
if($routeParams.terms !=""){
$scope.model.searchTerms = $routeParams.terms;
$scope.search();
}
});
Have you tried just using $watch on searchForm?
if($routeParams.terms != "") {
var unregister = $scope.$watch(function() {
return $scope.searchForm;
}, function() {
// might want to wrap this an if-statement so you can wait until the proper change.
unregister(); //stop watching
$scope.model.searchTerms = $routeParams.terms;
$scope.search();
});
}

Reload a directive on controller event

I have a directive which is fetching data through ajax on load. But after an event in the controller which is posting some data, the Directive should re-compile with the new ajax data so that the changes can be reflected. Can you please help.
I have a compile function in the directive which takes data and puts that in HTML file and generates markup.
Then I have a save comment function in the controller which saves a new comment and so the directive gets the new data.
compile: function(tElement, tAttrs) {
var templateLoader = $http.get(base_url + 'test?ticket=' + $routeParams.ticketid, {cache: $templateCache})
.success(function(htmlComment) {
if (htmlComment != '')
tElement.html(htmlComment);
else
tElement.html('');
});
return function (scope, element, attrs) {
templateLoader.then(function (templateText) {
if (tElement.html() != '')
element.html($compile(tElement.html())(scope));
else
element.html('<div class="no-comments comment"><p>Be the first to comment</p></div>');
});
};
}
This is the compile part of the directive. I want this to be called through a normal controller event.
I would recommend #Riley Lark' response but as you already mentioned that your API returns an HTML instead of JSON, here is my take.
Your controller as:
<div ng-controller="MyCtrl">
<button ng-click="save()">Save Comment</button>
<comments></comments>
</div>
myApp.controller('MyCtrl', function($scope) {
$scope.commentHTML = '';
$scope.alert = function(salt) {
alert('You clicked, My Comment ' + salt);
}
$scope.save = function() {
// this imitates an AJAX call
var salt = Math.random(1000);
$scope.commentHTML+= '<div ng-click="alert(' + salt + ')">My Comment ' + salt + '</div>';
};
});
And the comments directive as:
myApp.directive('comments', function($compile) {
return {
restrict: 'E',
link: function(scope, element) {
scope.$watch(function() { return scope.commentHTML; }, function(newVal, oldVal) {
if (newVal && newVal !== oldVal) {
element.html(newVal);
$compile(element)(scope);
}
});
}
}
});
Hope this solves your problem..!
Working Demo
After you fetch the data you need, put the data in a $scope property. Define your template in terms of that property and it will automatically change when the data returns.
For example, your template might be
<div ng-repeat="comment in comments">
{{comment}}
</div>
You don't need a compile function or to "reload a directive" to accomplish this. The solution you posted is a sort of reimplementation of angular. It looks like you want to download a template with the data already interpolated into it, but Angular will help you the most if you separate the template from the data and let Angular interpolate it on the client.

Angularjs - ngModel.$setViewValue is not a function

Here is my plunker and the code I can't get to work starts on line 32
http://plnkr.co/edit/pmCjQL39BWWowIAgj9hP?p=preview
I am trying to apply an equivalent to markdown filter onto a directive... I created the filter and tested with manually applying the filter and it works that way,, but I should only use the filter conditionally when the type of content on directive is set to markdown.
I am trying to accomplish this by updating ng-model >>> ngModel.$setViewValue(html) but I am getting an error
ngModel.$setViewValue is not a function.. which makes me thing that the controller is not recognized although it is required by the directive.
Here is a working controller:
var app = angular.module('testOne', ["ngResource", "ngSanitize"]);
app.controller('testOneCtrl', function ($scope) {
$scope.product = {
id:12,
name:'Cotton T-Shirt, 2000',
description:'### markdown\n - list item 1\n - list item 2',
price:29.99
};
});
app.directive("myText", function ($parse) {
return {
restrict: "E",
require: "?ngModel",
scope:{
css: "#class", type: "#type"
},
controller: function ($scope, $element, $attrs) {},
templateUrl: "template.html",
compile: function(elm, attrs, ngModel){
var expFn = $parse(attrs.contentType + '.' + attrs.value);
return function(scope,elm,attrs){
scope.$parent.$watch(expFn, function(val){
scope.exp = { val: val };
if ( attrs.type == 'markdown'){
var converter = new Showdown.converter();
var html = converter.makeHtml(val);
//scope.exp.val = html;
ngModel.$setViewValue(html);
ngModel.$render();
}
})
scope.$watch('exp.val', function(val){
expFn.assign(scope.$parent, val)
})
}
}
}
})
This is a filter for markdown which works when applied.. (I would consider using the filter if I could figure out the way to conditionally apply it to existing directive but I'd rather do it with ng-model)
/*
app.filter('markdown', function ($sce) {
var converter = new Showdown.converter();
return function (value) {
var html = converter.makeHtml(value || '');
return $sce.trustAsHtml(html);
};
});
*/
Here is the directive template
<div ng-class="{{css}}"
ng-click="view = !view"
ng-bind-html="exp.val">
</div>
<div>
<textarea rows="4" cols="30" ng-model="exp.val"></textarea>
</div>
This is the directive in use:
<mb-text ng-cloak
type="markdown"
content-type="product"
value="description"
class="test-one-text-2">
</mb-text>
Why ngModel is empty?
When using require on a directive the controller is passed as the 4th argument to the linking function. In you code you try to reference it as an argument of the compile function. The controller is only instantiated before the linking phase so it could never be passed into the compile function anyway.
The bigger issue is that require can only get a controller of the same element ({ require: 'ngModel' }), or parent elements ({ require: '^ngmodel' } ). But you need to reference a controller from a child element (within the template).
How to get ngModel?
Do not use require at all as you cannot get child element's controller with it.
From angular.element docs:
jQuery/jqLite Extras
controller(name) - retrieves the controller of the current element or its parent. By default retrieves controller associated with the ngController directive. If name is provided as camelCase directive name, then the controller for this directive will be retrieved (e.g. 'ngModel').
Inside the linking function you can get the hold of the controller like so:
var ngModel = elm.find('textarea').controller('ngModel');
I fixed your directive:
here is a plunker: http://plnkr.co/edit/xFpK7yIYZtdgGNU5K2UR?p=preview
template:
<div ng-class="{{css}}" ng-bind-html="exp.preview"> </div>
<div>
<textarea rows="4" cols="30" ng-model="exp.val"></textarea>
</div>
Directive:
app.directive("myText", function($parse) {
return {
restrict: "E",
templateUrl: "template.html",
scope: {
css: "#class",
type: "#type"
},
compile: function(elm, attrs) {
var expFn = $parse(attrs.contentType + '.' + attrs.value);
return function(scope, elm, attrs) {
scope.exp = {
val: '',
preview: null
};
if (attrs.type == 'markdown') {
var converter = new Showdown.converter();
var updatePreview = function(val) {
scope.exp.preview = converter.makeHtml(val);
return val;
};
var ngModel = elm.find('textarea').controller('ngModel');
ngModel.$formatters.push(updatePreview);
ngModel.$parsers.push(updatePreview);
}
scope.$parent.$watch(expFn, function(val) {
scope.exp.val = val;
});
scope.$watch('exp.val', function(val) {
expFn.assign(scope.$parent, val);
});
};
}
};
});

Categories

Resources