Suppose I have HTML with AngularJS module/controller as follows:
angular
.module("myModule", [])
.controller("myController", ['$scope', '$compile', function ($scope, $compile) {
$scope.txt = "<b>SampleTxt</b>";
$scope.submit = function () {
var html = $compile($scope.txt)($scope);
angular.element(document.getElementById("display")).append(html);
}
}]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<body ng-app="myModule" >
<div ng-controller="myController">
<form name="myForm">
<span>Age:</span><input type="number" name="age" ng-model="age"/>
<textarea ng-model="txt" ></textarea>
<input type="button" value="submit" ng-click="submit()" />
</form>
<div id="display"></div>
</div>
</body>
The above sample will allow adding an element to AngularJS app during run-time using $compile.
Suppose I want to insert an input element such as a text box name driversLinces
with the attribute ng-required="age > 21"
, suppose I want to insert this element with conditional required feature from the JavaScript console for testing and verification purposes. Also, suppose I want to do the same but I want to modify the ng-required
property if an existing element such as the age
, how I can do that?
I am thinking to create a function that will access $compile
somehow but not sure how. Can you help me? I am able to access the $compile service only from inside the controller.
Note: due to certain limitations and lack of information/resources, I have limited access to the full HTML code. I can access the HTML and AngularJS forms using a UI Modeler. I can add my custom HTML code but I don't know if I can enclose an existing Form Part with my own custom HTML container which is required to add a directive to access the inner parts.
I can access AngularJS scope and ng-form
elements using angular.element()
. I can trigger my JavaScript on Form-Load, on a click of a button, or when a model value changes. I can add a form element and link it to an AngularJS model. I could not figure out how to access the $compile service from JavaScript.
I will add more info to explain my objective or the use-case.
I want to add custom validation rules and errors to the AngularJS form from JavaScript. The platform I am working with uses AngularJS, but doesn't allow me to get easy access to AngularJS code to add directives, or at least for now, I don't have the needed resources for this purpose. However, this platform provides me with ability to trigger my custom JavaScript code on a click of a button which can be triggered automatically when the form loads (on-load event). Also, I can pass the ID of the button that was clicked. With this, I was able to access the scope using angular.element('#id').scope()
. This enabled me to access almost all the other elements. I can see all ng-models
and ng form controllers
and all its parents in the scope object. Sometimes, I have to access the $parent
to reach to the root, but I think eventually I am able to access almost anything from the scope object.
Now, I need to be able to find the form elements and add custom validation rules. I can travers all form elements from the scope object, and I can figure out how to get the element ID and its binding details. All I need now is how to add a custom validation rule and error message on form-load event.
For example, I can use JSON to represent validation rules for AngularJS form as follows:
[
{
"id": "employee-name",
"required": true,
"msg": "Employee name is required."
},
{
"id": "degree",
"customValidation": "someJSFunctionName",
"msg": "The provided degree is invalid. Please review the rules and try again."
}
]
Then, on form-load event, I want to load the above rules on the form and make them effective. How is this possible? Consider that I have access to the scope object, I can use only JavaScript, and I cannot use AngularJS directives.
Based on answer provided by PhineasJ below, I used the console with the following commands:
var injector = window.angular.injector(['ng']);
var $compile = injector.get('$compile');
var elm = angular.element("my-element-selector");
var elmScope = elm.scope();
elm.attr('ng-required', true);
var elmCompile = $compile(elm[0])(elmScope);
While the above didn't throw any error, however, it is not working as it should. If I make the field elm
empty, it won't trigger the ng-required
error, though I can see that the required
attribute was added after executing the $compile
command. I noticed that I have to execute the $compile
service every time I update the field value so that the validation rule will reflect, but I don't see the field's ctrl.$error
object being updated. It is always empty.
Then I tried the following:
var injector = window.angular.injector(['ng', 'myApp']);
var $compile = injector.get('$compile');
... I got the error Uncaught Error: [$injector:unpr] Unknown provider: $rootElementProvider
.
Then I tried the following:
var mockApp = angular.module('mockApp', []).provider({
$rootElement:function() {
this.$get = function() {
return angular.element('<div ng-app></div>');
};
}
});
var injector = window.angular.injector(['ng', 'mockApp', 'myApp']);
... no errors were thrown the first time, but when I tried again, I got the error The view engine is already initialized and cannot be further extended
. So I am stuck with the $compile
service.
I did try adding the rules directly using $validators()
and it was a success. See details below:
//The elm form controller is found on the $parent scope and this is beyond my control.
//The ng-form element names are generated by the back-end and I have no control over this part. In this case the HTML element 'elm' is the form element name 'ewFormControl123'.
elmScope.$parent.ewFormControl123.$validators.required =
function (modelValue, viewValue) {
console.log(modelValue, viewValue);
var result = !!(modelValue??null);
console.log("result = ", result);
return result;
}
The above does seem to work fine, however, I am still interested in using $compile
by injecting the validation rules or the directives into the HTML code and then run the $compile
service over that element. I think injecting the needed parts into the HTML and run $compile
is better.
With the help of @PhineasJ, I managed to prepare a small sample that uses AngularJS injector
and $compile
service. This is working successfully, but the same approach is not working on the target application.
w3school original sample: https://www.w3schools.com/angular/tryit.asp?filename=try_ng_ng-required
JS Fiddle sample: https://jsfiddle.net/tarekahf/5gfy01k2/
Following this method, I should be able to load validation rules during run-time for any field as long as there is a selector to grab the element.
I am now struggling with the errors I get when applying the same method on the target application. I have two problems:
const injector = angular.injector(['ng', 'myApp'])
with the app name, I get the error: Uncaught Error: [$injector:unpr] Unknown provider: $rootElementProvider
However, if I use the formController.$validators
object, I can a add the validation rule and it is respected. I am not sure why no one is recommending this approach.
I appreciate your help and feedback.
I found the solution. Check the answer I am adding below.
Tarek
The fix was to use the following command to get the injector:
var injector = angular.element("#myAngularAppID").injector();
where myAngularAppID
id the element ID of the HTML element where ng-app
is defined.
The following variations of the injector statements didn't work:
//Without using the app
var injector = window.angular.injector(['ng']);
//With the app
var injector = window.angular.injector(['ng', 'myApp'])
Once the above correction was implemented, the problem was solved.
Special thanks @PhineasJ. Also, the following references helped me:
Check the JS Fiddle which has all possible variations for using $compile
to add HTML elements dynamically and bind them to AngularJS Scope: