Refactoring. How to decouple Calls to Services and $scope values modification? - angularjs

We are working with two models: Weeks and Days. Every Day belongs to a Week.
An we got two views: week-detail and day-detail
How could we decouple the Calls to Service and the $scope values modification?
(maybe using AngularJS Directives)
.
Week Detail View has the following template, there are listed all Days that belong to the specific Week
<!-- file 1: week-detail.tpl.html -->
<table>
<tr ng-repeat="day in weekdetail.days">
<td>
<span ng-click="toggleActive(day.id, $index)">
{{ day.active }}
</span>
</td>
</tr>
</table>
The Controller that commands this View is the following:
// file 2: week-detail.controller.js
function WeekDetailCtrl ($scope, $routeParams, Week, Days, DayToggleActive) {
this.week = Week.get({ id: $routeParams.id });
this.days = Days.query({ id: $routeParams.id });
// #param dayId: id to be processed by DayToggleActvive.toggleActive() service
// #param position: $index from ng-repeat, to modify this specific DOM element
$scope.toggleActive = function(dayId, position) {
// call to service
(DayToggleActive.toggleActive({ id: dayId }))
.$promise
.then(function(data) {
// $scope values manipulation
$scope.weekdetail.week = data;
$scope.weekdetail.days[position].active = !$scope.weekdetail.days[position].active;
});
};
}
Day Detail View has the following template:
<!-- file 3: day-detail.tpl.html -->
<div ng-click="toggleDetailActive(daydetail.day.id)">
{{ daydetail.day.active }}
</div>
The Controller that commands this View is the following:
// file 4: day-detail.controller.js
function DayDetailCtrl ($scope, $routeParams, Day, DayToggleActive) {
this.day = Day.get({ id: $routeParams.id });
this.ods = Ods.query({ id: $routeParams.id });
// #param dayId: id to be processed by DayToggleActvive.toggleActive() service
$scope.toggleDetailActive = function(dayId) {
// call to same service
(DayToggleActive.toggleActive({ id: dayId }))
.$promise
.then(function(data) {
// $scope value manipulation is different in Day Detail View
$scope.daydetail.day.active = !$scope.daydetail.day.active;
});
};
}
.
Thank you in advance for your help
UPDATE
Thanks to help of Oddman, we have proceeded with an advance
We have added a Custom Directive Tag to HTML
<!-- file 3: day-detail.tpl.html -->
<div toggleActive="daydetail.day">
{{ daydetail.day.active }}
</div>
And added the directive to module
// file 4: day-detail.controller.js
module.directive('toggleActive', toggleActive);
function toggleActive() {
return {
scope: {day: '=toggleActive'},
restrict: "A",
link: function(scope, element, attrs ) {
element.bind("click", function() {
scope.day.active = !scope.day.active;
});
}
}
}
That looks better, but... what we need is to toggle scope.day.active after successfully call to Service, so...
// file 4: day-detail.controller.js
// inject DayToggleActive service dependency
module.directive('toggleActive', ['DayToggleActive', toggleActive]);
function toggleActive(DayToggleActive) {
return {
scope: {day: '=toggleActive'},
restrict: "A",
link: function(scope, element, attrs ) {
element.bind("click", function() {
(DayToggleActive.toggleActive({ id: scope.day.id }))
.$promise
.then(function(data) {
scope.day.active = !scope.day.active;
})
.catch(function(response) {
console.log(response);
});
});
}
}
}
Previously, the reference was in controller declaration, so let's remove it
// file 4: day-detail.controller.js
function DayDetailCtrl ($scope, $routeParams, Day) {
...
NOW
Week Detail View can be refactored the same way
<!-- file 1: week-detail.tpl.html -->
<table>
<tr ng-repeat="day in weekdetail.days">
<td>
<span toggleDayActive="day">
{{ day.active }}
</span>
</td>
</tr>
</table>
And Day Detail Controller will include a similar directive to which we defined for Week Detail Controller
// file 2: week-detail.controller.js
// inject DayToggleActive service dependency
module.directive('toggleDayActive', ['DayToggleActive', toggleDayActive]);
function toggleDayActive(DayToggleActive) {
return {
scope: {day: '=toggleDayActive'},
restrict: "A",
link: function(scope, element, attrs ) {
element.bind("click", function() {
(DayToggleActive.toggleActive({ id: scope.day.id }))
.$promise
.then(function(data) {
scope.day.active = !scope.day.active;
// HOW COULD WE ACCESS PREVIUSLY REFERENCED AS $scope.weekdetail.week ?
})
.catch(function(response) {
console.log(response);
});
});
}
}
}
How could we regain access to previously referenced as $scope.weekdetail.week ?
Do we need to pass that as parameter in an way?
Is there a way to reach that $scope?

I would setup directives for your toggling, and just pass the day or week to that. For example:
<div toggle-week="week"></div>
Then in your directive, something like:
module.directive('toggleWeek', function() {
return {
scope: {week: '=toggleWeek'},
link: function(scope, element, attrs) {
// add your scope function here
}
};
});
You can do a similar thing for your daily toggle. Then you just can just deal with the week/day binding in your directive rather than working directly on the $scope.
Hope that helps.

Related

ng-click does not work in directive of inline magnific popup

I have created magnific popup directive in angularjs. It works fine ng-click event on description link when first time loads but after close popup and again click on description link it does not work.
Please refer below code:
PhotoCtrl.js
(function () {
'use strict';
function PhotoCtrl($rootScope, $scope, $state, HzServices, HzPhotoService) {
$scope.doAlbumList = function () {
var data = {q: $state.params.pid};
$scope.albums = {};
$scope.albumsCount = 0;
$scope.photos = {};
$scope.photosCount = 0;
$rootScope.arrData = [];
var deferred = HzServices.deferred("/api/album/list", 'GET', data);
deferred.then(
function (res) {
/*
* success in repsonse
* Share common photo & album data across all controllers, directives by services.
*/
var data = {album: {count: res.data.count.album, data: res.data.album}, photo: {count: res.data.count.photo, data: res.data.photo}};
/*
* Create an array of magnific inline popup content
*/
angular.forEach(data.photo.data, function (value, key) {
if (value.data.length > 0) {
angular.forEach(value.data, function (value_, key_) {
$rootScope.arrData.push({
imageDescription: value_.photo_description,
imageScale_img: "/resize/" + value_.module + "/" + value_.photo_name,
imageOriginal_href: "/" + value_.module + "/" + value_.photo_name
});
});
}
});
HzPhotoService.setSharedData(data);
$scope.albums = $rootScope.sharedData.album.data;
$scope.albumsCount = $rootScope.sharedData.album.count;
$scope.photos = $rootScope.sharedData.photo.data;
$scope.photosCount = $rootScope.sharedData.photo.count;
},
function (res) {
/*
* Error hading in repsonse
*/
var data = {album: {count: $scope.albumsCount, data: $scope.albums}, photo: {count: $scope.photosCount, data: $scope.photos}};
HzPhotoService.setSharedData(data);
}
);
}
/**
* Get Photos data & count
* #returns {Array}
*/
$scope.doGetPhoto = function () {
return [{photos: $scope.photos, photoCount: $scope.photoCount}];
}
$scope.doEditDescription = function () {
console.log("description links from controller called");
}
angular
.module("AppWhizbite")
.controller('PhotoCtrl', ['$rootScope', '$scope', '$state', 'HzServices', 'HzPhotoService', PhotoCtrl]);
}());
photoList.html
<div>
<div class="total_album_photo gallery" ng-repeat-start="photo in photos track by $index">
<div class="no_of_photo imgWrapper">
<a href="javascript:void(0);" class="popup-link" data-index="{{$index}}">
<img ng-src="/resize/photo/{{photo.photo_name}}" height="120" width="120"/>
</a>
</div>
</div>
<div ng-repeat-end=""><hz-photo-popup></hz-photo-popup></div>
</div>
hzPhotoDirective.js
(function () {
'use strict';
angular
.module("AppWhizbite")
.directive("hzPhotoPopup", ["$rootScope", "$compile", "HzPhotoService", function ($rootScope, $compile, HzPhotoService) {
var magnificMarkup = "\n\
<form ng-controller=\"PhotoCtrl as Photo\" class=\"white-popup-block popMarkup ng-pristine ng-valid ng-scope\" id=\"dataPopup\" >\n\
<div class=\"popup_heading\">Photo</div>\n\
<div id=\"img_center\">\n\
<img style=\"width:100%\" src=\"\" id=\"img_center_content\" class=\"mfp-imageScale\">\n\
</div>\n\
<div class=\"popup_main\">\n\
<div class=\"popup_left photo_popup_left\">\n\
<div class=\"popup_raw1\">\n\
Edit description\n\
<div class=\"mfp-imageDescription\" style=\"cursor:pointer;\" ng-click=\"doEditDescription()\"></div>\n\
<textarea class=\"submitByEnter commentarea mfp-imageDescription\" placeholder=\"Edit description\" style=\"height: 76px;display:none;\"></textarea>\n\
</div>\n\
</div>\n\
</div>\n\
<div class=\"video_main\">\n\
</div>\n\
<button class=\"mfp-close\" type=\"button\" title=\"Close (Esc)\">×</button>\n\
</form>";
return {
restrict: "AE",
replace: false,
scope: true,
compile: function (scope, element) {
return{
pre: function (scope, element, attrs) {
if (scope.$last) {
// Iterate through all thumbnails class to bind magnific popup plugins
angular.forEach(angular.element(".gallery > .imgWrapper > a"), function (val, key) {
angular.element(".popup-link").eq(key).magnificPopup({
key: 'my-popup',
//items: arrData, // Array of media details
items: $rootScope.arrData, // Array of media details
index: key, // Index of media ref: data-index
type: 'inline',
verticalFit: true, // Fits image in area vertically
inline: {
// Magnific popup custom markup to show media (photo) gallery
markup: $compile(magnificMarkup)(scope)
},
gallery: {
enabled: true
},
callbacks: {
open: function () {
console.log("open called");
},
change: function () {
console.log("cahnge callaed");
},
markupParse: function (template, values, item) {
// optionally apply your own logic - modify "template" element based on data in "values"
// console.log('Parsing:', template, values, item);
console.log("markup parse called");
},
elementParse: function (item) {
console.log("element parse called");
}
}
});
});
}
}
}
},
link: function (scope, element, attrs) {
console.log("link method called");
}
}
}]);
}());
After R&D I crack the issue.
Magnific Popup callbacks objects have markupParse() method. It calls in every action of popup, so I put my angular js template $compile in markupParse method and it works fine.
It may different as per conditions or situations, but almost in all conditions it works finally fine.
Code:
inline: {
// Magnific popup custom markup to show media (photo) gallery
markup: magnificMarkup
},
callbacks:{
markupParse: function (template, values, item) {
$compile(template)(scope);
}
}
In the markupParse method having 3 parameters:
template : template holds actual HTML template which use in popup.
values: value holds current indexed value from arrData
item: item hold current item object.

angularjs - Sharing data between controllers through service

I have a 2 controllers [FirstController,SecondController] sharing two arrays of data (myFileList,dummyList) through a service called filecomm.
There is one attribute directive filesread with isolated scope that is bound to a file input in order to get the array of files from it.
My problem is that myFileList array in my service never gets updated when I select the files with the input. However, dummyList array gets updated immediately in the second div (inner2). Does anybody know why is this happening?
For some reason in the second ngrepeat when I switch from (fi in secondCtrl.dummyList) to (fi in secondCtrl.myFileList) it stops working.
Any help would be greatly appreciated.
Markup
<div ng-app="myApp" id="outer">
<div id="inner1" ng-controller="FirstController as firstCtrl">
<input type="file" id="txtFile" name="txtFile"
maxlength="5" multiple accept=".csv"
filesread="firstCtrl.myFileList"
update-data="firstCtrl.updateData(firstCtrl.myFileList)"/>
<div>
<ul>
<li ng-repeat="item in firstCtrl.myFileList">
<fileuploadrow my-file="item"></fileuploadrow>
</li>
</ul>
</div>
<button id="btnUpload" ng-click="firstCtrl.uploadFiles()"
ng-disabled="firstCtrl.disableUpload()">Upload
</button>
</div>
<div id="inner2" ng-controller="SecondController as secondCtrl">
<ul ng-repeat="fi in secondCtrl.dummyList">
<li>Hello</li>
</ul>
</div>
</div>
JS
angular.module('myApp', [])
.controller('FirstController',
['$scope','filecomm',function ($scope,filecomm) {
this.myFileList = filecomm.myFileList;
this.disableUpload = function () {
if (this.myFileList) {
return (this.myFileList.length === 0);
}
return false;
};
this.uploadFiles = function () {
var numFiles = this.myFileList.length;
var numDummies = this.dummyList.length;
filecomm.addDummy('dummy no' + numDummies + 1);
console.log('Files uploaded when clicked:' + numFiles);
console.log('dummy is now:'+ this.dummyList.length);
};
this.updateData = function(newData){
filecomm.updateData(newData);
console.log('updated data first controller:' + newData.length);
};
this.dummyList = filecomm.dummyList;
console.log('length at init:' + this.myFileList.length);
}]) //FirstController
.controller('SecondController',
['$scope', 'filecomm', function($scope,filecomm) {
var self = this;
self.myFileList = filecomm.myFileList;
self.dummyList = filecomm.dummyList;
console.log('SecondController myFileList - length at init:' +
self.myFileList.length);
console.log('ProgressDialogController dummyList - length at init:' +
self.dummyList.length);
}]) //Second Controller
.directive('filesread',[function () {
return {
restrict: 'A',
scope: {
filesread: '=',
updateData: '&'
},
link: function (scope, elm, attrs) {
scope.$watch('filesread',function(newVal, oldVal){
console.log('filesread changed to length:' +
scope.filesread.length);
});
scope.dataFileChangedFunc = function(){
scope.updateData();
console.log('calling data update from directive:' +
scope.filesread.length);
};
elm.bind('change', function (evt) {
scope.$apply(function () {
scope.filesread = evt.target.files;
console.log(scope.filesread.length);
console.log(scope.filesread);
});
scope.dataFileChangedFunc();
});
}
}
}]) //filesread directive
.directive('fileuploadrow', function () {
return {
restrict: 'E',
scope: {
myFile: '='
},
template: '{{myFile.name}} - {{myFile.size}} bytes'
};
}) //fileuploadrow directive
.service('filecomm', function FileComm() {
var self = this;;
self.myFileList = [];
self.dummyList = ["item1", "item2"];
self.updateData = function(newData){
self.myFileList= newData;
console.log('Service updating data:' + self.myFileList.length);
};
self.addDummy = function(newDummy){
self.dummyList.push(newDummy);
};
}); //filecomm service
Please see the following
JSFiddle
How to test:
select 1 or more .csv file(s) and see each file being listed underneath.
For each file selected the ngrepeat in the second div should display Hello. That is not the case.
Change the ngrepat in the second div to secondCtrl.dummyList
Once you select a file and start clicking upload, you will see that for every click a new list item is added to the ul.
Why does dummyList gets updated and myFileList does not?
You had a couple of issues.
First, in the filecomm service updateData function you were replacing the list instead of updating it.
Second, the change wasn't updating the view immediately, I solved this by adding $rootScope.$apply which forced the view to update.
Updated JSFiddle, let me know if this isn't what you were looking for https://jsfiddle.net/bdeczqc3/76/
.service('filecomm', ["$rootScope" ,function FileComm($rootScope) {
var self = this;
self.myFileList = [];
self.updateData = function(newData){
$rootScope.$apply(function(){
self.myFileList.length = 0;
self.myFileList.push.apply(self.myFileList, newData);
console.log('Service updating data:' + self.myFileList.length);
});
};
}]); //filecomm service
Alternately you could do the $scope.$apply in the updateData function in your FirstController instead of doing $rootScope.$apply in the filecomm service.
Alternate JSFiddle: https://jsfiddle.net/bdeczqc3/77/
this.updateData = function(newData){
$scope.$apply(function(){
filecomm.updateData(newData);
console.log('updated data first controller:' + newData.length);
});
};

angularjs scope function of a repeated directive

I am trying to have a directive with a repeat on it and have it call a function on the parent control as well as child controls. however when I add a scope: { function:&function}
the repeat stops working properly.
fiddle
the main.html is something like
<div ng-app="my-app" ng-controller="MainController">
<div>
<ul>
<name-row ng-repeat="media in mediaArray" on-delete="delete(index)" >
</name-row>
</ul>
</div>
</div>
main.js
var module = angular.module('my-app', []);
function MainController($scope)
{
$scope.mediaArray = [
{title: "predator"},
{title: "alien"}
];
$scope.setSelected = function (index){
alert("called from outside directive");
};
$scope.delete = function (index) {
alert("calling delete with index " + index);
}
}
module.directive('nameRow', function() {
return {
restrict: 'E',
replace: true,
priority: 1001, // since ng-repeat has priority of 1000
controller: function($scope) {
$scope.setSelected = function (index){
alert("called from inside directive");
}
},
/*uncommenting this breaks the ng-repeat*/
/*
scope: {
'delete': '&onDelete'
},
*/
template:
' <li>' +
' <button ng-click="delete($index);">' +
' {{$index}} - {{media.title}}' +
' </button>' +
' </li>'
};
});
As klauskpm said is better to move common logic to an independent service or factory. But the problem that i see is that the ng-repeat is in the same element of your directive. Try embed your directive in an element inside the loop and pass the function in the attribute of that element or create a template in your directive that use the ng-repeat in the template
<li ng-repeat="media in mediaArray" >
<name-row on-delete="delete(media)" ></name-row>
</li>
As I've suggested you, the better approach to share methods is building a Factory or a Service, just like bellow:
app.factory('YourFactory', function(){
return {
setSelected: function (index){
alert("called from inside directive");
}
}
};
And you would call it like this:
function MainController($scope, YourFactory) {
$scope.setSelected = YourFactory.setSelected;
// Could even use $scope.yf = YourFactory;, and call yf.setSelected(index);
// at your view.
(...)
module.directive('nameRow', function(YourFactory) {
(...)
$scope.setSelected = YourFactory.setSelected;
(...)
Hope it will help you.

Angular : how to re-render compiled template after model update?

I am working on an angular form builder which generate a json.
Everything works fine except one thing.
You can find an example here : http://jsfiddle.net/dJRS5/8/
HTML :
<div ng-app='app'>
<div class='formBuilderWrapper' id='builderDiv' ng-controller="FormBuilderCtrl" >
<div class='configArea' data-ng-controller="elementDrag">
<h2>drag/drop</h2>
<form name="form" novalidate class='editBloc'>
<div data-ng-repeat="field in fields" class='inputEdit'>
<data-ng-switch on="field.type">
<div class='labelOrder' ng-class='{column : !$last}' drag="$index" dragStyle="columnDrag" drop="$index" dropStyle="columnDrop">{{field.type}}
</div>
<label for="{{field.name}}" data-ng-bind-html-unsafe="field.caption"></label>
<input data-ng-switch-when="Text" type="text" placeholder="{{field.placeholder}}" data-ng-model="field.value" />
<p data-ng-switch-when="Text/paragraph" data-ng-model="field.value" data-ng-bind-html-unsafe="field.paragraph"></p>
<span data-ng-switch-when="Yes/no question">
<p data-ng-bind-html-unsafe="field.yesNoQuestion"></p>
<input type='radio' name="yesNoQuestion" id="yesNoQuestion_yes" value="yesNoQuestion_yes" />
<label for="yesNoQuestion_yes">Oui</label>
<input type='radio' name="yesNoQuestion" id="yesNoQuestion_no" value="yesNoQuestion_no"/>
<label for="yesNoQuestion_no">Non</label>
</span>
<p data-ng-switch-when="Submit button" class='submit' data-ng-model="field.value">
<input value="{{field.name}}" type="submit">
</p>
</data-ng-switch>
</div>
</form>
</div>
<div id='previewArea' data-ng-controller="formWriterCtrl">
<h2>preview</h2>
<div data-ng-repeat="item in fields" content="item" class='templating-html'></div>
</div>
</div>
</div>
The JS :
var app = angular.module('app', []);
app.controller('FormBuilderCtrl', ['$scope', function ($scope){
$scope.fields = [{"type":"Text/paragraph","paragraph":"hello1"},{"type":"Yes/no question","yesNoQuestion":"following items must be hidden","yes":"yes","no":"no"},{"type":"Text/paragraph","paragraph":"hello2"},{"type":"Submit button","name":"last item"}] ;
}]);
app.controller('elementDrag', ["$scope", "$rootScope", function($scope, $rootScope, $compile) {
$rootScope.$on('dropEvent', function(evt, dragged, dropped) {
if($scope.fields[dropped].type == 'submitButton' || $scope.fields[dragged].type == 'submitButton'){
return;
}
var tempElement = $scope.fields[dragged];
$scope.fields[dragged] = $scope.fields[dropped];
$scope.fields[dropped] = tempElement;
$scope.$apply();
});
}]);
app.directive("drag", ["$rootScope", function($rootScope) {
function dragStart(evt, element, dragStyle) {
if(element.hasClass('column')){
element.addClass(dragStyle);
evt.dataTransfer.setData("id", evt.target.id);
evt.dataTransfer.effectAllowed = 'move';
}
};
function dragEnd(evt, element, dragStyle) {
element.removeClass(dragStyle);
};
return {
restrict: 'A',
link: function(scope, element, attrs) {
if(scope.$last === false){
attrs.$set('draggable', 'true');
scope.dragStyle = attrs["dragstyle"];
element.bind('dragstart', function(evt) {
$rootScope.draggedElement = scope[attrs["drag"]];
dragStart(evt, element, scope.dragStyle);
});
element.bind('dragend', function(evt) {
dragEnd(evt, element, scope.dragStyle);
});
}
}
}
}]);
app.directive("drop", ['$rootScope', function($rootScope) {
function dragEnter(evt, element, dropStyle) {
element.addClass(dropStyle);
evt.preventDefault();
};
function dragLeave(evt, element, dropStyle) {
element.removeClass(dropStyle);
};
function dragOver(evt) {
evt.preventDefault();
};
function drop(evt, element, dropStyle) {
evt.preventDefault();
element.removeClass(dropStyle);
};
return {
restrict: 'A',
link: function(scope, element, attrs) {
if(scope.$last === false){
scope.dropStyle = attrs["dropstyle"];
element.bind('dragenter', function(evt) {
dragEnter(evt, element, scope.dropStyle);
});
element.bind('dragleave', function(evt) {
dragLeave(evt, element, scope.dropStyle);
});
element.bind('dragover', dragOver);
element.bind('drop', function(evt) {
drop(evt, element, scope.dropStyle);
var dropData = scope[attrs["drop"]];
$rootScope.$broadcast('dropEvent', $rootScope.draggedElement, dropData);
});
}
}
}
}]);
app.controller('formWriterCtrl', ['$scope', function ($scope){
}]);
app.directive('templatingHtml', function ($compile) {
var previousElement;
var previousIndex;
var i=0;
var inputs = {};
var paragraphTemplate = '<p data-ng-bind-html-unsafe="content.paragraph"></p>';
var noYesQuestionTemplate = '<p data-ng-bind-html-unsafe="content.yesNoQuestion"></p><input id="a__index__yes" type="radio" name="a__index__"><label for="a__index__yes" />{{content.yes}}</label><input id="a__index__no" class="no" type="radio" name="a__index__" /><label for="a__index__no">{{content.no}}</label>';
var submitTemplate = '<p class="submit"><input value="{{content.name}}" type="submit" /></p>';
var getTemplate = function(contentType, contentReplace, contentRequired) {
var template = '';
switch(contentType) {
case 'Text/paragraph':
template = paragraphTemplate;
break;
case 'Yes/no question':
template = noYesQuestionTemplate;
break;
case 'Submit button':
template = submitTemplate;
break;
}
template = template.replace(/__index__/g, i);
return template;
}
var linker = function(scope, element, attrs) {
i++;
elementTemplate = getTemplate(scope.content.type);
element.html(elementTemplate);
if(previousElement == 'Yes/no question'){
element.children().addClass('hidden');
element.children().addClass('noYes'+previousIndex);
}
if(scope.content.type == 'Yes/no question'){
previousElement = scope.content.type;
previousIndex = i;
}
$compile(element.contents())(scope);
}
return {
restrict: "C",
link: linker,
scope:{
content:'='
}
};
});
On the example there are 2 areas :
- the first one does a ngRepeat on Json and allow to reorder items with drag and drop
- the second area also does a ngRepeat, it is a preview templated by a directive using compile function. Some elements are hidden if they are after what I called "Yes/no question"
Here is an example of Json generated by the form builder :
$scope.fields =
[{"type":"Text/paragraph","paragraph":"hello1"},{"type":"Yes/no question","yesNoQuestion":"following items must be hidden","yes":"yes","no":"no"},
{"type":"Text/paragraph","paragraph":"hello2"},{"type":"Submit button","name":"last item"}] ;
When the page load everything is ok, Hello1 is visible and Hello2 is hidden.
But when I drop Hello1 after "Yes/no question", dom elements are reorganised but Hello1 is not hidden.
I think it comes from $compile but I don't know how to resolve it.
Could you help me with this please?
Thank you
I only see you setting the 'hidden' class on the element based on that rule (after a yes/no) in the link function. That's only called once for the DOM element - when it's first created. Updating the data model doesn't re-create the element, it updates it in place. You would need a mechanism that does re-create it if you wanted to do it this way.
I see three ways you can do this:
In your linker function, listen for the same dropEvent that you listen for above. This is more efficient than you'd think (it's very fast) and you can re-evaluate whether to apply this hidden class or not.
Use something like ngIf or literally re-creating it in your collection to force the element to be recreated entirely. This is not as efficient, but sometimes is still desirable for various reasons.
If your use case is actually this simple (if this wasn't a redux of something more complicated you're trying to do) you could use CSS to do something like this. A simple rule like
.yes-no-question + .text-paragraph { display: none; }
using a sibling target could handle this directly without as much work. This is much more limited in what it can do, obviously, but it's the most efficient option if it covers what you need.

how to exec js after AngularJS ng-repeat finished

I'm new to AngularJS. I want to use ng-repeat to render a list of data.
Each of the data should have a <abbr class="timeago" title="2012-10-10 05:47:21"></abbr> alike after rendered. And then I could use jquery plugin timeago to turn it into human friendly text about 1 hour ago.
My code is as below. But it take no effect. Please help.
EDIT: My problem is that, I can get the right html rendered. But code in directive do not run.
the html:
<div ng-app='weiboApp' ng-controller="WeiboListCtrl">
<table><tbody>
<tr ng-repeat='w in weibo' weiboLister='w'>
<td>{{ w.text }}></td>
<td><abbr class="timeago" title="{{ w.created }}"></abbr></td>
</tr>
</tbody></table>
</div>
the js:
var module = angular
.module('weiboApp', [])
.directive('weiboLister', function () {
return {
restrict: 'A',
link: function (scope, element, attr) {
scope.watch('w', function (val) {
console.log(element); //this never run
element.find("abbr.timeago").timeago(); //this never run
}, true);
}
}
});
function WeiboListCtrl($scope, $http) {
$http.get('/api/weibo/list').success(function(data) {
$scope.weibo = data;
});
}
The problem turned out to be: should define directive with camel-case weiboLister and use it in html with snake-case weibo-lister. Thanks to #tosh shimayama.
The correct code as below: (I added a remove function in case you're looking for the same thing.)
the html:
<div ng-app='weiboApp' ng-controller="WeiboListCtrl">
<table><tbody>
<tr ng-repeat='w in weibo' weibo-lister='w'> <!--important to be snake-case here-->
<td>{{ w.text }}></td>
<td><abbr class="timeago" title="{{ w.created }}"></abbr></td>
<td><a ng-click='remove(w)'>×</a></td>
</tr>
</tbody></table>
</div>
the js:
var module = angular
.module('weiboApp', [])
.directive('weiboLister', function () {
function delete(id, s_function, f_function) {
//...
if success { s_function(); }
else { f_function(); }
}
return {
restrict: 'A',
link: function (scope, element, attr) {
scope.$watch('w', function (val) {
element.find("abbr.timeago").timeago();
}
scope.destroy = function(callback) {
deletenews(scope.w.id, function () {
//s_function, things you want to do when delete with success
element.fadeOut(400, function () {
//this callback will splice the element in model
if (callback) callback.apply(scope);
})
}, function () {
//f_function, when delete with failure
});
};
}
}
});
function WeiboListCtrl($scope, $http) {
$http.get('/api/weibo/list').success(function(data) {
$scope.weibo = data;
});
$scope.removeWeibo = function(w) {
var idx = $scope.weibo.indexOf(w);
if (idx !== -1) {
this.destroy(function() {
$scope.weibo.splice(idx, 1);
});
}
};
}

Resources