Communication between directives (and other parts) in AngularJS - javascript

I'm looking for guidelines about when and why should I use the "require" option in the directive definition,
why not communicate just using the Scope - like most of the times in Angular ?
How is it, that suddenly in directives, I ask for the controller itself and not just attaching things to the scope(s) ?
Generally speaking - there are many ways to communicate between directives/controllers/scopes in Angular -
Scope inheritance.
RootScope "emits".
Services (factory/service/provider).
Requiring controllers in directives.
Requiring new Scope/ isolate scope / "normal" scope in directives.
More?
And while I understand how they work technically, it's not clear to me what are the guidelines to decide which one to use and why.
Will be happy for some general/high level guidelines. Thanks.

require is particularly useful if you want to create custom form controls (see section Implementing custom from controls) -- i.e., you want to create your own form control with a directive. You can require the ngModelController to get access a lot of existing functionality via its API/functions.
Another use case is found on the AngularJS home page, section Create Components, where the pane directive uses require: '^tabs' to get access to the tabs controller. Since both of these components/directives create isolate scopes, scope inheritance is not an option. A service wouldn't be a good fit either, since your app could have multiple tabs directives. So, in this case, the controller is used as a means for the pane directive to be able to affect the tabs scope -- to be able to get at the scope. Without using require, the pane directive can't get at the tabs scope. I discuss this in more detail (with a picture) in this SO answer: 'this' vs $scope in AngularJS controllers
require can only be used if the controller-of-interest is defined on the same element (e.g., ngModelController), or if there is a hierarchy (e.g., pane -> tab).

Related

Set context scope in Angular

