ng-switch not reacting to dynamic value change - javascript

i am new and experimenting with AngularJS.
That being said, i am unsure how to bind value to an ng-switch and change between views dynamically by changing the 'activeView' variable value.
Side note: is it a good practice to switch between views like this? The views are different ways of displaying the same data so its pretty much the same context and i did not see a reason to create a different page for the two views. Oh and the views contents are actually directives components.
the html:
<body ng-controller="SomeController as vm">
<div ng-switch="{{vm.activeView}}">
<div ng-switch-when="someView">
some directive component
</div>
<div ng-switch-when="anotherView">
another directive component
</div>
<div ng-switch-default>
nothing to show
</div>
</div>
</body>
the controller:
(function () {
'use strict';
angular
.module('app')
.controller('SomeController', SomeController);
function SomeController() {
var vm = this;
vm.activeView = '';
function setViewType(viewType) {
vm.viewTypes = viewType;
}
setViewType('anotherView');
}
})();
So what happen is that the default switch is visible, event after calling setViewType('anotherView');
Thanks in advance!

You don't need the interpolation when using ng-switch ({{vm.activeView}}).
You aren't assigning vm.activeView with a value.
Your angular.module doesn't have a dependency array. If this is the place that you define your module then add it.
(function () {
'use strict';
angular.module('app', []) // add the dependency array
.controller('SomeController', SomeController);
function SomeController() {
var vm = this;
vm.activeView = 'anotherView'; // change this
function setViewType(viewType) {
vm.viewTypes = viewType;
}
setViewType('anotherView'); // this changes the viewTypes prop
}
})();
Check on this JSFIDDLE.

Related

Variables not updating outside function

