One feller's views on the state of everyday computer science & its application (and now, OTHER STUFF) who isn't rich enough to shell out for www.myfreakinfirst-andlast-name.com
Using 89% of the same design the blog had in 2001.
FOR ENTERTAINMENT PURPOSES ONLY!!!
Back-up your data and, when you bike,
always wear white.
As an Amazon Associate, I earn from qualifying purchases. Affiliate links in green.
controller:
["$scope", /* more jive */],
function ($scope /* more jive */)
{
$scope.$watch('inLinks', function (value) {
// omgwtfbbq?
// ...
Well, that's @ and = stuff is pretty cryptic, especially since I hadn't used a
directive in AngularJS before (Angular 2+, sure, but still learning
ye olde AngularJS -- going backwards, I know. Don't forget I had my first run-in with Perl in 2014 [sic]).
[Having to have a
separate controller to change direct scopes] is clearly not a
great solution.
What we want to be
able to do is separate the scope inside a directive from the
scope outside, and then map the outer scope to a directive's
inner scope. We can do this by creating what we call an isolate
scope. To do this, we can use a directive's scope option:
...
Let's take a closer
look at the scope option:
//...
scope:{
customerInfo:'=info'},//...
The scope option is
an object that contains a property for each isolate scope
binding. In this case it has just one property:
Its name (customerInfo)
corresponds to the directive's isolate
scope property, customerInfo.
Its value (=info) tells $compile to
bind to the info attribute.
Good heavens, AngularJS. Not the most discoverable. Seems like in AngularJS in general there's
lots of magic stringing. Perhaps I shouldn't be surprised, but that doesn't mean I like it. (Here I mean largely inline
array notation, which is too hipster by half. I mean, I get
that you don't what to lose information when you minimize, but then
how about pass real references, not refer to objects by their
original names? Sheesh.* )
But let's go a little deeper so that we know what the @ symbol means
too.
The 'isolate' scope object hash defines a set of local
scope properties derived from attributes on the directive's
element. These local properties are useful for aliasing values
for templates. The keys in the object hash map to the name of
the property on the isolate scope; the values define how the
property is bound to the parent scope, via matching attributes
on the directive's element:
@ or @attr -
bind a local scope property to the value of DOM attribute.
The result is always a string since DOM attributes are
strings. If no attr name
is specified then the attribute name is assumed to be the
same as the local name. Given <my-componentmy-attr="hello
{{name}}"> and
the isolate scope definition scope:{ localName:'@myAttr'}, the directive's scope
property localName will
reflect the interpolated value of hello {{name}}. As the name attribute
changes so will the localName property
on the directive's scope. The name is
read from the parent scope (not the directive's scope).
= or =attr -
set up a bidirectional binding between a local scope
property and an expression passed via the attribute attr. The
expression is evaluated in the context of the parent scope.
If no attr name
is specified then the attribute name is assumed to be the
same as the local name. Given <my-componentmy-attr="parentModel"> and
the isolate scope definition scope:{ localModel:'=myAttr'}, the property localModel on
the directive's scope will reflect the value of parentModel on
the parent scope. Changes to parentModel will
be reflected in localModel and
vice versa. If the binding expression is non-assignable, or
if the attribute isn't optional and doesn't exist, an
exception ($compile:nonassign)
will be thrown upon discovering changes to the local value,
since it will be impossible to sync them back to the parent
scope.
By
default, the $watch method
is used for tracking changes, and the equality check is
based on object identity. However, if an object literal or
an array literal is passed as the binding expression, the
equality check is done by value (using the angular.equals function).
It's also possible to watch the evaluated value shallowly
with $watchCollection: use =* or =*attr
If you've used Angular 2+ (what a failed naming scheme, btw. Why not
"Angular.IO" after the website or just NGular or something so that
googling this jive would be easier?), you can already see our steady
progression to banana
boxes.
But for now, what a mess.
If a scope property is a string that starts with @, then we'll
initialize that scope property with the directive's DOM
attribute that matches what comes after the @.
That is, this is a one-way process. Props down, and set up
events if changes need to go back up.
If the prop starts with an =, we're doing the equivalent of
Angular.IO's banana boxing, it appears.
So if we change whatever's in here, the parent is changing
too.
Back to our original example now... We had a cryptic (to me) $scope.$watch('inLinks',
function (value)..., and now we know what that means. @ means
we've got a prop that $watch can check for changes, which, in
this case, means:
The listener is
called only when the value from the current watchExpression and
the previous call to watchExpression are
not equal (with the exception of the initial run, see below).
Inequality is determined according to reference inequality, strict comparison via
the !== Javascript
operator, unless objectEquality ==true (see next
point)
When objectEquality ==true, inequality of the watchExpression is
determined according to the angular.equals function.
To save the value of the object for later comparison, the angular.copy function
is used. This therefore means that watching complex objects
will have adverse memory and performance implications.
Guess that works. Let's ignore the =* shallow comparison jive for
now. All we've really got is one prop called inLinks that will be
passed... um... "in" that we'll update whenever it changes, probably
after the page finishes loading and making some resource calls. And
sure enough...
<li ng-repeat="subLink in link.SubLinks|filter: {ShowInNav:
true}" ...
That means once we've loaded the correct navigational links for this
context, we'll start pushing them into the DOM.
* Sorry, I've tried to come up with a good synonym, but "stupid"
fits here. My earlier use of "hipster" was my kind attept at an
alternative, but this stuff really feels a little too much like a
first-pass solution to be in a nice, mature templating library.
The postings on this site are [usually] my own and do not necessarily reflect the views of any employer, past or present, or other entity. About Our Author