AngularJS中$parsers.unshift与$parsers.push的区别及验证问题
First off, let's clarify the core difference between $parsers.unshift() and $parsers.push(), then dive into why your current code isn't behaving as expected.
$parsers.unshift() vs $parsers.push()
The $parsers array in AngularJS is a pipeline of functions that run in order from first to last when the view value (user input, or your array bound to ng-model) needs to be converted/synced to the model value. Here's the key distinction:
$parsers.unshift(yourFunction)adds your function to the start of the pipeline. This means it runs before any built-in AngularJS parsers or other custom parsers you've added. Use this when you need to modify or validate the value early in the process (e.g., before Angular's default validation kicks in).$parsers.push(yourFunction)adds your function to the end of the pipeline. It runs after all earlier parsers have completed. However, if any parser before it returnsundefined, the pipeline stops, and your pushed function will never execute.
Why Your Code Isn't Working
Let's break down the two issues you're facing:
1. $parsers.push() Function Never Runs
AngularJS's ngModel only triggers the $parsers pipeline when it detects a change in the view value reference. Since you're binding to an array (a reference type), modifying the array's internal elements (like pushing/popping items) doesn't change the array's reference—so ngModel doesn't see a "change" and won't fire $parsers.
Additionally, if any parser before your pushed function returns undefined, the pipeline halts, skipping your function entirely. Using unshift() avoids this risk if you need your logic to run first.
2. $validators.requiredCount Only Runs Once
By default, AngularJS runs validators only when:
- The model value is initialized
- The view value changes (again, based on reference for objects/arrays)
- The form is submitted
Since your array's reference isn't changing when elements are added/removed, the validator doesn't re-run after initialization.
Fixing Your Directive
To get your validation working properly and ensure $parsers triggers, here's a revised version of your directive:
return { restrict: 'A', require: 'ngModel', link: function ($scope, elem, attrs, ngModelCtrl) { // Use unshift to ensure this parser runs first (avoids pipeline halting issues) ngModelCtrl.$parsers.unshift(function (viewValue) { console.log('Parser triggered:', viewValue); // Always return the value (or modified value) to keep the pipeline running return viewValue; }); // Define your validator with safe fallback for undefined values ngModelCtrl.$validators.requiredCount = function (modelValue, viewValue) { const value = modelValue || viewValue || []; return value.length === parseInt(attrs.requiredCount, 10); }; // Watch for changes in the array's elements (not just reference) $scope.$watchCollection(attrs.ngModel, function (newVal) { if (newVal) { // Force ngModel to recognize the change and trigger parsers/validators ngModelCtrl.$setViewValue(newVal); // Manually trigger validation to re-run your requiredCount check ngModelCtrl.$validate(); } }); } };
Key Changes Explained:
- $watchCollection: This listens for changes to the array's elements (not just the reference), so it triggers when items are added/removed.
- $setViewValue: Forces ngModel to process the updated array, which fires the
$parserspipeline. - $validate(): Manually triggers all validators, ensuring your
requiredCountcheck runs every time the array changes. - Safe Fallback: Handles cases where
modelValueorviewValuemight beundefined(e.g., initial load).
Final Notes on $parsers Usage
- Use
unshift()when you need your logic to run early (e.g., sanitizing input before validation). - Use
push()only if you're sure earlier parsers won't returnundefinedand you need to run logic after all other parsing/validation is done.
内容的提问来源于stack exchange,提问作者Hamed Zakery Miab

