I have a component which gets initialized with a value got from an ajax request in its controller $postLink event. This component is inside a form.
Then the user may change the default value and then submit. When the form is submitted, the component value becomes blank. I need to find a way to reinitialize back to the value got from the ajax request, but of course the $postLink event cannot fire again since the component has already been created.
Component js:
app.component('formFieldPortafoglioSelector', {
templateUrl: 'ngComponents/form-field-portafoglio-selector.html',
bindings: {
label: '#',
model: '='
},
controller: ['$scope', 'ApiService', function($scope, ApiService) {
var ctrl = this;
ctrl.$postLink = function() {
ApiService.domini.then(function(response) {
$scope.portafogli = response.domini.portafogli;
ctrl.model = $scope.portafogli[0].id_ptf;
});
};
}]
});
Component Template:
<div class="form-group">
<label class="col-form-label col-form-label-sm">{{ $ctrl.label }}</label>
<select class="form-control custom-select"
ng-options="ptf.id_ptf as ptf.des_ptf for ptf in portafogli"
ng-model="$ctrl.model"
>
</select>
</div>
Component Usage:
<div ng-controller="resourceController as rc">
<form ng-submit="rc.operazione.$save()">
<form-field-portafoglio-selector model="rc.operazione.id_ptf_sorgente" label="Portafoglio Sorgente">
</form-field-portafoglio-selector>
</form>
</div>
The Resource Controller is defined as follows:
app.controller('resourceController', ['$scope', '$resource', function($scope, $resource) {
this.operazione = new $resource('operazioni', null, {})();
}]);
Related
So, I'm using a directive which is a form to allow the app user to edit or create users.
When I navigate to users/:id, I'm pulling in a userDetail view which pulls in a directive user-form.
...
<h1>User Detail</h1>
<p>{{ detail.first_name }}</p>
<p>{{ userDetail.last_name }}</p>
<p>{{ userDetail.age }}</p>
<p>{{ userDetail.address }}</p>
<p>{{ userDetail.gender }}</p>
<user-form ctrl="editUser" user-detail="userDetail"></user-form>
This view has a controller which pulls in the userDetail via an ajax call:
userDetail.controller("UserDetailController", ['$http', '$scope', '$routeParams', function($http, $scope, $routeParams) {
function getUser() {
var url = "http://localhost:3000/api/users/" + String($routeParams.id)
$http.get(url).then(function(response) {
$scope.userDetail = response.data;
if (!response.data) {
document.location = '/#!/error'
}
})
}
getUser();
}]);
So I created my directive and its controller is based on the ctrl attribute when I pull in the directive depending on the page I am:
function createUser($scope, $rootScope, $http) {
$scope.submitUser = function(isValid) {
var url = 'http://localhost:3000/api/users'
var data = $scope.user;
var config = 'contenttype';
if (isValid) {
$http.post(url, data, config).then(function(response) {
$scope.users.push(response.data);
});
}
}
}
function editUser($scope, $http, $routeParams) {
setTimeout(function() {
//get $scope.userDetail
//assign $scope.user = $scopeDetail to populate the form with the details to allow editing.
console.log($scope.user);
}, 2000)
}
function userFormDirective() {
return {
scope: { users: '=', userDetail: '=', user: '=' },
name: 'ctrl',
controller: '#',
templateUrl: 'components/userForm/userForm-template.html',
}
}
var app = angular.module('user-management', [
'ngRoute',
'users',
// 'userForm',
'userDetail'
]).directive('userForm', userFormDirective)
.controller('createUser', createUser)
.controller('editUser', editUser);
Here is my userForm template:
<form name="userForm" novalidate ng-submit="submitUser(userForm.$valid)">
<fieldset>
<dl>
<dt><label for="first-name">First Name</label></dt>
<dd><input id="first-name" ng-model="user.first" type="text" required /></dd>
</dl>
<dl>
<dt><label for="last-name">Last Name</label></dt>
<dd><input id="last-name" ng-model="user.last_name" type="text" required/></dd>
</dl>
<dl>
...
So basically, when I go to users/2, it does an AJAX request to get the user details 9Hence, a timeout function). Then, I want the directive's controller to access the models in the view so that I can assign the userDetails that were obtained and assign it to the models in the view, thereby autpopulating it and allowing edits. Not sure if this is a correct way of doing it because I'm rather new to AngularJS.
I have two controllers: One with controls - textarea and button. And another that takes the value of the text from the textarea and then displays it. I am able push the value of the textarea into an array. But I cant access the array in another directive so that it wud display the value.
I am new to angular scopes.
First Controller:
This directive has its own controller.
angular.module('myApp')
.controller('listController', ['$scope','$compile','$http', function($scope, $compile, $http){
'ngInject';
var vm =this;
console.log("in addmectrl");
$scope.tasks=[];
$scope.cardarr =[];
vm.addme = function(){
$scope.tasks.push({title: vm.title, cardarr: []}); //pushes in to an array
}
}])
.directive('addListControls', function() {
return {
restrict: 'E', // Element directive'
controller: 'listController as listctrl2',
scope: { tasks: "#",
cardarr: "#"},
template: `
<textarea ng-model= "listctrl2.title" placeholder ="Add a List"
id="input" style = "position:absolute">
</textarea>
<button id="controlbutton" class ="btn btn success"
style = "position:absolute"
ng-click="listctrl2.addme()">Save
</button>
<img id ="remove" src ="remove.png"
ng-click ="listctrl2.removeinput()">`,
};
});
This directive accesses the parent controller.
Display the text in this template---
angular.module('myApp')
.directive('listWrapperTitlebox', function() {
return {
restrict: 'E', // Element directive
template: `
<b class ="card1" id ="cardtitle">{{task.title}}</b>
<a class ="card1" tabindex="0" data-trigger ="focus"
data-toggle="popover"
ng-click = "ctrl.listpopover($index)" >...</a>`
};
});
I'm using Angular 1.4.1 and UI Bootstrap 0.13.
I have a directive from which I'm opening a modal. The modal opens fine, but the buttons seemingly don't get bound to their handlers - they don't do anything. I've used this same code in another project just fine, except not from within a directive.
My directive:
(function () {
var app = angular.module('myApp');
app.directive('someDirective', function () {
return {
restrict: 'E',
templateUrl: 'SomeDirective.html',
scope: {
list1: '=list1',
list2: '=list2',
save: '&'
},
controller: ['$scope','$modal','myService', function ($scope,$modal,myService) {
$scope.openModal = function () {
var modalInstance = $modal.open({
templateUrl: 'ModalTemplate.html',
controller: 'modalController',
backdrop: 'static',
size: 'sm',
resolve: {
saveData: function () {
//do save action
}
}
});
modalInstance.result.then(
function (itemToSave) {
//save item
},
function () {
//Cancel
}
);
};
}]
}
});
}());
The modal's controller:
(function() {
var app = angular.module('myApp');
app.controller('modalController', [
'$scope', '$modalInstance', 'saveData',
function($scope, $modalInstance, saveData) {
$scope.saveData = saveData;
$scope.save = function() {
$modalInstance.close($scope.saveData);
};
$scope.cancel = function() {
$modalInstance.dismiss('cancel');
};
}
]);
}());
And the template for the modal content:
<div class="modal-header bg-info">
<h3 class="modal-title">Add New Record</h3>
</div>
<div class="modal-body">
<form class="form-horizontal" role="form"></form>
</div>
<div class="modal-footer bg-info">
<button class="btn btn-primary" ng-click="save()">Save</button>
<button class="btn btn-warning" ng-click="cancel()">Cancel</button>
</div>
My thought is that I'm having issues with scope, but I can't track it down. I have put break points on modalController. The app.controller() call happens when the app loads, I've seen that. But breakpoints within save() and cancel() never get hit.
Can someone help me figure out why the modal's buttons don't do anything?
This turned out to be a stupid mistake. At some point I apparently overwrote the name of one of the other controllers in my project with the same name of the controller I was using for the modal. The other controller did not have save() or cancel() methods so nothing was happening. As soon as I fixed my error and all controllers once again had their proper names this started working again.
I've come across a quite a few similar questions and tried them all with no success. I'm not sure if I'm doing this the "Angular way" so I might need to change my approach. In short I'm initialising a scoped variable from within a controller, the variable is then shared with the directive's scope. However when this value is changed as part of a user interaction the new value is not being synchronised with the controllers variable. My abbreviated code follows:
Controller
The $watch method is only called once i.e. when the page loads.
.controller('NewTripCtrl', ['$scope', function($scope) {
$scope.pickupLocation;
$scope.dropoffLocation;
$scope.$watch('dropoffLocation', function(oldVal, newVal) {
console.log('dropofflocation has changed.');
console.log(oldVal);
console.log(newVal);
});
}])
HTML
<div class="padding">
<input ion-google-place type="text" class="ion-google-place" autocomplete="off" service="places" placeholder="Pickup Address" location="pickupLocation"></input>
</div>
<div class="padding">
<input ion-google-place type="text" class="ion-google-place" autocomplete="off" service="places" placeholder="Dropoff Address" location="dropoffLocation"></input>
</div>
Directive
angular.module('ion-google-place', [])
.directive('ionGooglePlace', [
'$ionicTemplateLoader',
'$ionicBackdrop',
'$q',
'$timeout',
'$rootScope',
'$document',
function ($ionicTemplateLoader, $ionicBackdrop, $q, $timeout, $rootScope, $document) {
return {
restrict: 'A',
scope: {
location: '=location'
},
link: function (scope, element, attrs) {
scope.locations = [];
console.log('init');
console.log(scope);
console.log(attrs);
// var geocoder = new google.maps.Geocoder();
var placesService = new google.maps.places.AutocompleteService();
var searchEventTimeout = undefined;
var POPUP_TPL = [
'<div class="ion-google-place-container">',
'<div class="bar bar-header item-input-inset">',
'<label class="item-input-wrapper">',
'<i class="icon ion-ios7-search placeholder-icon"></i>',
'<input class="google-place-search" type="search" ng-model="searchQuery" placeholder="' + 'Enter an address, place or ZIP code' + '">',
'</label>',
'<button class="button button-clear">',
'Cancel',
'</button>',
'</div>',
'<ion-content class="has-header has-header">',
'<ion-list>',
'<ion-item ng-repeat="l in locations" type="item-text-wrap" ng-click="selectLocation(l)">',
'{{l.formatted_address || l.description }}',
'</ion-item>',
'</ion-list>',
'</ion-content>',
'</div>'
].join('');
var popupPromise = $ionicTemplateLoader.compile({
template: POPUP_TPL,
scope: scope,
appendTo: $document[0].body
});
popupPromise.then(function (el) {
var searchInputElement = angular.element(el.element.find('input'));
// Once the user has selected a Place Service prediction, go back out
// to the Places Service and get details for the selected location.
// Or if using Geocode Service we'll just passing through
scope.getDetails = function (selection) {
//console.log('getDetails');
var deferred = $q.defer();
if (attrs.service !== 'places') {
deferred.resolve(selection);
} else {
var placesService = new google.maps.places.PlacesService(element[0]);
placesService.getDetails({
'placeId': selection.place_id
},
function(placeDetails, placesServiceStatus) {
if (placesServiceStatus == "OK") {
deferred.resolve(placeDetails);
} else {
deferred.reject(placesServiceStatus);
}
});
}
return deferred.promise;
};
// User selects a Place 'prediction' or Geocode result
// Do stuff with the selection
scope.selectLocation = function (selection) {
// If using Places Service, we need to go back out to the Service to get
// the details of the place.
var promise = scope.getDetails(selection);
promise.then(onResolve, onReject, onUpdate);
el.element.css('display', 'none');
$ionicBackdrop.release();
};
function onResolve (details) {
console.log('ion-google-place.onResolve');
scope.location = details;
$timeout(function() {
// anything you want can go here and will safely be run on the next digest.
scope.$apply();
})
if (!scope.location) {
element.val('');
} else {
element.val(scope.location.formatted_address || '');
}
}
// more code ...
};
}
]);
The $watch function only detects a change when the page loads but never again. What is the correct way to provide the controller with the value that the user enters into the directive's input element?
As angular docs says:
Scope inheritance is normally straightforward, and you often don't even need to know it is happening... until you try 2-way data binding (i.e., form elements, ng-model) to a primitive (e.g., number, string, boolean) defined on the parent scope from inside the child scope.
This is a common issue when doing 2-way data binding with primitives. Try doing this same but try to share an object instead of a primitive.
i.e:
.controller('NewTripCtrl', ['$scope', function($scope) {
$scope.pickupLocation = {location: ''};
$scope.dropoffLocation = {location: ''};
...
Here are your options
Option#1 using ng-model
with watch function in parent controller
<input ion-google-place type="text" class="ion-google-place" autocomplete="off" service="places" placeholder="Dropoff Address" ng-model="dropoffLocation"></input>
and in directive
scope: {
location: '=ngModel'
},
You don't need Watch here.
Option#2 Through Object literal
.controller('NewTripCtrl', ['$scope', function($scope) {
$scope.locationChanged = function(location){
//you watch code goes here
}
}
<input ion-google-place type="text" class="ion-google-place" autocomplete="off" service="places" placeholder="Dropoff Address" location="dropoffLocation" location-changed="locationChanged(location)" ></input>
//directive scope
scope: {
location: '=',
locationChanged: '&'
}
//in link function invoke parent controllers method as
scope.locationChanged({location:scope.location});
Option#3 Through Function reference
controllect and directives scope same as option#2
<input ion-google-place type="text" class="ion-google-place" autocomplete="off" service="places" placeholder="Dropoff Address" location="dropoffLocation" location-changed="locationChanged" ></input>
//in link function invoke parent controllers method as
scope.locationChanged()(scope.location);
Option#2 is recommended for better readability.
watching variables should be avoided as much as possible.
I am making an app, where I have a lot of input fields. Those input fields are generated from JSON object array field with AngularJS ngRepeat directive and have a button next to them which open an Angular UI Bootstrap modal to edit this value in a bigger textarea. I cannot figure out how to reference this model property to Angular UI Bootstrap so that I can save the changes made in modal. Since this functionality is needed in multiple views, I turned it into a service.
I have made a plunker to illustrate my problem.
http://plnkr.co/edit/ZrydC5UExqEPvVg7PXSq?p=preview
Currently in plunker example modal contains textarea, but I will actually need to use Text-Angular directive, because those fields contain some HTML markup and I would be easier for users to edit values with this nice addon.
TextAngular
EDIT: Please, if you are taking time to answer, you might aswell take a little more time to edit my plunker example to show exactly how your solution would look like, because seems that everyone who tries to help me, think they know the solution, but in reality it doesn't work :( Thanks!
I personally like to decorate my $scope with the services (i.e. $scope.modalService = ModalService;), so I understand the source of the logic. In the ng-repeat you then pass the value item into the method call:
<div class="input-group">
<input class="form-control" ng-model="value.value">
<span class="input-group-addon" ng-click="modalService.openTextEditModal(value)">
<span class="glyphicon glyphicon-align-justify"></span>
</span>
</div>
The modal service and modal template would then reference the object, in this case a clone of the object to help with state management, not the text:
app.factory('ModalService', function($modal) {
return {
openTextEditModal: function(item) {
var modalInstance = $modal.open({
templateUrl: 'modal.html',
backdrop: 'static',
controller: function($scope, $modalInstance, $sce, item) {
var clone = {};
angular.copy(item, clone);
$scope.clone = clone;
$scope.close = function() {
$modalInstance.dismiss('cancel');
};
$scope.save = function() {
angular.extend(item, clone);
$modalInstance.close();
};
},
size: 'lg',
resolve: {
item: function() {
return item;
}
}
});
}
};
});
With the corresponding modal template changes:
<div class="modal-header">
<h3 class="modal-title">Edit text</h3>
</div>
<div class="modal-body">
<textarea class="form-control" ng-model="clone.value"></textarea>
</div>
<div class="modal-body">
<button class="btn btn-warning" ng-click="close()">Close</button>
<button class="btn btn-success pull-right" ng-click="save()">Save</button>
</div>
It might be easier to make a controller for your modal and pass in the objects that you need from your scope. Those will be passed by reference so changes to them will update the scope of your parent scope. Something like this in your MainCtrl :
var modalInstance = ModalService.open({
templateUrl: 'modal.html',
controller: 'YourModalController',
size: 'lg',
resolve: {
text: function () {
return $scope.editText;
}
}
});
modalInstance.result.then(function () {
});
And then in your modal controller:
app.controller('YourModalController', ['$scope', '$modalInstance', 'text', function YourModalController($scope, $modalInstance, text) {
$scope.text = text;
$scope.close = function() {
$modalInstance.dismiss('cancel');
};
$scope.save = function() {
$modalInstance.close($scope.text);
};
}]);
And if you want it to be reusable so you do not have to duplicate the modal instance code in the parent controller you could make that a directive.
You can return the promise and then handle the success callback in the controller.
In the openTextEditModal function, return modalInstance.result;.
Then, in the controller you can do this:
$scope.editText = function(text){
ModalService.openTextEditModal(text).then(function(newText){
console.log(newText);
$scope.text = newText; // do something with the new text
});
};