I created a component search-context, which works well. It's configurable and it does what it's supposed to do.
<search-context context-name="Groups"
compare-columns="['displayName']"
search-manager="$ctrl"
query-url="/group/search/{{contextId}}"
icon="fa fa-users"
on-resolve-item-url="resolveItemUrl(row)"></search-context>
Here it is in action, standalone.
There are various other search contexts, and I'd like to create a search-manager component such that I can write markup like this:
<search-manager>
<search-context context-name="Devices"
compare-columns="['displayName']"
search-manager="$ctrl"
query-url="/device/search/{{contextId}}"
icon="fa fa-laptop"></search-context>
<search-context context-name="Groups"
compare-columns="['displayName']"
search-manager="$ctrl"
query-url="/group/search/{{contextId}}"
icon="fa fa-users"
on-resolve-item-url="resolveGroupEditUrl(row)"></search-context>
</search-manager>
The general plan is for search-context to check whether it has a search-manager and if so suppress its own input/button controls, and the search-manager will supply input controls and supply the search term to the search contexts.
The examples in the AngularJS component documentation demonstrate dynamic child controls using ng-repeat in the control template, but it's not clear how to set things up to handle explicit markup such as I propose. If at all possible I'd prefer not to need to explicitly specify the search-manager="$ctrl"
parent reference.
How does one go about this and what are the supporting topics one must research and understand? Just the key concept names would be a big help but an overview and a further-reading list would be awesome.
My first attempt at the template for search-manager
looks like this
<div>
<div class="panel-heading">
<h3 class="panel-title">Search</h3>
<div class="input-group">
<input class="form-control" ng-model="$ctrl.term" />
<span class="input-group-btn" ng-click="$ctrl.search()">
<button class="btn btn-default">
<i class="fa fa-search"></i>
</button>
</span>
</div>
</div>
<div class="panel-body">
<ng-transclude></ng-transclude>
</div>
</div>
The code looks like this
function SearchManagerController($scope, $element, $attrs, $http) {
var ctrl = this;
ctrl.searchContext = [];
ctrl.registerSearchContext = function (searchContext) {
ctrl.searchContext.push(searchContext);
}
ctrl.search = function () {
ctrl.searchContext.forEach(function (searchContext) {
searchContext.search(ctrl.term);
});
};
}
angular.module("app").component("searchManager", {
templateUrl: "/app/components/search-manager.html",
controller: SearchManagerController,
transclude: true,
bindings: {
term: "@"
}
});
The child components are transcluded but they need a reference to the search-manager
component, and $ctrl
is not in scope.
How do we get a reference to the parent?
To obtain a reference to the parent all you need to do is require
the parent in the search-context
declaration. The double caret prefix means to search the parents. Single caret starts with the current object which will work but is slightly less efficient. The question mark means don't barf if you can't find it, just return undefined. This is necessary when the component may not always be parented by a search manager.
angular.module("app").component("searchContext", {
templateUrl: "/app/components/search-context.html",
controller: SearchContextController,
require: {
searchManagerCtrl: "?^^searchManager"
},
bindings: {
...
}
});
But what if you need the parent to have references to the children?
In the SearchContextController
we implement the $onInit
lifecycle event handler.
ctrl.$onInit = function () {
if (ctrl.searchManagerCtrl) {
ctrl.searchManagerCtrl.registerSearchContext(ctrl);
}
};
registerSearchContext
is a method defined in the parent's controller for this purpose. The implementation essentially pushes each registered control into an array property we define on its scope, and then methods of the parent can enumerate the children.
For a directive this require
trick is expressed slightly differently. You must declare the property searchManagerCtrl
in the directive scope, and supply the expression directly as the value of require.
require: "?^^searchManager",
You must also supply a link
function. One of the parameters of a link
function is controller
and a reference to searchManager
will be passed in this parameter, at which point you can assign it to a property of the directive scope. The $onInit
lifecycle event is still available for registering with the search manager, but refers to $scope
rather than ctrl
.