Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular - using the same form to Create and Update

I have a really simple CRUD app for managing music albums. Just two fields are tracked, title and artist.

In this example, the dropdown shows a list of albums, and if I fill out the form and click Save it will be added to the list.

In the second example, selecting an album will populate the form so it can be edited and updated.

My question is, is there a way to get both functionality in the same form? Sure I could create two identical forms and have them do slightly different things, but given they're operating on the same data, it would be nice if when a current_album is selected, it updates, and when "New album..." is selected, it creates.

The major roadblock seems to be value vs ng-model. I can set the value so it populates when I pick an item from the <select> OR I can set an ng-model="newAlbum", but not both to my knowledge.

like image 890
diplosaurus Avatar asked Feb 26 '14 10:02

diplosaurus


2 Answers

You shouldn't be using the value attribute with ng-model. This is a very bad practice.

What I would suggest is to use ng-change on your list and keep a cloned object with the editing value.

$scope.onChange = function() {
  if ($scope.currentAlbum) {
    $scope.editing.title = $scope.currentAlbum.title;
    $scope.editing.artist = $scope.currentAlbum.artist;
  } else {
    $scope.editing = {};
  }
};

The all you need to do when saving is checking is it a new object or not:

$scope.addOrSaveAlbum = function() {
  if ($scope.currentAlbum) {
    $scope.currentAlbum.title = $scope.editing.title;
    $scope.currentAlbum.artist = $scope.editing.artist;
  } else {
    $scope.albums.push({ title: $scope.editing.title, artist: $scope.editing.artist });
  }

  $scope.editing = {};
};

See http://jsfiddle.net/4Zeuk/12/

(thank you to Wawy to point out ng-change instead of $scope.$watch)

like image 167
Benoit Tremblay Avatar answered Oct 13 '22 08:10

Benoit Tremblay


You can indeed get both functionality without the need of two different forms, but you can't use the same object in the scope in ng-model for both select and form fields.

But what you can do is have two different objects in the scope, one that contains the value of the selected item and the other will contain either a new instance of an album or a copy of the selected one. Then when you click the save/update button, based on the id of the object in the form you can decide if you need to save or modify the album collection.

Here is one way of doing what I've just explained:

<div ng-app="albumShelf">
<div ng-controller="MainCtrl">
  <div style="float:left;">
    <select ng-options="b.title for b in albums" ng-model="current_album" ng-change=updateForm()>
      <option value="">Choose album...</option>
    </select>
  </div>

  <div style="float:left;">
      <form>
        <input type="text" ng-model="newAlbum.title">
        <br>
        <input type="text" ng-model="newAlbum.artist">
        <br>
        <input type="submit" value="{{ current_album ? 'Update' : 'Save' }}" ng-click="modifyAlbumCollection()">
      </form>
  </div>
</div>

var albumShelf = angular.module('albumShelf', [/*'ngResource'*/])
.controller('MainCtrl', ['$scope', function($scope/*, albumFactory*/) {
    //$scope.albums = albumFactory.query();

    $scope.albums = [
        { id: 1, title: 'Disorganized Fun', artist: 'Ronald Jenkees' },
        { id: 2, title: 'Secondhand Rapture', artist: 'MS MR' }
    ];

    $scope.modifyAlbumCollection = function() {
        if ($scope.newAlbum.id !== null) {
            //We are modifying an existing album
            var i, found = false;
            for (i = 0; i<$scope.albums.length && !found; i++) {
                if ($scope.albums[i].id === $scope.newAlbum.id) {
                    $scope.albums[i] = angular.copy($scope.newAlbum);
                    found = true;
                }
            }
        } else {
            //We are adding a new album to the collection
            $scope.newAlbum.id = $scope.albums.length;
            $scope.albums.push(angular.copy($scope.newAlbum));
            $scope.newAlbum = { id: null, title: '', artist: '' };
        }
    };

    $scope.updateForm = function() {
        if ($scope.current_album) {
            //Copy the information from the selected album into the form.
            $scope.newAlbum = angular.copy($scope.current_album);
        } else {
            //Clear previous album info.
            $scope.newAlbum = { id: null, title: '', artist: '' };
        }
    };
}])
//.factory('albumFactory', function($resource) {
//    return $resource('/albums/:albumId.json', { albumId: '@id' }
//    );
//});

Here is the jsfiddle

In my opinion it's more clear if you use a ng-change in the select rather than a $watch on the ng-model value. Because what you want is update the form when a new value is selected from the dropdown rather than watching for changes on the object in the $scope.

like image 42
Wawy Avatar answered Oct 13 '22 08:10

Wawy