1

I am creating a component to edit an array of simple objects.

According to the Angular 1.7.2 documentation, components should use one-way (< and @) bindings where possible, and use & bindings for output callbacks. It states:

For components however, only the component that owns the data should modify it, to make it easy to reason about what data is changed, and when.

Regarding input objects and arrays, it states specifically:

The general rule should therefore be to never change an object or array property in the component scope.

My component accepts an array of objects as input, provides a way to edit the properties on these objects, and insert and delete objects to the array. The component needs to update its view if the object array is changed elsewhere. The component does not own the data it accepts as input.

I've defined the bindings of the component as follows:

{
    objects: "<",
    addObject: "&",
    deleteObject: "&",
    updateObject: "&"
}

Considering that the objects data should not be changed by the component, the component must make a local copy of the objects during $onChanges. Changes to the local copy are then propagated to the parent via the callbacks.

However, this creates a disconnect between the local copy and the original data. $onChanges is only triggered if the objects reference itself is reassigned.

The solution to that is to place a watch on objects with object equality that triggers the creation of the local copy. This seems counter to what components are supposed to do, and I've seen elsewhere that $watch used in components is seen as a crutch and/or smelly design.

This plunker demonstrates the issue.

What is the correct way to implement this design?

2 Answers 2

2

I believe the documentation you are referencing is talking specifically about changing an object or array property, as this change will not be reflected in the parent scope. I don't see any issue with a component mutating the objects passed in.

For your example - if you pass an array via the "objects" binding into your component - and then your component sets its "objects" property to a different array - the parents original array will not reference this change.

The difference to = is that the bound properties in the component scope are not watched, which means if you assign a new value to the property in the component scope, it will not update the parent scope. Note however, that both parent and component scope reference the same object, so if you are changing object properties or array elements in the component, the parent will still reflect that change. The general rule should therefore be to never change an object or array property in the component scope.

Sign up to request clarification or add additional context in comments.

4 Comments

The documentation also states "only the component that owns the data should modify it".
Fair enough. I would argue it's fine for an 'objects editor' to edit objects. I disagree with your statement that you need to create a local copy of the objects. Leave the reference to the passed in array as-is and modify it in the parent via callbacks.
How would you implement the input and select bindings without mutating the passed in array data?
I wouldn't..I would mutate the passed in array data. That said, you could explore adding listeners for change events, then calling preventDefault(). Pass the change out to the parent and have it mutate the objects.
0

As for me the answer would be very simple - components are not ment to perform data manipulation. You told that you created component to edit array? Then this is where you can start using services. Each service is a singleton, therefore you can reference the same object property from every place you did inject it. Checkout what AngularJS team says about services

Managing data via scopes is reckless. Scopes can easily become corrupted or dirtied by other controllers, directives, models and the like. It gets messy quickly. Having a central channel (in this case a service) ... not only is a lot cleaner - it’s also much easier to manage as the application grows. Lastly - it keeps your code modular (something Angular excels at). Should you ever need that service for another project, you don’t need to dig through scopes, controllers, filters, etc. to find relevant code - it’s all there in the service!

Speaking about components - first of all they are HTML directives. Component should have HTML template. The purpose of a component is to reflect some data received from the parent scope in its template and pass some event or data (which is a result of user interaction) to the higher level through callback. If you need an external logic in the component - better delegate it to a service.

2 Comments

This doesn't appear to answer the question. There's nothing stopping using a service to perform the data manipulation - a controller was used in the example for expediency. Using a service to share the same object property doesn't help if we want to reuse the component for other objects elsewhere.
the problem is not in the implementation, but in the whole approach you chose

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.