如何在DOM操作后更新Angular绑定?通用实现方案问询
Great question—this is a common pain point when mixing declarative frameworks like AngularJS (since you’re using ng-repeat) with imperative tools like SVG-Edit. Let’s break this down into actionable steps to build a generic reverse-binding solution:
1. First: Understand AngularJS’s Binding Flow
AngularJS defaults to model → view binding via $watch and the digest cycle. For reverse sync (view → model), we can’t rely on ng-model (it’s built for form controls, not SVG elements). Instead, we need to manually map DOM changes back to the underlying scope data.
2. How to Find All Bindings on a DOM Element
AngularJS stores binding context in the element’s attached scope, and we can use the $parse service to unpack interpolated expressions (like {{item.x}}):
- First, grab the element’s scope with
angular.element(svgEl).scope()(forng-repeatitems, this will be the child scope created for eachitem). - Then, iterate over the element’s attributes to find any with
{{ }}interpolation. For each matching attribute, strip the curly braces to get the raw expression (e.g.,item.xfromx="{{item.x}}"). - Use
$parse(expression)to create a getter/setter function for that model path.
Here’s a quick snippet to collect bindings:
const el = angular.element(modifiedSvgElement); const scope = el.scope(); const bindings = {}; // Loop through element attributes to find interpolated bindings angular.forEach(el[0].attributes, attr => { if (attr.value.includes('{{')) { const rawExpr = attr.value.replace(/{{|}}/g, '').trim(); bindings[attr.name] = { setter: $parse(rawExpr).assign, // Function to update the model rawExpr: rawExpr }; } });
3. Writing DOM Changes Back to the Model
Once you have the bindings, the next step is to sync updated DOM values to the scope. Don’t listen directly to DOM changes (like using MutationObserver)—SVG-Edit has its own events that fire when an element is modified, which are far more efficient.
For example, if SVG-Edit triggers an elementChanged event when editing finishes:
// Hook into SVG-Edit's edit completion event svgEditor.on('elementChanged', modifiedEl => { const el = angular.element(modifiedEl); const scope = el.scope(); const bindings = /* fetch bindings as above */; // Use $timeout to ensure DOM updates are fully applied first $timeout(() => { angular.forEach(bindings, (binding, attrName) => { // Parse the DOM value (adjust type as needed: number, string, etc.) const newValue = parseFloat(el.attr(attrName)); // Write the value back to the scope model binding.setter(scope, newValue); }); // Trigger a digest cycle to sync any dependent bindings scope.$apply(); }); });
Handling Complex Expressions
If you have bindings like x="{{item.x * scale}}", reverse-sync gets trickier—you can’t directly derive item.x from the x value unless you know scale. For a generic solution, it’s best to design your SVG bindings to reference raw model properties (not computed values) whenever possible. If you must use computed expressions, store the dependencies (like scale) in the scope so you can reverse-calculate the original model value.
4. Do You Need to Subscribe to DOM Changes?
Avoid direct DOM change listeners (like MutationObserver) unless SVG-Edit has no exposed events. DOM changes fire constantly during edits (e.g., dragging an element triggers hundreds of x/y updates), which would flood your digest cycle and hurt performance.
SVG-Edit’s built-in events are tailored to user actions (e.g., finish dragging, resize complete) and are the right hook for syncing changes in batches.
5. Building a Generic Directive
To make this reusable, wrap the logic in an AngularJS directive. This way, you can add reverse-binding to any SVG element with a single attribute:
app.directive('svgReverseBind', ($parse, $timeout) => { return { link: (scope, el, attrs) => { // Pre-collect bindings on directive initialization const bindings = {}; angular.forEach(el[0].attributes, attr => { if (attr.value.includes('{{')) { const rawExpr = attr.value.replace(/{{|}}/g, '').trim(); bindings[attr.name] = $parse(rawExpr).assign; } }); // Listen for SVG-Edit's element modification event svgEditor.on('elementChanged', modifiedEl => { if (modifiedEl !== el[0]) return; $timeout(() => { angular.forEach(bindings, (setter, attrName) => { const newValue = parseFloat(el.attr(attrName)); setter(scope, newValue); }); scope.$apply(); }); }); } }; });
Use it in your template like this:
<rect ng-repeat="shape in shapes" x="{{shape.x}}" y="{{shape.y}}" width="{{shape.width}}" svg-reverse-bind></rect>
- Scope Inheritance: For
ng-repeatitems, the child scope’sshapeis a reference to the parent scope’s array item—so updatingshape.xin the child scope will automatically sync back to the parent array (no need for$parent). - Type Handling: Adjust the value parsing (e.g.,
parseFloat,parseInt, raw strings) based on the attribute type (e.g.,fillis a string,xis a number). - Performance: If you’re editing dozens of elements at once, batch the
$applycalls to avoid excessive digest cycles.
内容的提问来源于stack exchange,提问作者Wouter Schut