I have my variables bound to this in my controller, and in my route designation I use controllerAs: 'game'. This allows me to include them in my HTML with {{game.var}}. Sometimes, I bind objects that I want to display, but this forces me to write repeatedly {{game.object.a}}, {{game.object.b}}, {{game.object.c}}.
In a previous project using Meteor, I could set the data context with the with keyword.
{{#with object}}
{{a}}
{{b}}
{{/with}}
but I don't see a similar feature to this in Angular. The closest I've been able to make work is adding the attribute ng-repeat="object in [game.object]". This works, but it isn't very semantic. This also causes me to get a quick flash of a second element when game.object changes, as the new one loads before the first one is erased.
Is there a better solution to this problem?
Angular intentionally uses this context scope to avoid confusion between parent and child scopes. If you're not using child scopes, you can skip the controller as syntax entirely and just bind everything to $scope in your model, which turns game.a and game.b to just a and b in your view.
If you are using child scopes, you can still skip controllerAs, but then it becomes confusing what controller a given model in the view belongs to. There's no with or using syntax, so you need to declare the bound scope game.a, childGame.a everywhere you refer to these models, which might be overly verbose but is at least clear.
See this post, as well.
Regarding the flash issue, I would avoid using ng-repeat for semantic purposes. It's primary use case is to display an array of similarly structured data.

Is ng-init truely a bad way set variable in view without controller

I am using ui-select to create a complex page with multuple views. For those that don't know each view is loaded via ajax when a user selects a tab (or in my case a dropdown). Views traditionally have their own controller and html templet. Views inherrit the scope of the parent page they are on, but have their own child scope.
In my case some of our views have no controller at all. They are so simple that we only have html pages for them. The problem is that I would like them to be able to toggle a boolean on a configuration object on the parent page's scope, to turn off on on some control fields on the parent page. Everything already exists, multiple pages use these views, I simply want to expand them to toggle something on the parent.
I could do this in three ways. I could in my ui-router use the resolve method to explicitly toggle the field I want when the view is loaded, but then I have to do this in each ui-router for each page explicitly; no cool code-reuse.
I could have my ui-router load a controller, where currently I have no controller, and the controller could do this. However, this means manually adding a controller that is mostly unneeded everywhere I use the view, and again doesn't feel like code reuse.
Or I could use ng-init to have my view's explicitly set the value I want at load time. To me this feels cleanest and easiest, since I don't already have a controller.
However, ng-init is frowned on, and should only be used for aliasing supposedly. Is there a cleaner approach then using the ng-init? is it really bad to have a view without a controller (to be more exact they have the parent controller still, but that isn't reloaded when a new view is opened).
The fact that you don't specify controller for the route doesn't change anything. The view has its own scope, so it can be manipulated with either route controller or with directives that don't have their own scope (e.g. ngInit).
The objective of well-known remark on ngInit
The only appropriate use of ngInit is for aliasing special properties
of ngRepeat, as seen in the demo below. Besides this case, you should
use controllers rather than ngInit to initialize values on a scope
is to keep you from 'html programming'. And it doesn't have too much sense because the separation of concerns is not among Angular's virtues. Angular's habit of evaluating the code from html attributes and the way how it is used by core directives prompt you to defy it even further.
Just dont init that variables at all. ng-show="smth", ng-if="smth" works ok if 'smth' is not defined and later u change it using ng-click="smth = !smth".

AngularJS : How can I avoid tightly coupling stacked directives when both need values from parent scope?

I'm trying to wrap a 3rd party (non-Angular) library in a set of Angular directives for use in my application. So far I've got a base control that I wrapped in a directive (basically on link it just replaces a div in the template with the 3rd party control). I wanted to setup this directive to be re-used across my app as I'll need the control multiple times. This seemed to call for an Isolate scope and to setup bindings on my views to avoid tight coupling. This works for the base functionality, but in some cases I need to extend the functionality of that control.
My thought was to create a second directive that leveraged different parts of the 3rd party library to add functionality to the base control. This directive would need to communicate with the 3rd party control in the first directive, as well as bind to values on the parent view. I setup both directives as restrict: 'A' to make them attributes, then I stack them on a single div in my view -
<div directive-base directive-add-on />
The problem here is communicating between all the different pieces. If the base directive is an isolate scope, the add-on can't be isolate and thus can't communicate with the parent view. If I make them both child scope I can see everything on the parent scope, but then I'm more tightly coupling my directives to my view's controller.
Is there a different approach to doing this that avoids the tight coupling?
Can you treat the add-ons as attribute arguments to the directive-base?
Normally, I'd have a directive on an element like:
<directive-base add-on="true_or_scope_variable"></directive-base>
In your directive link function or associated controller, you can adjust the content based on the attribute values.
Does this work?

When do we use $scope and when do we we use var in AngularJS?

Is it better to use var than to use $scope. in AngularJS variables inside functions?
My reason for asking this is not as simple as it seems. I recently read about $watch, $digest, $apply. Although I didn't understand it completely, I understood that $digest works in a loop.
This post explains it quite well.
How do I use $scope.$watch and $scope.$apply in AngularJS?
So if you have $scope.myVar defined in your controller, you are explicitly telling Angular to monitor the changes on myVar. Doesn't this activity slow down the overall system?
Short answer: yes, it's better to declare any variable that's purely internal to your controller as a javascript variable ("var") rather than adding it to an Angular scope.
Angular scope objects provide many nice features that allow them to act as the model in an model-view-*(MV*) architecture (for instance data binding). Or said another way, as the Angular guide to scopes says "Scope is the glue between application controller and the view".
It's best to only put objects that you need in your model, that you need bound both to the DOM and your controller/services/..., on the scope as those features do come at a performance cost, as you point out. And it can also be confusing to other's who read your code if your scopes are "cluttered" with variables that aren't actually part of your model.
Here's the specific features of scopes from Angular scope docs:
Scopes provide APIs ($watch) to observe model mutations.
Scopes provide APIs ($apply) to propagate any model changes through
the system into the view from outside of the "Angular realm"
(controllers, services, Angular event handlers).
Scopes can be nested to limit access to the properties of application
components while providing access to shared model properties. Nested
scopes are either "child scopes" or "isolate scopes". A "child scope"
(prototypically) inherits properties from its parent scope. An
"isolate scope" does not. See isolated scopes for more information.
Scopes provide context against which expressions are evaluated. For
example {{username}} expression is meaningless, unless it is evaluated
against a specific scope which defines the username property.

AngularJS how to write functions in directive

For me AngularJS directives looks like wrapped functionality, like some web component. I am trying to wrap progress bar from Bootstrap i.e.:
I made basic directive thats fine, but I would like to add some API to that component. Some functions which I can use in controller that could control component behaviour. E.g. start, stop, reset, gotopercent etc. I made that functions at controller but I think it is not best practice since that functions should belong to directive, not a controller so I can easily reuse it in other pages.
Question:
How to create functions in AngularJS directive which allows me to control component behavior.
I made that functions at controller but I think it is not best practice since that functions should belong to directive, not a controller so I can easily reuse it in other pages.
Well, no. Functions do belong into controllers - but directives can have their own controller where you define your "directive API" independent from you ApplicationController or PageController.
Take a look at the angular-ui/bootstrap repo, where there's already a directive for the bootstrap progressbar.

Categories