How to make a link function change a scope variable - angularjs

I am trying to use AngularJS to make a model that responds to a click from the user. I have all the information for the model in the controller (other elements will be added to the scope later), and the directive handles showing the element in the view and performing actions on it, such as minimizing it on click.
What I'm having trouble with is making the link function in my makeDialog directive change the value of experimentDialog.minimized in the model. How can I accomplish this? What am I doing wrong?
Right now, nothing happens when you click the button -- so help troubleshooting is also appreciated.
Let me know if you need any more information!
Angular:
angular.module('root', [])
.controller('index', ['$scope', function($scope){
$scope.experimentDialog = {
minimized: false,
width: 200,
height: 300,
top: 10,
left: 10,
template: 'experiment-dialog.html'
};
}])
.directive('makeDialog', function() {
return {
restrict: 'E',
scope: {
model: '='
},
templateUrl: 'dialog.html',
link: function(scope, element, attrs) {
scope.minimize = function() {
scope.model.minimized = true;
scope.$apply();
};
}
};
});
dialog.html:
<html>
<link href='dialog.css' rel='stylesheet' type='text/css'/>
<body>
<div class='dialog' ng-hide={{model.minimized}}>
<button id='minimize' ng-click="minimize()"> _ </button>
</div>
</body>
</html>
EDIT: if it helps, here is the whole thing on Plunker:
http://plnkr.co/edit/3S9q53VfGqRrWbVgR5XU?p=preview

So, your button does change the model. The problem was your ng-hide syntax. I display the model in the box so you can see the values.
<div class='dialog' ... ng-hide="model.minimized"> <!-- no {{}}! -->
This forked plunkr has, what I believe to be, your intended effect.

Related

AngularJS: What is the best way to bind a directive value to a service value changed via a controller?

