Using controller as syntax only updates one of the scopes - javascript

I'm a bit confused when it comes to the controller as syntax as I've never worked with it before. I'd like to know the correct way to fix this. Haven't been able to find a similar problem when searching.
I have a menu and a button which toggles the menu. The menu has its own scope and the button has another, as they live in two separate files and containers.
When I click the button it only updates the nav.isActive within the button scope. I created a service for storing the state, which I shouldn't have to do when I think about it.. Should I? Because the only way to watch if that value changes is with a watcher, which would require me to use $scope since this doesn't have the $watch function, however, I'd like to avoid this as much as I can as it will affect performance which is important in this project.
What is the correct way of updating a "scope" variable from another "scope" when using the controller as syntax?
Controller:
nav.controller('NavCtrl', ['navState', function(navState) {
var nav = this;
nav.menu = [
{icon: 'color_lens', href: ''},
{icon: 'color_lens', href: 'about'},
{icon: 'color_lens', href: 'contact'},
{icon: 'color_lens', href: 'logout'},
{icon: 'color_lens', href: 'faq'}
];
nav.toggleMenu = function() {
nav.isActive = navState.checkState();
}
}]);
The service for passing the value from one scope to the other:
nav.service('navState', [function() {
var isActive = false;
this.checkState = function() {
isActive = !isActive;
return !isActive;
}
}]);
The menu markup (menu.html):
<nav class="si-wrapper si-dark" ng-controller="NavCtrl as nav" ng-class="{active: nav.isActive}">
<ul class="si-list">
<li class="si-item" ng-repeat="item in nav.menu">
<a href="#/{{item.href}}">
<div class="si-inner-item">
<i class="material-icons md-36" ng-bind="item.icon"></i>
</div>
</a>
</li>
</ul>
<h1>{{nav.isActive}}</h1> <!-- This doesn't change -->
</nav>
The button which toggles the menu (header.html):
<div ng-controller="NavCtrl as nav">
<button ng-click="nav.toggleMenu()">Toggle</button>
<span>{{nav.isActive}}</span> <!-- This updates however -->
</div>

