AngularJS directive only called once? - javascript

I'm trying to shove mixitup inside my angular page and in order to do so I made a directive module for it
angular.module('MainCtrl', [])
.controller('MainController', function($scope) {
$scope.tagline = 'To the moon and back!';
})
.directive('mixitContainer', function() {
return {
restrict: 'A',
link: function(scope, element, attrs) {
$(element).mixItUp(scope.$eval(attrs.mixitContainer));
}
};
});
Don't worry about the simplicity of the main controller, it is simply a test.
Now, the issue is that the directive only get's called once! If I go to another page and ask angular to load another controller and then go back to the home page and ask angular to load MainCtrl again, the directive isn't loaded!
Heres the with the directive:
<div id="Container" class="mixit-container" mixit-container="{load: {sort: 'order:asc'}, controls: {toggleFilterButtons: true, toggleLogic: 'and'}}">
Anyone got any ideas?

AngularJS doesn't include routing facilities. Those are provided either by ngRoute (a core but optional module), ui-router (ngRoute made super-awesome-amazing), or another replacement. You don't say which you use, and each has different behaviors.
Whichever it is, this is going to come down to the router, not the directive. The directive will get called whenever necessary. It doesn't control that itself - 'necessary' means Angular is compiling a portion of the DOM, usually from a template file, and has run into the directive. It will call the directive and ask it "what now?"
The above-named routers have different behaviors, both from each other and also based on how you configure them internally. In all of them you can arrange things so that templates do, or do not, get re-rendered. For example, in ui-router you can have 'child' states. If you have a child state active, the parent is also active... so going from the child to the parent will not re-render a template because it was already done earlier. And to make matters more complex, you can even override this by hooking the $stateChangeStart event and force the router to redraw that view even if it didn't think it needed to.
All this means... set your attention to your directive aside. It will do what you want as soon as the higher level does what you want. Get your router behaving the way you expect and you will be happy!

Related

Angular 1.5 to use transclude or not to use transclude

