flob
flob

Reputation: 3908

Something like angular `<ng-content>` for Angular.js (1.6) components?

Is there something like the angular <ng-content> for Angular.js components without using transclude ?

I try to get some structural components (row-div+label+error+help-popup) to work with different fillings (select, input, checkbox, radio, ...) and want to have one component that helps providing the layout and therein one that provides the custom inputs.

The code I would like to use is something like:

html

<form-row input-id="someId" input-name="">
  <form-row-input-typeahead ng-model="some.thing"></form-row-input-typeahead>
</form-row>

formRow.html & .js

 <div class="form-row" ng-form="row"
     ng-class="{'form-row-error' : $ctrl.isInvalid()}">
    <label ng-attr-for="{{$ctrl.inputId}}">
        <span translate="{{$ctrl.label}}"></span>
    </label>
    <div class="form-row-input">
        <ng-content></ng-content>
    </div>
    <div class="form-row-error">
        <p class="form-error--msg"
           ng-show="$ctrl.isInvalid()"
           translate="{{$ctrl.errorMessage}}">
        </p>
    </div>
</div>
"use strict";
import module from './module';
import formRowHtml from './formRow.html';

FormRowController.$inject = ['$scope'];
function FormRowController($scope) {
  const ctrl = this;
  ctrl.isInvalid = isInvalid;

  function isInvalid() {
    return $scope.row[ctrl.inputName].$invalid && $scope.row[ctrl.inputName].$touched;
  }
}

export default module.component('formRow', {
  template: formRowHtml,
  controller: FormRowController,
  bindings: {
    inputName: '<',
    inputId: '<',
  }
});

formRowInputTypeahead.html & .js

<input type="text" name="{{$ctrl.row.inputName}}"
   ng-attr-id="{{$ctrl.row.inputId}}"
   ng-model="$ctrl.ngModel"
   uib-typeahead="option for option in $ctrl.options | filter:$viewValue | limitTo:5"
   ng-change="$ctrl.ngChange"
   ng-required="$ctrl.ngRequired">
"use strict";
import module from './module';
import formRowInputTypeaheadHtml from './formRowInputTypeahead.html';
import angular from 'angular';

FormRowInputTypeaheadController.$inject = ['$scope'];
function FormRowInputTypeaheadController($scope) {
  const ctrl = this;
  ctrl.$onInit = $onInit;

  function $onInit() {
    if (angular.isUndefined(ctrl.ngRequired) || ctrl.ngRequired === null)
      ctrl.ngRequired = true;
  }
}

export default module.component('formRowInputTypeahead', {
  template: formRowInputTypeaheadHtml,
  controller: FormRowInputTypeaheadController,
  require: {
    row: '^formRow',
  },
  bindings: {
    ngModel: '=',
    ngChange: '<',
    ngRequired: '<',
    options: '<',
  }
});

Upvotes: 5

Views: 4522

Answers (1)

flob
flob

Reputation: 3908

My solution is to just use <ng-transclude>.

It works without the scope resolution mind twist required in directives as components always define their own scope. Therefore it seems to be the clean and simple solution.

formRow.html & .js

 <div class="form-row" ng-form="row"
     ng-class="{'form-row-error' : $ctrl.isInvalid()}">
    <label ng-attr-for="{{$ctrl.inputId}}">
        <span translate="{{$ctrl.label}}"></span>
    </label>
    <div class="form-row-input">
        <ng-transclude></ng-transclude>
    </div>
    <div class="form-row-error">
        <p class="form-error--msg"
           ng-show="$ctrl.isInvalid()"
           translate="{{$ctrl.errorMessage}}">
        </p>
    </div>
</div>
"use strict";
import module from './module';
import formRowHtml from './formRow.html';

FormRowController.$inject = ['$scope'];
function FormRowController($scope) {
  const ctrl = this;
  ctrl.isInvalid = isInvalid;

  function isInvalid() {
    return $scope.row[ctrl.inputName].$invalid && $scope.row[ctrl.inputName].$touched;
  }
}

export default module.component('formRow', {
  template: formRowHtml,
  transclude: true,
  controller: FormRowController,
  bindings: {
    inputName: '<',
    inputId: '<',
  }
});

Upvotes: 2

Related Questions