I want to create a "Header" service to handle the title, buttons, and color of it.
The main idea is to be able to customize this header with a single line in my controllers like this:
function HomeCtrl($scope, Header) {
Header.config('Header title', 'red', {'left': 'backBtn', 'right': 'menuBtn'});
}
So I created a service (for now I'm only focussing on the title):
app.service('Header', function() {
this.config = function(title, color, buttons) {
this.title = title;
}
});
...And a directive:
app.directive('header', ['Header', function(Header) {
return {
restrict: 'E',
replace: true,
template: '<div class="header">{{title}}</div>',
controller: function($scope, $element, $attrs) {
$scope.$watch(function() { return Header.title }, function() {
$scope.title = Header.title;
});
}
};
}]);
So, this actually works but I'm wondering if there are no better way to do it.
Especially the $watch on the Header.title property. Doesn't seem really clean to me.
Any idea on how to optimize this ?
Edit: My header is not in my view. So I can't directly change the $scope value from my controller.
Edit2: Here is some of my markup
<div class="app-container">
<header></header>
<div class="content" ng-view></div>
<footer></footer>
</div>
(Not sure this piece of html will help but I don't know which part would actually...)
Thanks.
If you are using title in your view, why use scope to hold the object, rather than the service? This way you would not need a directive to update scope.header, as the binding would update it if this object changes
function HomeCtrl($scope, Header) {
$scope.header = Header.config('Header title', 'red', {'left': 'backBtn', 'right': 'menuBtn'});
}
and refer to title as
<h1>{{header.title}}</h1>
Update
Put this in a controller that encapsulates the tags to bind to the header:
$scope.$on("$routeChangeSuccess", function($currentRoute, $previousRoute) {
//assume you can set this based on your $routeParams
$scope.header = Header.config($routeParams);
});
Simple solution may be to just add to rootScope. I always do this with a few truly global variables that every controller will need, mainly user login data etc.
app.run(function($rootScope){
$rootScope.appData={
"header" : {"title" : "foo"},
"user" :{}
};
});
.. then inject $rootScope into your controllers as warranted.

Call a function in a angular-controller from outside of the controller?

I have a lightbox-dierective and controller that looks like this:
directive('modalDialog', function() {
return {
restrict: 'E',
scope: {
show: '='
},
replace: true, // Replace with the template below
transclude: true, // we want to insert custom content inside the directive
template: '<div class="ng-modal" ng-show="show"><div class="ng-modal-overlay" ng-click="hideModal()"></div><div class="ng-modal-dialog" ng-style="dialogStyle"><div class="ng-modal-dialog-content" ng-transclude><div class="ng-modal-close" ng-click="hideModal()">X</div></div></div></div>'
};
}).controller('Lightbox', function($scope) {
$scope.modalShown = false;
$scope.toggleModal = function() {
$scope.modalShown = !$scope.modalShown;
};
});
Here is the desierd html, what I need is to open the secon ligthbox from withing the first one:
<div ng-controller="Lightbox">
<span ng-mousedown='toggleModal()'>Open lightbox one</span>
<modal-dialog show='modalShown'>
<h2>One lightbox <span ng-mousedown='toggleModal()'>Open lightbox two</span></h2>
</modal-dialog>
</div>
<div ng-controller="Lightbox">
<span ng-mousedown='toggleModal()'>Open lightbox one</span>
<modal-dialog show='modalShown'>
<h2>another lightbox</h2>
</modal-dialog>
</div>
For most cases it works great! I use it in several occations throughout the site, with diffrent lightboxes and different content.
I have now come across a case, where I need to call one of the lightboxes from outside of the controller. Can this be achieved and in that case how do I reference the right lightbox?
I'd extend that setting to an object
var modalSet = {
shown: false,
toggle: function(){ modalSet.shown = !modalSet.shown }
}
Then put it on your main controller (the one with ngApp attribute) and have your entire scope modaleble.
Also, directives do have a controller option, but since only one modal is gonna show up at any given time, you might not want to re-create a controller for every new instance.
Upon re-reading your question: Where is it exactly -> "outside of the controller"?

Angularjs - Hide content until DOM loaded

I am having an issue in Angularjs where there is a flicker in my HTML before my data comes back from the server.
Here is a video demonstrating the issue: http://youtu.be/husTG3dMFOM - notice the #| and the gray area to the right.
I have tried ngCloak with no success (although ngCloak does prevent the brackets from appearing as promised) and am wondering the best way to hide content until the HTML has been populated by Angular.
I got it to work with this code in my controller:
var caseCtrl = function($scope, $http, $routeParams) {
$('#caseWrap').hide(); // hides when triggered using jQuery
var id = $routeParams.caseId;
$http({method: 'GET', url: '/v1/cases/' + id}).
success(function(data, status, headers, config) {
$scope.caseData = data;
$('#caseWrap').show(); // shows using jQuery after server returns data
}).
error(function(data, status, headers, config) {
console.log('getCase Error', arguments);
});
}
...but I have heard time and time again not to manipulate the DOM from a controller. My question is how can I achieve this using a directive? In other words, how can I hide the element that a directive is attached to until all content is loaded from the server?
In your CSS add:
[ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak {
display: none !important;
}
and just add a "ng-cloak" attribute to your div like here:
<div id="template1" ng-cloak>{{scoped_var}}<div>
doc: https://docs.angularjs.org/api/ng/directive/ngCloak
On your caseWrap element, put ng-show="contentLoaded" and then where you currently have $('#caseWrap').show(); put $scope.contentLoaded = true;
If the caseWrap element is outside this controller, you can do the same kind of thing using either $rootScope or events.
Add the following to your CSS:
[ng\:cloak],[ng-cloak],.ng-cloak{display:none !important}
The compiling of your angular templates isn't happening fast enough.
UPDATE
You should not do DOM manipulation in your controller. There are two thing you can do...
1. You can intercept changes to the value within the scope of the controller via a directive! In your case, create a directive as an attribute that is assigned the property you want to watch. In your case, it would be caseData. If casedata is falsey, hide it. Otherwise, show it.
A simpler way is just use ngShow='casedata'.
Code
var myApp = angular.module('myApp', []);
myApp.controller("caseCtrl", function ($scope, $http, $routeParams, $timeout) {
$scope.caseData = null;
//mimic a delay in getting the data from $http
$timeout(function () {
$scope.caseData = 'hey!';
}, 1000);
})
.directive('showHide', function () {
return {
link: function (scope, element, attributes, controller) {
scope.$watch(attributes.showHide, function (v) {
if (v) {
element.show();
} else {
element.hide();
}
});
}
};
});
HTML
<div ng-controller='caseCtrl' show-hide='caseData'>using directive</div>
<div ng-controller='caseCtrl' ng-show='caseData'>using ngShow</div>
JSFIDDLE:http://jsfiddle.net/mac1175/zzwBS/
Since you asked for a directive, try this.
.directive('showOnLoad', function() {
return {
restrict: 'A',
link: function($scope,elem,attrs) {
elem.hide();
$scope.$on('show', function() {
elem.show();
});
}
}
});
Stick (show-on-load) in your element, and in your controller inject $rootScope, and use this broadcast event when the html has loaded.
$rootScope.$broadcast('show');
I have used Zack's response to create a 'loading' directive, which might be useful to some people.
Template:
<script id="ll-loading.html" type="text/ng-template">
<div layout='column' layout-align='center center'>
<md-progress-circular md-mode="indeterminate" value="" md-diameter="52"></md-progress-circular>
</div>
</script>
Directive:
directives.directive('loading', function() {
return {
restrict: 'E',
template: 'll-loading.html',
link: function($scope,elem,attrs) {
elem.show();
$scope.$on('loaded', function() {
console.log("loaded: ");
elem.hide();
});
}
}
});
This example uses angular-material in the html
The accepted answer didn't work for me. I had some elements that had ng-show directives and the elements would still show momentarily even with the ng-cloak. It appears that the ng-cloak was resolved before the ng-show returned false. Adding the ng-hide class to my elements fixed my issue.

Angular, ng-repeat to build other directives

I'm building a complex layout, that takes a JSON document and then formats it into multiple rows, with each row then having more rows and/or combinations of rows/columns inside them.
I'm new to Angular and am just trying to get to grips with Directives. They are easy to use for very simple things, but quickly become very difficult once you need to anything more complicated.
I guess I'm doing this the wrong way around, but is there a way to simply add the name of a directive (in the example below, I've used ) and get that directive to be rendered on an ng-repeat?
Maybe the same way that you can use {{{html}}} instead of {{html}} inside of mustache to get a partial to render as HTML and not text.
As expected, the example below simply writes the name of the directive into the dom. I need Angluar to take the name of the directive, understand it, and then render before before it is written. Due to the complex layout of the page I need to design, I could be rendering many different directives, all inside each other, all from 1 JSON document (which has been structured into different rows and then row / column combinations).
Example code that renders the name of the directive to the page, but gives you an idea of how I'd like to write a solution the problem...
<div app-pages></div>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.1.5/angular.min.js"></script>
<script>
var app = angular.module("app", ['main']);
angular.module('main', [])
.controller("appPageController", ['$scope', function( $scope ){
$scope.pages = [];
var page1 = {
title: 'Page 1',
directive: '<app-page-type-1>'
};
var page2 = {
title: 'Page 2',
directive: '<app-page-type-2>'
};
$scope.pages.push(page1);
$scope.pages.push(page2);
}])
.directive("appPageType2", function factory() {
console.log('into page type 2');
return {
replace: true,
template: 'This is the second page type'
};
})
.directive("appPageType1", function factory() {
console.log('into page type 1');
return {
replace: true,
template: 'This is the first page type'
};
})
.directive("appPages", function factory() {
console.log('into pages');
return {
replace: true,
template: '<ul><li ng-repeat="page in pages">{{page.directive}}</li></ul>'
};
});
</script>
This is one possible alternative to your idea. The idea is to append the directive you defined in page object for each html element inside the ng-repeat. Please take a look at the demo. Hope it helps.
<div ng-app="myApp" ng-controller="appPageController">
<ul>
<li ng-repeat="page in pages" app-pages></li>
</ul>
</div>
.directive("appPages", function ($compile) {
console.log('into pages');
return {
replace: true,
link: function (scope, elements, attrs) {
var html = '<div ' + scope.page.directive + '></div>';
var e = angular.element(html);
elements.append(e);
$compile(e)(scope);
}
};
});
Demo

AngularJS input with focus kills ng-repeat filter of list

Obviously this is caused by me being new to AngularJS, but I don't know what is the problem.
Basically, I have a list of items and an input control for filtering the list that is located in a pop out side drawer.
That works perfectly until I added a directive to set focus to that input control when it becomes visible. Then the focus works, but the filter stops working. No errors. Removing focus="{{open}}" from the markup makes the filter work.
The focus method was taken from this StackOverflow post:
How to set focus on input field?
Here is the code...
/* impersonate.html */
<section class="impersonate">
<div header></div>
<ul>
<li ng-repeat="item in items | filter:search">{{item.name}}</li>
</ul>
<div class="handle handle-right icon-search" tap="toggle()"></div>
<div class="drawer drawer-right"
ng-class="{expanded: open, collapsed: !open}">
Search<br />
<input class="SearchBox" ng-model="search.name"
focus="{{open}}" type="text">
</div>
</section>
// impersonateController.js
angular
.module('sales')
.controller(
'ImpersonateController',
[
'$scope',
function($scope) {
$scope.open = false;
$scope.toggle = function () {
$scope.open = !$scope.open;
}
}]
);
// app.js
angular
.module('myApp')
.directive('focus', function($timeout) {
return {
scope: { trigger: '#focus' },
link: function(scope, element) {
scope.$watch('trigger', function(value) {
if(value === "true") {
console.log('trigger',value);
$timeout(function() {
element[0].focus();
});
}
});
}
};
})
Any assistance would be greatly appreciated!
Thanks!
Thad
The focus directive uses an isolated scope.
scope: { trigger: '#focus' },
So, by adding the directive to the input-tag, ng-model="search.name" no longer points to ImpersonateController but to this new isolated scope.
Instead try:
ng-model="$parent.search.name"
demo: http://jsbin.com/ogexem/3/
P.s.: next time, please try to post copyable code. I had to make quite a lot of assumptions of how all this should be wired up.

Resources