A question regarding transclude within an angular 1.5.8 component, and it's uses.
Here is an example of some code;
var app = angular.module('app', [])
function AccordionController () {
var self = this;
// add panel
self.addPanel = function(panel) {
// code to add panel
}
self.selectPanel = function() {
//code to select panel
}
}
// register the accordion component
app.component('accordion', {
template: '<!---accordion-template-->',
controller: AccordionController
}
function AccordionPanelController () {
// use parents methods here
var self = this;
// add panel
self.parent.addPanel(self);
// select panel
self.parent.selectPanel(self);
}
// register the accordion-panel component
app.component('accordionPanel', {
// require the parent component
// In this case parent is an instance of accordion component
require: {
'parent': '^accordion',
template: '<!---accrodion-panel-template-->',
controller: AccordionController
}
My question is would it be better to nest all the according panels within the parent using transclude or alternatively pass in a data array to the parent which this loops out the required number of panels based on the array passed inside using a binding.
Thanks
// added
Many thanks for your reply, an example I have of transclude possibly being necessary is in the following bit of code
<modal modal-id="editCompany" title="Edit Company"> <company-form company="$ctrl.company"></company-form> </modal>
Here we have a modal component which may have a variety of other components used within it, on the example above I am adding the company form, but this could we be an contact form. is there an alternative way?
I've worked with angular pretty extensively. Two enterprise tools managing and displaying large amounts of data, dozens of interactive widget modules, all that.
Never, once, have I had anything to do with transclude. At work we are explicitly told not to use it (link functions too). I thought this was a good thing, and the way Angular 2 turned out it seemed that thinking wasn't totally without reason.
I would go with the iteration to lay out the required number of items. At work this wouldn't be a choice because transclude wouldn't be an option.
The thing with using transclude in a component architecture is that it visually breaks the idea of single responsibility and messes with the architecture.
<html>
<navigation></navigation>
<accordion>
<accordion-panel></accordion-panel>
</accordion>
<footer></footer>
</html>
In this example you know your page has a navigation menu, an accordion and a footer. But at the index level (or root component) you don't want to know / see what the accordion contains.
So the accordion-panel component should only appear in its direct parent component.
As for your other question, through the use of require or attributes you pass an array of panels that you iterate using ng-repeat inside the accordion component.
app.component('accordion', {
template: `<accordion-panel ng-repeat="..."></accordion-panel>`,
controller: AccordionController
}

Issues with AngularJS Controller

I have the following setup in my html file:
<body>
<nav ng-controller="loginCtrl">
<div>
<li>Apple</li>
<li>Mango</li>
<li>{{user}}</li>
</div>
</nav>
<div ui-view></div>
</body>
I have a navigation menu and the ui-view that displays different pages.
I also have a controller, loginCtrl, with a scope variable called $scope.user. This controller is also called in the UI-state router for the login.html file as well so that the login form can use its methods.
When a user logs in, I want to show his name in the navigation menu using the {{user}} above. The navigation menu as you can see is visible (static) regardless of other partial pages that will be loaded in the ui-view.
At the moment, it is not working and I don't know why.
My understanding is that the login form in the login.html and the navigation menu are in different files so that may be they are using the same controller (under the same module) yet may be operating in different scopes/environments (am not really sure about that).
That is why I update the value of $scope.user but it doesn't appear in the navigation menu.
Why is it not working and how can I achieve my functionality?
Using a singleton service to share same UserData object:
app.service('UserData', function(){return {name: 'default'};});
app.controller('LoginController', function($scope, UserData){
$scope.user = UserData;
});
Now, all controller instances have access to same UserData object.
When user.name has changed, all controllers can see it.
I'm wondering if it's necessary to cal your LoginController for your ui-view and a sibling element, when you could load it on a parent element
Anyway, you have several solutions to make the two-way binding work:
#vp_arth solution is really great, usually used this to share data between controllers
Move your ng-controller attribute to a common parent element, and if needed, declare another controller for your login.html, that will be a child of your LoginController. Then use an object instead of a variable in your parent scope:
$scope.user = {};
and then fill it in your child scope like this:
$scope.user.name = ...
Even if you're using the same controller as parent AND child scope, you should make this work with something like this:
$scope.user = $scope.user ? $scope.user : {}; instead of $scope.user = {};
(if it's not clear I can make a comparative fiddle to show you)
This wiki really helped me when I had issues like yours: https://github.com/angular/angular.js/wiki/Understanding-Scopes

Why does my $watch only ever fire once?

I'm factoring out some widget and the $watch expression works perfectly having all in one file but now I moved the relevant controller part into a new controller and the markup into a new html and the $watch fires exactly once after initialization but not when editing typing in the associated input.
JS:
app.controller('getRecipientWidgetController', [ '$scope', function($scope) {
console.log("controller initializing")
var testReceivingAddress = function(input) {
console.log("change detected")
}
$scope.$watch("addressInput", testReceivingAddress)
} ])
HTML of wrapper:
<ng-include
src="'partials/getRecipientWidget.html'"
ng-controller="getRecipientWidgetController"
ng-init="recipient=cert"> <!-- ng-init doesn't influence the bug. -->
</ng-include>
HTML of partials/getRecipientWidget.html:
<md-text-float ng-model="addressInput"></md-text-float>
I suspect there is some scope voodoo going on? I left the ng-init in to make clear what I want to achieve: build an obviously more complex, reusable widget that in this instance would work on $scope.cert as its recipient.
That is probably because ng-include will create a new inherited scope on the included HTML, hence $scope.addressInput in your controller is not the same reference as $scope.addressInput in getRecipientWidget.html
Well it's not easy to explain, but you should either put ng-controller within the HTML of getRecipientWidget.html (and not on the div above that includes it), OR you can use an object such as something.addressInput instead of the raw addressInput which avoids references issues on raw types (number/string).
ng-include creates new scope.
Try this
<md-text-float ng-model="$parent.addressInput"></md-text-float>
Plunker example

AngularJS ng-include only once

How can I use ng-include in such way that it's content will be loaded only once?
Here is what I have:
<div data-ng-if="%condition-1%" data-ng-include="%url-1%"></div>
<div data-ng-if="%condition-2%" data-ng-include="%url-2%"></div>
<div data-ng-if="%condition-3%" data-ng-include="%url-3%"></div>
...
In my case only one condition is true at some moment of time.
And any condition can change its value many times during page lifetime.
So ng-include will load the same content again and again.
How can I tell Angular to process ng-include only once - when the appropriate condition becomes true for the first time?
Loading them all at once will kill the page because every template is large and heavy.
Also there is no strict sequence of condition changes, for example, condition-3 may never become true during page lifetime - I'd like not to load url-3 content at all in this case.
Thanks!
UPDATE
Yes, template is already on cache. But it has a complicated internal structure like references to external images, iframes and so on - all this things are reloading each time when I'm using ng-include.
You have many solutions but only 2 come to my mind at the moment
1° Replace the ng-if for a ng-show, as the ng-if deletes the dom and all children scopes available, forcing the framework to make the request once again, while if you were using ng-show, the dom would only be hidden and the request would have only be made once.
2° If you do need to use ng-if and the content from the server is static, you could cache it on the javascript layer by manually accesing the $templateCache service provided by angular, or if the content you wish to load is html, you could either use the $templateCache service on the javascript layer or use the ng-template tag to preload that data.
Example:
<script id="url/you/want.html" type="text/ng-template">
<div>I am preloaded dom that responds to the url/you/want.html
requests made by this application
</div>
</script>
Cheers
How about using only one ng-include and using some logic in the controller to switch which source to use using a binding? This way only one will ever be loaded at a time.
Controller
function($scope) {
$scope.activeTemplate = null; //some default or even null
$scope.$watch('condition', function(newvalue) {
//whatever logic you need to switch template
if (newvalue == 'condition1') {
$scope.activeTemplate = '/path/to/condition1.html';
} else if (newvalue == 'condition2') {
$scope.activeTemplate = '/path/to/condition2.html';
} else {
$scope.activeTemplate = '/path/to/default.html';
}
});
}
This way only one template will ever be loaded at a time, and you've reduced the number of bindings from 3 to 1. (however you have added a watch so effectively from 3 to 2 maybe)

Communication between multi-layered directives in Angular

I'm creating a few directives that will make up a single "screen" in my app. To create this new screen you would write it like this:
<screen title="Test Title">
<side-menu align="left">
<menu-item>Test One</menu-item>
<menu-item selected="true">Test Two</menu-item>
<menu-item disabled="true">Test Three</menu-item>
</side-menu>
<content animation="fade">
<view>Content for menu option 1</view>
<view>Content for menu option 2</view>
<view>Content for menu option 3</view>
</content>
</screen>
Each <menu-item> will display one of the "views" inside of the <content> tag. It works like tabs. I've got this set up by keeping track of each <view> inside of the <content> directive in an array when they are linked. Same for <menu-item>.
My question is, now that I've got this set up, what is the best way to communicate between the <side-menu> directive and the <content> directive to hide and show the correct view when clicked? Should I use events, a common service to hold state, or is there a way I can maybe access the controller inside of the <screen> directive from the <view> and <menu-item> directives, and hold the data/state there? From my understanding I can only access the parent controller from a child directive, but not the "grand parent" controller if you will, unless I use some sort of pass-through.
I plan to have a few more components on this "screen" that will need to communicate as well so I'm hoping to determine the "correct" way to do this before I continue, or at least get some feedback and other ideas.
If any of that is confusing, I'd be happy to provide more information.
So after a bit of digging, I've learned that you can pass an array to the require property of a directive.
You can use this to find parent controllers, and grandparent controllers... etc. Previously each of my directives would have one require value such as: require: '^sideMenu' for the menuItem directive.
Now I can require the sideMenu and screen controllers into the menuItem directive by passing an array:
require: ['^screen', '^sideMenu']
Now in the link function of my menuItem directive, I can access these controllers this way:
link: function(scope, element, attrs, controllers) {
var screenCtrl = controllers[0];
var sideMenuCtrl = controllers[1];
}
Notice the controllers property is now an array of the controllers that I required, and are accessed by index. Although I feel as though my directives are little more tightly coupled now, I do like it better than using events/services.
I'm explaining all of this, because no where in the Angular docs does it mention this.

Categories

Resources