Catching access to $scope properties - angularjs

is it possible to catch the following: if I have some template
<div ng-controller="SomeController">
{{notthere}}
</div>
and notthere is not defined in the controllers $scope, can I have a callback fired to handle this? Like:
$scope.$catch(function(name) {
// name === 'notthere'
return 'someval';
}
BR,
Daniel

well, you can do
<div ng-controller="SomeController">
{{notthere || 'someval'}}
</div>
but I'm not sure if this meets your requirements

I don't thnkg so. Angular (by default) will add nothere as a property to link (with the controller's scope) when it is compiling.
You potentially could do something fancy with preLink in a directive, but there is no straightforward way to do this.
BTW, the fact that nothere is automatically added to the scope can be very beneficial. It makes it easy to declare "view-only" properties (for validation etc.) that you don't need to concern the controller with.

You might try something like this
<div ng-controller="SomeController",ng-if="nothere!==undefined">
{{nothere}}
</div>
<div ng-controller="SomeController",ng-if="nothere===undefined">
someValue
</div>

Related

AngularJS : Correct usage of ng-model

I was reading Angular js docs when I came across the issues mentioned related to using ng-model with directives like ng-include , ng-switch , and ng-view.The reason mentioned was child scope and parent scope but I was not able to understand it completely.
Also it was mentioned that issue with primitives can be easily avoided by following the "best practice" of always have a '.' in your ng-model.
Here's the link
Can anyone please explain it in less-technical language?
ng-include, ng-switch and ng-if creates child scope.
It means that if you create
<div ng-controller="MyCtrl">
<div id="innerDiv" ng-if="!something">
<input ng-model="model">
</div>
</div>`
the model will be created in scope created by the #innerDiv (because of using ng-if). It might be problematic if you want to use model value in the controller, because it won't be possible (Parent scope has no access to properties of child scope!).
The solution is to use $parent.model in <input ng-model="model">. Then the property will be changed in parent's scope and that is what you usually want to achieve.
However using $parent might look not good for someone, so better solution is to create a named model in the controller. The you can use it and follow the rule "always have a '.' in your ng-model." Child scope created by ng-if has access to parrent scope, so he can use already defined $scope.model and change $scope.model.text property.
Controller:
$scope.model = {};
Html:
<div ng-controller="MyCtrl">
<div id="innerDiv" ng-if="!something">
<input ng-model="model.text">
</div>
</div>`
(But remember that if you not define $scope.model in the controller, it would behave like in first example).
If you are not sure that you are in the same scopes, you can check it by displaying scope's $id. Simply add in html {{$id}} above ng-if (or ng-include, ng-switch) and inside.
<div ng-controller="MyCtrl">
scope id: {{$id}}
<div id="innerDiv" ng-if="!something">
child scope id:{{$id}}
</div>
<div>scope id again {{$id}}</div>
</div>`
Some examples:
https://jsfiddle.net/La90btfh/3/

using jqlite to target nested directive element

I have a directive that has a template something like:
<div class="some-class">
<div class="some-inner-class">
<div class="class-to-target">
</div>
</div>
</div>
in the directives link function I would like to assign 'class-to-target' to a variable and then use addClass() and removeClass(). Can anyone recommend a clean way to achieve this?
Any thoughts greatly appreciated
C
This should work just fine with the ng-class variable. You won't even need to use addClass() or removeClass(), you can simply manipulate the variables from within your linking function. Here is an example usage:
<div ng-class={classToTarget: applyClass}>
Then inside your linking function you simply set applyClass to true|false based on whether you want the class applied to the div or not. classToTarget should be set to the name of the class you want to apply to the div.
The ng-class documentation has some really good examples covering this exact scenario. See https://docs.angularjs.org/api/ng/directive/ngClass for more information.

Angularjs: is this the correct way to use controller and directive?

Normally the directive will use data in the scope, for example:
<div my-directive
data-x='x'
data-y='y'
data-z='z'
...>
</div>
However, the arguments of the directive grow very fast. So I put them in a model class.
So each of my directive has it's own model class.
<div my-directive
data-model='myDirectiveModel'>
</div>
In controllers, I just need to set
$scope.myDirectiveModel = {
x: 100.
y: 200,
add: function(){
return this.x + this.y;
}
};
Is this the correct way to use controller and directive?
It's certainly a cleaner way of passing data through to a directive than having lots attributes in the markup.
However I would suggest that one of the good things about Angular is it's declarative syntax. If you always and exclusively do what you've suggested, when you come to read back your markup in a couple of months you'll have to delve into code to see what's going on. Being more explicit in your markup could save you some time. For example when you do this:
<div my-directive show-chickens="yes" chimp-count="monkeys" />
less is hidden from you when you read the markup.
However, if you are happy with your approach and want to continue, I would just declare the model as the value of the directive attribute like so...
<div my-directive='myDirectiveModel' />
just to make it even more clean.

Shouldn't there be an AngularJS ngWith directive?

Maybe I'm crazy, or too used to KnockoutJS, but I keep looking for an ngWith directive in the docs to define the scope on an element, in a controller, or for an included (ngInclude) partial.
For example:
I'd like to write a controller that augments MyItem like:
MyModule.controller('MyItemCtrl', function($scope) {
$scope.doSomethingToItem = function() {
$scope.name = "bar";
};
});
Or a view/template for MyItem like:
<div ng-controller="MyItemCtrl">
{{name}}
<button ng-click="doSomethingWithItem()">Do Something</button>
</div>
But in both of these cases I'm imagining my $scope to be prototypically inherit from my model, MyItem.
But the scope doesn't inherit from the model!!
Which baffles me.
Instead, my model is a property on the scope.
In the case of a repeater:
<div ng-repeat="item in list">
<div ng-controller="MyItemCtrl">
{{item.name}}
<button ng-click="doSomethingWithItem()">Do Something</button>
</div>
</div>
which means everywhere I have to use item.this or item.that instead of just this and that. I have to remember which functions are native to the model, and which were applied directly to the scope by a controller.
If I want to have a partial to display names (stupid example, I know, but you get the idea):
<h3>{{name}}</h3>
I have to write it
<h3>{{item.name}}</h3>
and then ensure the model is always item. Usually by wrapping it in a directive simply to defines a scope with an item property.
What I often feel like I want to do is simply:
<div ng-include="'my/partial.html'" ng-with="item"></div>
or
<div ng-repeat="list" ng-controller="MyItemCtrl">
{{name}}
<button ng-click="doSomethingWithItem()">Do Something</button>
</div>
Is there some magical directive out there that I haven't found? Or am I completely wrong and just looking for trouble?
Thanks.
EDIT:
Many thanks to Brandon Tilley for explaining the dangers of using scopes as models. But I still often find the need for some quick declarative scope manipulation and wish for an ng-with directive.
Take, for example, you have a list of items which, when clicked, shows an expanded view of the selected item. You might write it something like:
<ul>
<li ng-repeat="item in items" ng-click="selection = item">{{item.minView}}</li>
</ul>
<div ng-controller="ItemController">
{{selection.maxView}}
</div>
now you have to get properties of the selected item using selection.property rather than what I'd want: item.property. I'd also have to use selection in ItemController! Making it wholly coupled with this view.
I know, in this simple example I could have a wrapping controller to make it work, but it illustrates the point.
I've written a very basic with directive:
myApp.directive('with', ['$parse', '$log', function(parse, log) {
return {
scope: true,
link: function(scope, el, attr) {
var expression = attr.with;
var parts = expression.split(' as ');
if(parts.length != 2) {
log.error("`with` directive expects expression in the form `String as String`");
return;
}
scope.$watch(parts[0], function(value) {
scope[parts[1]] = value;
}, true);
}
}
}]);
that simply creates a new scope parsing one expression onto another value, allowing:
<ul>
<li ng-repeat="item in items" ng-click="selection = item">{{item.minView}}</li>
</ul>
<div with="selection as item" ng-controller="ItemController">
{{item.maxView}}
</div>
This seems infinitely useful to me.
Am I missing something here? Just making trouble for myself down the line somehow?
I've found I can just put an array around the source of ng-repeat and it becomes functionally an ng-with. :)
<div ng-repeat="name in [vm.dto.Person.Name]" >
<input type="text" ng-model="name.First" />
<input type="text" ng-model="name.Last" />
</div>
Seems like some purests may not like this... Also doesn't seem like there is a good alternative.
This is a great question. I can see how this may be confusing coming from another front-end framework, but in Angular, the scope has a reference to the model, and the syntax you're describing is normal. I personally like to describe the scope as more like a view model.
Miško Hevery, the author of AngularJS, does a good job of explaining this concept in this video, starting at about the 30 minute mark and lasting about 3 minutes:
People oftentimes think that the scope is the model, and that's not the case. Scope has references to the model. [...] So in the view, you say model dot whatever property you want to access.
While it may be possible to write an ngWith directive that does kind-of what you're looking for, since Angular uses prototypal inheritance for scopes, you will likely run into the same issues that Miško describes in the aforementioned video at 31:10 (where you think you're updating a value on the parent scope but you're actually not). For more details on prototypal inheritance in AngularJS, check out the excellent article The Nuances of Scope Prototypal Inheritance on the AngularJS wiki.
Another approach would be to set a new variable via ng-init:
<div ng-init="name = vm.dto.Person.Name" >
<input type="text" ng-model="name.First" />
<input type="text" ng-model="name.Last" />
</div>
I think one side effect of this when coming from knockout is that is encourages you to create more components with less responsibility, and of course within a component the 'this' is the viewmodel for that component. If you get to a point where it is really annoying you then it might be a sign you need a new component.
And it sure is nice being able to just refer to something in the component view model without thinking about the scope you're in. And you can't tell me you miss $parents[2] and $root ;-)
PS. Yes I know you can use let in knockout, which means fewer $ stuff.
I definitely miss knockout when doing Angular stuff (I still use both) but it sure is nice to do certain things things like title="Customer #{{ row.customerId }} - {{ row.fullName }}" and not have everything under data-bind.

How do I access the child ngModel from a directive?

Like in this question, I want to add .error on a form field's parent .control-group when scope.$invalid is true.
However, hardcoding the form name like in ng-class="{ error: formName.fieldModel.$invalid }" means that I can't reuse this in different forms, plus I'd rather not repeat this declaration everywhere.
I figured that a directive that looks something like this could work:
<div class="control-group" error-on="model1, model2">
<input ng-model="model1">
<input ng-model="model2">
</div>
So when either model1 or model2 is not valid, .control-group gets .error added.
My attempt here. Is it possible to access the models from the directive, given the model names?
If there's a better approach, I'd love to hear it too.
I don't think that writing a custom directive is necessery for this use-case as the ng-form directive was created exactly for situations like those. From the directive's documentation:
It is useful to nest forms, for example if the validity of a sub-group
of controls needs to be determined.
Taking your code as an example one would write:
<div class="control-group" ng-class="{ error: myControlGroup1.$invalid }>
<ng-form name="myControlGroup1">
<input ng-model="model1">
<input ng-model="model2">
</ng-form>
</div>
By using this technique you don't need to repeat expressions used in ng-model and can reuse this fragment inside any form.
You can also change the markup in the accepted answer to do without the nesting, since ng-form is also a class directive:
<div class="control-group ng-form" name="controlGroup11" ng-class="{ error: controlGroup1.$invalid }>
<input ng-model="model1">
<input ng-model="model2">
</div>
Final solution Fiddle
Inside your link function, you can get access to the formController. It has all of the controls. So the following will give your directive access to .$valid:
el.controller('form')[attrs.errorOn].$valid
However, I don't know how to watch that for changes. I tried watching attrs.errorOn (i.e., watch the ng-model property), but the watch doesn't trigger unless a valid value is input (because of the way Angular forms work... unless that value is valid, it is not assigned to the scope property set by ng-model.)
Fiddle.
Maybe someone can take this further...

Resources