So, I have two controllers (they have been reduced for simplicity) with one function each. The functions change a variable inside each of the controllers. The thing is, it looks like only the variable in the first controller is updated.
app.js
(function() {
var app = angular.module('ScBO', []);
var token = {};
app.controller("LoginController", ['$http', function($http){
this.userData = {};
var lc = this;
this.in = false;
this.loginGo = function(){
$http.post(<link>, <userData>)
.then(function successCallback(response){
token = response.data.access_token;
lc.in=true;
}, function errorCallback(response){
lc.in=false;
});
};
}]);
app.controller("DashboardController", ['$http', function($http){
var dsb = this;
this.totalLocals = 0;
this.refresh = function(){
$http.get('<link>?access_token='+token)
.then(function successCallback(response){
dsb.totalLocals = response.data.number_of_locals.number_of_locals;
}, function errorCallback(response){
});
};
}]);
})();
index.html
<body ng-controller="LoginController as loginCtrl">
<div id="login-div" ng-hide="loginCtrl.in">
<form ...>
...
</form>
</div>
<div id="dashboard" ng-show="loginCtrl.in" ng-controller="DashboardController as dsb">
<div class="numbers">
<p>Number of Locals</p>
{{dsb.totalLocals}}
</div>
</div>
</body>
So in the html, the first div appears in the beginning and not the second one. Then it hides and the second div shows up. This means that they are following the update of the loginCtrl.in variable.
However, when I call the refresh function, it doesn't update the value in the last div.
I've tried with $scope and have been searching in here but I haven't been able to find a solution yet. Specially since the two controllers are equal but only one of them seems to be updating the variables normally.
So, I've figured out the problem.
In the code above the error doesn't show as I only posted part of the code for simplicity, but I thought it was enough.
The thing was: I had the button that triggers the refresh function in a different div (like #leonardoborges ' plunkr). Like that two controllers are instantiated with their own values, something that I didn't know. The timeout function updates all instances of the controller, so that was confusing me.
So now I just put everything within one instance of the controller.

Why is the ng-class directive being compiled after my custom directives

Link to plnkr for example explained below: http://plnkr.co/edit/oYvwHnAIvFb4rUqqwsz3?p=preview
Hi, I dont understand why angular is compiling my code this way and need some help understanding why it is doing so. I have an outer directive which doing an ng-repeat on and array and creating a new directive for each item in the array. Wrapping around this inner directive is a div which has an ng-class attached to it.
The issue I'm running into is that I want the ng-class to be applied to the wrapping div before the directive's link function is called, but this isn't the case. The controller and link function of all of the nested directives are compiled before any of the wrapping ng-class functions are called. You can see an example of what time talking about in the plnkr I've linked above (check the console to see the order in which things are getting printed). The print order I want it to be is the following:
adding class to inner directive 1
Inner - Controller undefined
Inner - Link 1
adding class to inner directive 2
Inner - Controller undefined
Inner - Link 2
Inner - Controller undefined
...
Any help on understanding this / getting it to compile in the order i need would be great.
relevant code:
html
<div bn-outer>
<div ng-repeat="a in arr">
<div ng-class="classFunction(a)">
<span bn-inner ng-model="model" ng-init="model=a">
directive: {{a}}
</span>
</div>
</div>
</div>
angular
// Create an application module for our demo.
var app = angular.module( "Demo", [] );
app.directive(
"bnOuter",
function() {
function Controller( $scope ) {
console.log( "Outer - Controller" );
$scope.arr = [1,2,3,4,5,6];
$scope.classFunction = function(int){
console.log("adding class to inner directive ", int);
};
}
function link( $scope, element, attributes, controller ) {
console.log( "Outer - Link" );
}
// Return directive configuration.
return({
controller: Controller,
link: link
});
}
);
app.directive(
"bnInner",
function() {
function Controller( $scope ) {
console.log( "Inner - Controller", $scope.model );
}
function link( $scope, element, attributes, controller ) {
console.log( "Inner - Link", $scope.model );
}
// Return directive configuration.
return({
controller: Controller,
link: link
});
}
);
Thanks!
An excellent explanation on this topic can be found here
http://www.bennadel.com/blog/2810-directive-architecture-template-urls-and-linking-order-in-angularjs.htm
And this
http://odetocode.com/blogs/scott/archive/2014/05/28/compile-pre-and-post-linking-in-angularjs.aspx
Basically it sounds you need to play with the 'pre-link' function

Getting ng-repeat to work inside of AngularJS's $interpolate service

I am using the AngularJs-UI components for Bootstrap. I would like to insert a filled out template into one of the data elements for the popover feature. This works find for all elements not inside of a ng-repeat. How do I get the ng-repeat elements to work inside of a interpolated template?
I have a plunker at http://plnkr.co/edit/Cuku7qaUTL1lxRkafKzv Its not working because I don't know how to get Angular-UI-bootstrap to in plunker.
<div data-popover="{{createHTML()}}">some content</div>
My local scope has the function createHTML() that looks something like this.
angular.module('myApp', ['ngSanitize'])
.controller("myController", function(myService){
$scope.createHTML = function() {
var thingy = { blah: "foobar", collection: [ "1", "2", "3" ] };
return myService.html_for(thingy);
}
});
And the service is
angular.module('myApp')
.service('myService', function($templateCache, $interpolate, $sanitize, $log) {
"use strict";
function html_for(thingy) {
var template = $templateCache.get('thingyTemplate.html'),
link = $interpolate(template),
html = link(thingy),
unsafe = $sanitize(html);
return unsafe;
}
return {
html_for: html_for
}
});
Templates:
<script type="text/ng-template" id="thingyTemplate.html">
<div>
<div><strong>Blah:</strong> {{blah}}</div>
<div data-ng-repeat="foo in collection"><strong>Collection:</strong> {{foo}}</div>
<div><strong>Collection[0]:</strong> {{collection[0]}}</div>
<div><strong>Collection[1]:</strong> {{collection[1]}}</div>
<div><strong>Collection[2]:</strong> {{collection[2]}}</div>
</div>
</script>
<script type="text/ng-template" id="template/popover/popover.html">
<div class="popover {{placement}}" data-ng-class="{ in: isOpen(), fade: animation() }">
<div class="arrow"></div>
<div class="popover-inner">
<h3 class="popover-title" data-ng-bind="title" data-ng-show="title"></h3>
<div class="popover-content" data-ng-bind-html="content"></div>
</div>
</div>
</script>
$interpolate doesn't handle directives like ngRepeat (
Difference between parse, interpolate and compile ). $interpolate:
Compiles a string with markup into an interpolation function. This
service is used by the HTML $compile service for data binding.
To handle ngRepeat and other directives you want $compile. But for your use case $compile is going to result, unfortunately, in a number of changes because:
It needs a scope to compile against rather than just a context like $interpolate. Moreover it needs the scope thingy is on.
This means we'll need to reference your properties like so {{thingy.blah}} instead of {{blah}} inside your template.
The compile needs to happen when the popup is on the dom.
The popup is only on the dom when it's open.
So we can't just replace $interpolate with $compile inside your service.
One approach is to replace data-ng-bind-html with the following directive that acts like an ng-bind-html that has a built in $compile (clearly you should only use this with html that you know is safe).
.directive('compile', function($compile) {
return function(scope, element, attrs) {
scope.$watch(
function(scope) {
return scope.$eval(attrs.compile);
},
function(value) {
var result = element.html(value);
$compile(element.contents())(scope.$parent.$parent);
}
);
};
});
Used like so (with compile replacing ng-bind-html:
<div class="popover-content" compile="content"></div>
One issue is that we need thingy to be in scope. There's a few of ways of handling that- but for demonstration purposes I've manually gone back up to the scope the popover is called from - which is 2 scopes up thus the scope.$parent.$parent.
Using this compile directive you no longer $interpolate or $sanitizeso the function in your service can shrink down to just returning the appropriate template:
function html_for() {
var template = $templateCache.get('thingyTemplate.html');
return template;
}
demo fiddle

Testing a directive that depends on another directive - Angularjs

I have a directive that contains another directive. Here is the directive:
<div class="chat-container">
<h5 class="chat-header">
<span class="patient-name-container">{{encounter.patient.firstName }} {{encounter.patient.lastName}}</span></h5>
<div ng-show="showMessages">
<div messages-container messages="encounter.comments"></div>
</div>
</div>
And here is my test:
var element;
beforeEach(module('app/views/chat.container.html'));
beforeEach(inject(function($templateCache, $compile, $rootScope) {
var chatTemplate = $templateCache.get('app/views/chat.container.html');
$templateCache.put('views/chat.container.html', chatTemplate);
var directive = angular.element('<div chat-container max-chat-count="800" class="pull-left"></div>');
element = $compile(directive)($rootScope);
$rootScope.$digest();
}));
it('the remaining count to be 800', function() {
expect(element.find('#counter').text()).toBe('800');
});
});
My error is Error: Unexpected request: GET views/messages.container.html. It is looking for the html to create the messages-container directive, but it cannot find it. I have tried adding the other directive like this right after the first one:
var messagesTemplate = $templateCache.get('app/views/messages.container.html');
$templateCache.put('views/messages.container.html', messagesTemplate);
but I still get the same error, so I have 2 questions.
First - How do I test this?
Second - I am now creating my test with a dependency on another directive, what is the right way to handle that?
The problem was me not adding the template with the module function before I tried to access it. Here is the code that fixed the issue:
var element, scope;
beforeEach(module('app/views/chat.container.html'));
* beforeEach(module('app/views/messages.container.html'));
beforeEach(inject(function($templateCache, $compile, $rootScope) {
var chatTemplate = $templateCache.get('app/views/chat.container.html');
$templateCache.put('views/chat.container.html', chatTemplate);
var messagesTemplate = $templateCache.get('app/views/messages.container.html');
$templateCache.put('views/messages.container.html', messagesTemplate);
var directive = angular.element('<div chat-container max-chat-count="800" class="pull-left"></div>');
element = $compile(directive)($rootScope);
$rootScope.$digest();
scope = $rootScope;
}));
I did not have the line with the * before. Now everything works perfectly.