Your problem is that nav.isActive is not set for one of the controllers. Basically whenever you use ng-controller a new controller (and $scope) is created. So for each of your controllers, $scope.isActive needs to be set for the related view to reference it.
In your posted code, isActive is only set when toggleMenu() is run, which happens only in header.html.
In order to get your code to work, simply set isActive on controller load. For example:
nav.controller('NavCtrl', ['navState', function(navState) {
// put isActive on the scope on controller load
this.activeState = navState.getState();
nav.toggleMenu = function() {
navState.toggleState();
};
}]);
You need to make your service better by separating state access and state manipulation. Also wrapping it in a container object will ensure you won't have any scope hierarchy issues.
nav.service('navState', [function() {
var state = {
isActive: false
};
this.toggleState = function() {
state.isActive = !state.isActive;
};
this.getState = function(){
return state;
};
}]);
Then you need to use `activeState.isActive' inside your view.
Now you will have a service with a shareable state, and two controllers that both reference the same service on load. Then inside your menu view, when you toggle the state, both controllers scopes are updated.

From the angularjs docs:
When a Controller is attached to the DOM via the ng-controller directive, Angular will instantiate a new Controller object, using the specified Controller's constructor function. A new child scope will be created and made available as an injectable parameter to the Controller's constructor function as $scope.
Additionally, the ng-controller directive has a scope that inherits from its parent scope. So, you can also define the shared data in the parent. It will be accessible on the 'NavCtrl' scope. But since you want to use the controller as synax one way would be to share the data with a help of a service.
However, I don't like the approach with the 'ng-controller' directive. The other solution is to replace it with a hierarchy of directives where the child directives will require the parent one. Also use 'controllerAs' property there.

Related

Why $scope.getList() gets invoked on state change of $scope.showList?

In the below code,
<label>
<input type="checkbox" ng-model="showList">
Show unordered list
</label>
<ng-include src="getList()"></ng-include>
$scope.getList() gets invoked on change of $scope.showList by check or uncheck, where $scope.showList is used as,
app3.controller('gListCtrl', function($scope){
$scope.getList = function(){
return $scope.showList ? "ulgrocerylist.html" : "grocerylist.html";
};
});
Why $scope.getList() gets invoked on change of state of $scope.showList?
Similar code,
<p>
<button ng-disabled="disableButton">Button</button>
</p>
<p>
<input type="checkbox"
ng-model="disableButton">DisableButton
</p>
make sense to me, because disableButton state is changing, so button gets disabled or enabled due to two way binding.
First of all, You're a little bit incorrect on your question. The $scope.getList() function gets invoked not only on a state change, but on every digest cycle. Let me explain.
Because the framework has absolutelly no clue what code is in the getList function. It does not statically analize your code, since it would be both very hard and very inneficient. Due to the nature of how you can use AngularJS, you could be changing the output of getList according to a variable in a completely different controller, service, scope, etc. Thus, this output might need to be rerendered upon every digest cycle. AngularJS recognizes this, because you have the function call in your template and calls it on every digest to check whether it needs to swap out the template.
Consider this application structure:
<div ng-app="testTest">
<script type="text/ng-template" id="template.html">
<div>Hello world!</div>
</script>
<div ng-controller="templateViewer">
<div>
<div ng-include="content()"></div>
</div>
</div>
<div ng-controller="templateChanger">
<button ng-click="handleClick()">Show / hide content</button>
</div>
</div>
and this code to wire it:
var app = angular.module('testTest', []);
app.factory('template', function() {
return {
show: false
};
});
app.controller('templateChanger', function($scope, template) {
$scope.handleClick = function() {
// toggle showing of template
template.show = !template.show;
};
});
app.controller('templateViewer', function($scope, template) {
// if the result of this function is not re-evaluated on every digest cycle,
// Angular has no idea whether to show or hide the template.
$scope.content = function() {
return template.show ? 'template.html' : '';
};
});
So, the framework needs to rely on this constant re-evaluation of properties and functions that are binded to the templates in the HTML. Since all the data structures that you use are plain javascript objects, and you don't explicitly tell the framework that something in your viewmodel has changed (as you would do by invoking set() methods on your models in other frameworks, such as Backbone or Ember) – angular has to check all variables and re-run all the functions that could possibly change the look of your view, and ng-include is one of these cases.
You can use watcher or observe events on showList variable value changes.

Preventing Angular Template from Initial Display

I am following an online example. However, it doesn't work quite the way I had hoped it would. Now, I could easily do this with jQuery and a class, but I am trying to do it the "Angular Way".
The Angular Template for my tags is initially displaying. Once the scope starts to process, then it hides & the tags come-in as expected when binding.
Q: How do I prevent the Angular Template form initially displaying?
UPDATE:
Applying "ng-bind" only changes the nature of the problem. It doesn't solve the problem.
MY MARKUP LOOKS LIKE:
<div ng-controller="BlogsIndexController">
<div class="tags-cloud tags-cloud-category" ng-show="isShown">
<div class="tag" ng-repeat="category in categories">
{{category.Name}}
</div>
</div>
</div>
MY CONTROLLER LOOKS LIKE:
// CONTROLLER
application.controller('BlogsIndexController', function ($scope, $http, categoryTagsDataService) {
var vm = this;
// Internal
vm.on = {
databind: {
categories: function () {
var categories = categoryTagsDataService.list()
categories.success(function (data) {
$scope.categories = data;
$scope.isShown = true;
});
}
}
};
vm.databind = function () {
vm.on.databind.categories();
};
// Scope
$scope.isShown = false;
$scope.categories = [];
// Initialize
vm.databind();
});
You should use ngBind="category.Name" instead of {{category.Name}}:
<a href="#" data-iso-sort="iso-sort-category-{{category.SortKey}}"
ng-bind="category.Name"></a>
It is preferable to use ngBind instead of {{ expression }} if a template is momentarily displayed by the browser in its raw state before Angular compiles it. Since ngBind is an element attribute, it makes the bindings invisible to the user while the page is loading.
More info here.
Update 1:
I've never used ngCloak, but docs say that it may help you:
<a href="#" data-iso-sort="iso-sort-category-{{category.SortKey}}"
ng-bind="category.Name" ng-cloak></a>
Update 2:
I've checked this answer and it seems that you need also to add the next CSS rule:
/*
Allow angular.js to be loaded in body, hiding cloaked elements until
templates compile. The !important is important given that there may be
other selectors that are more specific or come later and might alter display.
*/
[ng\:cloak], [ng-cloak], .ng-cloak {
display: none !important;
}

How can I force my $modal window to create objects on the main scope

I have a main page in which I open a modal window on a button click. The new modal window is assigned the same scope as the main controller. I mean I create it like so:
app.module.....{
$scope.modalInstance = null;
$scope.myNumber = null;
$scope.openModal = function(){
modalInstance = $modal.open({
templateUrl: 'template/modal/Modal.html',
size: size,
windowClass: 'small-size-modal',
scope: $scope
});
};
All the data the modal window displays is taken from the scope of the controller and everything works fine. Now there is also a text box input to capture a number from the user. And I would like that this number is directly created on the controller scope itself so that I can access it from the controller like so.
$scope.submitNumberFromModal = function(){
$scope.doSomething($scope.myNumber);
};
My Modal html input element is like so:
<div class="modal-content">
<input type="text" class="input-box" size="10" ng-model="myNumber"/>
But I see that myNumber is being created on a separate scope which I do not know how to access. Any help is hightly appreciated.
$modal.open creates a new scope with $scope as parent. New scope prototypically inherits from parent, this means that any object from parent controller's scope (including this in the case of controllerAs) can be used to bind the value and use it in both parent and modal scopes.
So it would be
$scope.data = {};
in parent controller, and
<input type="text" class="input-box" size="10" ng-model="data.myNumber"/>
in modal.
But I see that myNumber is being created on a separate scope which I
do not know how to access.
It can be accessed with modal's own controller, any sort of controller interaction (e.g. events) can be used to make it talk to parent controller.

Directive communication - sharing a reference to an inner HTML element

I want to find a way to do clean communication between my two sibling directives. I want to implement "insertAtCaret" functionality for a textarea in one directive, to be called from another.
<text-holder ng-model='model.text' />
<text-inserter>
<a ng-click='insert("hello")'>Hello</a>
</text-inserter>
text-holder turns into something like this:
<div class='my-class'>
<h3>Enter some text:</h3>
<textarea ng-model='ngModel'></textarea>
</div>
The text-inserter needs to insert stuff into that textarea - what's the cleanest angular-ish way to allow that communication? I want to be able to support multiple instances of that on the page. Should I just create a unique id for each one from a shared service? It seems a little unclean.
You can :
Wrappe your directive in outer DOM element.
create a communication directive on this element.
Use the controller of this directive as an API for communication between the two directives.
Use require from the two directive, to, set, the text.
<div text-com-directive>
<text-holder ng-model='model.text' />
<text-inserter>
<a ng-click='insert("hello")'>Hello</a>
</text-inserter>
</div>
Directive :
directive('textComDirective', function(){
return {
scope:{},
controller: function($scope){
// create functions that will be used to set/use the text inserter.
}
}
});
The only chain between two directives is a variable that is supposed to be updated, this is also used by both directive. The text-inserter directive is sort of like choosing the method to be executed to the text-holder
html
<text-holder ng-model='model.text'></text-holder>
<text-inserter>
<a ng-click='model.insert("hello")'>Hello</a>
</text-inserter>
script.js
var app = angular.module('testapp',[]);
app.controller('appController', function ($scope) {
$scope.model = {text: 'sample', insert: function(a){$scope.model.text = a}};
})
app.directive('textInserter', function () {
return {
restrict: 'E',
trasclude: true // important to keep the content that is defined outside of directive
}
});
Sample
The insert function is set in the controller that is holding the variable to pass to the directive, this way helps us to easy understand what logic should be applied and is going to happen for the model variable in the initiated scope it self.
The more benefit is you can situational change the behavior for some specific instance.

Dynamically composing a UI using Knockout.js

I'm working with the awesome Knockout.js library on a project and am looking for a way to compose sections of my UI at run-time.
For example I have have a couple of templates (simplified, below) that are made up of child templates. Id like to pass a view model to these and render them, and then be able to append (and remove) the contents from criteria form.
<!-- used with LineGraphModel -->
<script type="text/html" name="linegraph-template">
<div id="LineGraph">
<div data-bind="contextTemplate: { name: 'series-template', data: seriesChoices, context: { selected: series } }"></div>
<div data-bind="contextTemplate: { name: 'xaxis-template', data: xAxisChoices, context: { selected: xaxis } }"></div>
<div data-bind="contextTemplate: { name: 'daterange-template', data: dateRangeChoices, context: { selected: dateRange } }"></div>
<div data-bind="template: { name: 'button-template', data: $data }"></div>
</div>
</script>
<!-- used with PieChartModel -->
<script type="text/html" name="piechart-template">
<div id="PieGraph">
<div data-bind="contextTemplate: { name: 'series-template', data: seriesChoices, context: { selected: series } }"></div>
<div data-bind="contextTemplate: { name: 'daterange-template', data: dateRangeChoices, context: { selected: dateRange } }"></div>
<div data-bind="template: { name: 'button-template', data: $data }"></div>
</div>
</script>
I've begin wandering down the path of ko.renderTemplate but I can't seem to find any good documentation on how to create a new div and append the result to an existing div. Is this possible, or is there another approach I should be trying?
After writing all this down, it dawns on me that this might exceed the scope of your question quite a bit. If that is indeed the case, I apologize; I hope that you still might get some value out of it.
This stuff here comes from a real app I have been working on for several months now. It's a quick and dirty extraction and might contain bugs or typos where I removed app-specific code or simplified it to make it easier to follow.
With it, I can
arbitrarily nest viewmodels
dynamically add viewmodels on the fly
render Knockout templates bound to these nested viewmodels, and use the results flexibly
Here's a quick overview how it works.
Pretend for a second you are going to build an app that shows a list of messages. The user can click on a message to open a modal dialog and reply. We have three viewmodels:
a root viewmodel called Main
a MessageList that takes care of displaying the list of messages
a third one called MessageReply that is responsible for the reply functionality.
All our viewmodel constructors are neatly namespaced in app.viewmodels. Let's set them up:
$(document).ready(function() {
var mainVm,
messageListVm,
messageReplyVm;
// we start with Main as the root viewmodel
mainVm = new app.viewmodels.Main();
// MessageList is a child of Main
messageListVm = mainVm.addChildVm('MessageList');
// and MessageReply in turn is a child of MessageList
messageReplyVm = messageListVm.addChildVm('MessageReply');
// the root is the only one that gets bound directly
ko.applyBindings(mainVm);
});
Our markup looks something like this:
<body>
<!-- context here: the Main viewmodel -->
<div data-bind="childVm: 'MessageList'">
<!-- context here: the MessageList viewmodel -->
<ul data-bind="foreach: messages">
<!-- context here: the individual message object -->
<li>
<p data-bind="text: body, modal: {viewmodelName: 'MessageReply', parentViewmodel: $parent, setupViewmodelWith: $data, templateName: 'message-reply-template'}">
</p>
</li>
</ul>
</div>
</body>
<script id="message-reply-template" type="text/html">
<!-- context here: the MessageReply viewmodel -->
<div>
<textarea data-bind="value: message().body"></textarea>
<input type="submit" data-bind="click: submit">
</div>
</script>
There are two custom bindings in there, childVm and modal. The former just looks up a child viewmodel ands sets it as the binding context, whereas the modal binding is responsible for rendering the template in the correct context and handing the result to a separate JS library.
Viewmodels gain the ability to nest by borrowing constructor functions, a Parent, a Child or both at the same time. Here is the source for them.
Parents
If a viewmodel should be able to have child viewmodels, it borrows the Parent constructor:
app.viewmodels.Main = function Main() {
app.viewmodels.Parent.apply(this);
this.currentUser = //.. imagine the current user being loaded here from somewhere
};
As a parent viewmodel, Main has gained three things:
.addChildVm(string): add a child viewmodel by passing its name. It's automatically looked up in the app.viewmodel namespace.
.getVm(name): returns the child viewmodel named 'name'
._childVms: an observable list containing all the children
Children
Every viewmodel apart from the root Main is at least a child viewmodel. MessageList is both a child to Main, and a parent to MessageReply. Very appropriately to its name, it houses the messages to be displayed in the list.
app.viewmodels.MessageList = function MessageList() {
app.viewmodels.Parent.apply(this);
app.viewmodels.Child.apply(this);
// children need to set this, so we can find them by name through .getVm()
this._viewmodelName = function() { return "MessageList"; };
this.currentUser = null;
this.messages = ko.observableArray([]);
this.init = function init() {
that.currentUser = that._parentVm.currentUser;
var messages = GetMessages() // pseudocode - load our messages from somewhere
this.messages( messages);
};
};
As a child viewmodel, MessageList gains:
the ability to access its parent through this._parentVm
an optional init function, which is called automatically by the parent if present
So above when we added MessageList to Main with
messageListVm = mainVm.addChildVm('MessageList');
, Main
created a new instance of MessageList
added the instance to its own children
and called the childs init
The child then set itself up by getting a reference to the current user, which is mainted by the parent Main viewmodel.
Our last viewmodel: the MessageReply
MessageReply is just a child viewmodel; like it's parent MessageList did itself, it too copies the current user when initialized. It expects to be handed a Message object from the modal binding, then creates a new Message in reply to it. That reply can be edited and submitted through the form in the modal.
app.viewmodels.MessageReply = function MessageReply() {
app.viewmodels.Child.apply(this);
this._viewmodelName = function() { return "MessageReply"; };
var that = this;
this.currentUser = null;
// called automatically by the parent MessageList
this.init = function init() {
that.currentUser = that._parentVm.currentUser;
};
this.messageWeAreReplyingTo = ko.observable();
// our reply
this.message = ko.observable();
// called by the 'modal' binding
this.setup = function setup(messageWeAreReplyingTo) {
// the modal binding gives us the message the user clicked on
this.messageWeAreReplyingTo( messageWeAreReplyingTo );
// imagine that Message is a model object defined somewhere else
var ourReply = new Message({
sender: that.currentUser,
recipient: that.messageWeAreReplyingTo().sender();
});
this.message( ourReply );
};
// this is triggered by the form submit button in the overlay
this.submit = function submit() {
// send the message to the server
}
};
The 'childVm' binding
Source code
<body>
<!-- context here: the Main viewmodel -->
<div data-bind="childVm: 'MessageList'">
<!-- context here: the MessageList viewmodel -->
</div>
This is merely a convenience wrapper around Knockouts own 'with:' binding. It takes a viewmodel name as its value accessor, looks up a child viewmodel of that name in the current binding context, and uses the 'with:' binding to set that child as the new context.
The 'waitForVm' binding
Source code
This isn't used in the example above, but is quite useful if you want to add viewmodels dynamically at runtime, as opposed to before ko.applyBindings. This way, you can delay initializing parts of your application until the user actually wants to interact with them.
waitForVm waits until the specified viewmodel is available before binding its child elements. It does not modify the binding context.
<div data-bind="waitForVm: 'MessageList'">
<!-- bindings in here are not executed until 'MessageList' is loaded -->
<div data-bind="childVm: 'MessageList'"> ... </div>
</div>
The 'modal' binding
Source code
This takes a Knockout template, marries it to a viewmodel, renders it and passes the result to an external JS library that handles the modal dialog.
Imagine that this modal library
when initialized, creates a DOM container before </body>
when asked to display the modal, takes this container and shows it overlayed over the rest of the page, lightbox-style
Let's look at the modal binding in action again:
<!-- context here: the individual message object -->
<li>
<p data-bind="text: body, modal: {viewmodelName: 'MessageReply', parentViewmodel: $parent, setupViewmodelWith: $data, templateName: 'message-reply-template'}">
</p>
</li>
modal will
use the parent viewmodel MessageList, found in our current binding context at $parent
ask it via getVm() for its child viewmodel instance MessageReply
add a click binding to the <p>, which when activated
calls setup() on MessageReply, handing it our $data - the current message the user clicked on
prepares the modal and
renders the template 'message-reply-template', bound to the MessageReply viewmodel, into the modals DOM container

Categories