Dynamic menu bar with angularjs

I'm trying to create a menu bar using Angularjs. I've done similar things before with Backbonejs, but I have a hard time getting my head around how to do this with angular.
In my html file, I have the following menu placeholder.
<div id='menu1'></div>
<div id='menu2'></div>
<div id='menu3'></div>
<div id='menu4'></div>
<div id='menu5'></div>
A number of my angular modules add a menu when they are loaded (in run). Each of them only reserves a particular slot (i.e. menu1..5), so they don't clash. When some modules aren't loaded, their menu would not show in the menu bar.
An angular module would conceptually look like:
angular.module('myModule3', [])
.service('someService', function($http) {
// get some data to populate menu (use $http)
this.menuItems = ['orange', 'apple', 'banana']
})
.run(['someService', function(someService) {
// create a rendered menu item
...
// insert it at id="menu3"
})
For sake of simplicity, the rendered menu item should look like:
<ul>
<li>organge</li>
<li>apple</li>
<li>banana</li>
</ul>
I'm fairly new to angular, so I don't really know where to begin. I've been reading up on directives, but don't see how they fit in here, as they require some custom markup (maybe a custom menu tag containing the DOM target (i.e. menu..5). Also, how to connect this to a controller is not clear to me.
Update
In addition to the above base template (containing arbitrary anchor points in the DOM) and the directive (which will produce a DOM element which will be inserted at these anchor points), a template will facilitate the creation of the DOM element. This template will be located in a separate file containing the position the directive's DOM element will be inserted to (as opposed to the usual case of directives in which an already existing tag will be replaced/inserted into specific markup that matches the directive's definition:
<menu ng-model="Model3DataService" target="#menu3">
<ul>
<li ng-repeat="for item in items"></li>
</ul>
</menu>
Again, coming from a Backbone/jquery background this makes sense, but this may not be the right thing to do in angular. If so, please let me know how I could keep the base template free of any knowledge about the modules and assumptions of where they put their menu (i.e. which slot of the menu bar they allocate). I'm happy to hear about other solutions...
Each module should have its menu loader defined:
angular.module('module1', []).
factory('module1.menuLoader', function() {
return function(callback) {
callback(['oranges', 'bananas'])
}
});
Your application should contain menu directive which can load menu items for any module only if exists.
angular.module('app', ['module1']).
directive('menu', ['$injector', function($injector) {
return {
restrict: 'A',
template:
'<ul><li ng-repeat="item in items">{{item}}</li></ul>',
scope: {},
link: function($scope, $element, $attrs) {
var menuLoaderName = $attrs.menu+'.menuLoader';
if ($injector.has(menuLoaderName)) {
var loaderFn = $injector.get(menuLoaderName);
loaderFn(function(menuItems) {
$scope.items = menuItems;
});
}
}
};
}]);
Final html:
<div class="content">
<div menu="module1"></div>
<div menu="module2"></div>
<div menu="module3"></div>
</div>
After running the application only module1 menu will be loaded. Other menu placeholders remain empty.
Live demo: http://plnkr.co/edit/4tZQGSkJToGCirQ1cmb6
Updated: If you want to generate markup on the module side the best way is to put the template to the $templateCache in the module where it's defined and then pass the templateName to the application.
angular.module('module1', []).
factory('module1.menuLoader', ['$templateCache', function($templateCache) {
$templateCache.put('module1Menu', '<ul><li ng-repeat="item in items">{{item}}</li></ul>');
return function(callback) {
callback('module1Menu', ['oranges', 'bananas'])
}
}]);
angular.module('app', ['module1'])
.directive('menu', ['$injector', function($injector) {
return {
restrict: 'A',
template:
'<div ng-include="menuTemplate"></div>',
scope: {},
link: function($scope, $element, $attrs) {
var menuLoaderName = $attrs.menu+'.menuLoader';
if ($injector.has(menuLoaderName)) {
var loaderFn = $injector.get(menuLoaderName);
loaderFn(function(menuTemplate, menuItems) {
$scope.menuTemplate = menuTemplate;
$scope.items = menuItems;
});
}
}
};
}]);

Categories