【问题标题】:Insert/Remove templates/views on button click in angularjs在angularjs中单击按钮时插入/删除模板/视图
【发布时间】:2015-03-10 20:28:05
【问题描述】:

在 ASP.NET MVC 中编程时,我习惯于使用局部视图进行 CRUD 操作。现在我正在使用 Spring + AngularJS,我希望我能以类似的方式工作。到目前为止,通过使用 angular-ui-router,我已经成功地完成了一些基本的创建/更新分区。

例子:

layout.html

<!doctype html>
<html lang="en" ng-app="Example">
<head>
    <meta charset="utf-8">
    <title>Test</title> 
</head>
<body>
<div class="container">
    <nav class="row">
        <ul class="nav nav-pills">
            <li ui-sref-active="active"><a ui-sref="clients">Clients</a></li>
        </ul>           
    </nav>    
</div>
<div class="container">
    <div class="row">
        <div class="col-md-12" ui-view></div>
    </div>
</div>
<script src="libs/jquery/dist/jquery.min.js"></script>
<script src="libs/angular/angular.min.js"></script>
<script src="libs/angular-ui-router/release/angular-ui-router.min.js"></script>
<script src="appjs/app.js"></script>
<script src="appjs/services.js"></script>
<script src="appjs/controllers/ClientController.js"></script>
<script src="appjs/filters.js"></script>
<script src="appjs/directives.js"></script>
</body>
</html>

/client/create.html

<h1>Create new client</h1>
<form name="clientForm">    
    <div ui-view></div>
</form>

/client/edit.html

<h1>Edit client</h1>
<form name="clientForm" ng-show="!loading">
    <input type="hidden" name="id" ng-model="client.id" />  
    <div ui-view></div>
</form>

/client/_createOrEdit.html

<div class="form-group" ng-class="{'has-error': clientForm.name.$invalid}">

    <label for="name">Name:</label>
    <input id="name" 
    name="name" 
    type="text" 
    ng-model="client.name" 
    class="form-control" 
    required />

    <span class="help-block" ng-show="clientForm.name.$error.required">Name field is required</span>

</div>

<div class="form-group" ng-class="{'has-error': clientForm.address.$invalid}">

    <label for="address">Address:</label>
    <input id="address" 
    name="address" 
    type="text" 
    ng-model="client.address" 
    class="form-control" 
    required />

    <span class="help-block" ng-show="clientForm.address.$error.required">Address field is required</span>

</div>


<button class="btn btn-primary btn-lg btn-block" 
ng-click="addOrUpdate(client)" 
ng-disabled="clientForm.$invalid || clientForm.$pristine">Save</button>

app.js

App.config(function($stateProvider, $urlRouterProvider) {
    $urlRouterProvider.otherwise("/");

    $stateProvider
    /* Client */
    .state("clients", {
        url : "/clients",
        templateUrl : "clients/index",
        controller : ClientListController
    })
    .state("clientCreate", {
        abstract : true,
        url : "/client",
        templateUrl : "clients/create",
        controller : ClientCreateController
    })
    .state("clientCreate.createOrEdit", {
        url : "/create",
        templateUrl : "clients/createOrEdit",
    })
    .state("clientEdit", {
        abstract: true,
        url : "/client/{id:[0-9]{1,9}}",
        templateUrl : "clients/edit",
        controller : ClientEditController
    })
    .state("clientEdit.createOrEdit", {
        url : "/edit",
        templateUrl : "clients/createOrEdit",
    })

});

ClientController.js

var ClientListController = function($scope, $http) {
    $scope.error = false;
    $scope.loading = false;

    $scope.getClientList = function() {
        $scope.loading = true;
        $http.get('clients/clientlist.json')
        .success(function(clientList) {
            $scope.clients = clientList;
            $scope.loading = false;
        })
        .error(function() {
            $scope.error = true;
            $scope.loading = false;
        });
    }

    $scope.getClientList();
};

var ClientCreateController = function($scope, $http, $location) {
    $scope.client = {};
    $scope.loading = false;

    $scope.addOrUpdate = function(client) {
        client.id = null;
        $scope.loading = true;
        $http.post('clients/createOrEdit', client)
        .success(function() {
            $scope.loading = false;
            $location.path('/clients').replace(); //redirect to index 
        });
    };
};

var ClientEditController = function($scope, $stateParams, $http, $location) {
    $scope.client = {};
    $scope.error = false;
    $scope.loading = false;

    $scope.getClientById = function(id) {
        $scope.loading = true;
        $http.get('clients/' + id + '/client.json')
        .success(function(client) {
            $scope.client = client;
            $scope.loading = false;
        })
        .error(function() {
            $scope.error = true;
            $scope.loading = false;
        });
    }

    $scope.addOrUpdate = function(client) {
        $scope.loading = true;
        $http.post('clients/createOrEdit', client)
        .success(function() {
            $scope.loading = false;
            $location.path('/clients').replace(); //redirect to index
        });
    };

    var selectedId = $stateParams.id;
    $scope.getClientById(selectedId);
}

这是我目前的设置,我对它本身非常满意。然而,当试图通过增加一点复杂性来扩展它时,我陷入了困境。

假设我们的客户可以有多个位置,那么现在每个客户都有一个位置列表。

我想做的是制作用于创建/编辑位置的部分视图,并在编辑或创建客户端时为客户端拥有的每个现有位置显示它们、删除它们的能力以及添加新位置的能力,所有这些在客户端创建/编辑页面上。

在 ASP.NET MVC 中,我可以使用部分视图、BeginCollectionItem 和一些 jQuery 来完成此操作。我应该如何用角度来解决这个问题?

我尝试在内部使用带有 ng-include 的 ng-repeat,但是在尝试添加/删除位置时,ng-repeat 上没有任何反映,而且我也不喜欢这种方法。

我认为单击按钮时没有发生任何事情的原因与我在使用 insertLocation 方法单击按钮时在控制台中收到Async XHR deprecated warning 有关。

insertLocation() 看起来像这样

$scope.insertLocation = function() {
    $scope.client.locations.push({});
}

并放置在创建和编辑控制器中,并且客户端变量也被编辑为如下所示

$scope.client = {locations: []};

请注意。我并不是在寻找解决这种情况的方法(尽管它会有所帮助),但更多的是对我的方法的建议。这是错的吗?如果是正确的心态,正确的方法是什么?

【问题讨论】:

  • 我对布局有点困惑,为什么会有 CreateOREdit 视图?我明白了有一个用于创建客户端的创建视图,用于编辑的编辑,createoredit 做什么?
  • @Rohit 创建和编辑表单都有相同的输入,两者之间的唯一区别是编辑还有一个隐藏输入来存储已编辑项目的 id。因此 createOrEdit 包含创建和编辑操作通用的表单输入。其目的是尽可能地遵守 DRY 原则。
  • 您的意思是对位置重复第二个form-group 1 次或更多次? (n 个位置 + 1 个空用于添加新位置或类似的东西?)
  • @craigb 位置是具有 3 个属性的对象。我想要一个包含这些属性中的每一个的表单输入的部分视图,并且 1. 为客户拥有的每个现有位置重复 n 次,并用相应的数据(以及隐藏的 id 输入)填充它,2. 在按下 + 按钮添加相同的部分,没有隐藏的 id 输入和空输入。

标签: angularjs angularjs-ng-repeat angular-ui


【解决方案1】:

n 个位置可以使用 ng-repeat 渲染,并带有一个用于添加新位置的额外表单组:

<div class="form-group" ng-repeat="location in client.locations">
    <label for="address">Address:</label>
    <input type="text" model="location.address"  />
    <a ng-click="remove(location)">remove</a>
</div>

<div class="form-group">
    <label for="address">Address:</label>
    <input type="text" model="newlocation.address"  />
    <a ng-click="addLocation()">add</a>
</div>

控制器看起来像这样:

.controller('MyCtrl', function($scope, client) {

    $scope.client = client;
    $scope.newlocaiton = {
        address: null
    }

    $scope.remove = function(location) {
        // using underscore.js/lodash.js for remove
        _.remove($scope.client.locations, location);
    };

    $scope.addLocation = function() {
        // ignoring whatever you might need to do calling 
        // server to persist etc. Just adding to client
        $scope.client.locations.push($scope.newlocation);
    };
})

至于“部分”我并不完全清楚你想要什么,但标记重用的 Angular 方式是通过这样的指令:

.directive('LocationForm', function() {
    return {
        restrict: 'EA',
        scope: {
            location: '=',
            onRemove: '='
        },
        templateUrl: 'location.edit.html',
        link: function(scope, el, attr) {
            // omitted
        }
    }
})

location.edit.html

<label for="address">Address:</label>
<input type="text" model="location.address"  />
<a ng-click="remove(location)">remove</a>

及用法:

<div class="form-group" ng-repeat="location in client.locations">
    <location-form location="location" />
</div>

(注意,我没有尝试这个,所以指令示例可能不完整)

见:

【讨论】:

  • 我所说的部分是你所想的。可重复使用的标记。在 asp.net mvc 中,它们被称为“部分视图”,可以使用 @Html.Partial("viewlocation", bound_model) 注入到 html 中,或者由控制器返回以进行 ajax 调用。在我的 OP 中,我认为 _createOrEdit.html 是一个部分视图,因为它在创建和编辑 html 中都被注入。无论如何,我会尝试使用您的答案作为参考来做一些事情,看看我想出了什么,因此会更新 OP。哦,顺便说一句,感谢您向我介绍 lodash,它看起来是一个非常有用的库。
  • 使用您的示例作为参考,我设法得到了我想要的东西。将通过一个简单的示例很快更新 OP。赞成票和赏金归您所有。谢谢!
【解决方案2】:

为了完整起见,这是我的解决方案:Plunker link

为了希望让事情变得更简单,我已将客户端位置之间的多对一关系替换为项目与其子项目之间的多对一关系。

所以按照完成的顺序:

  1. 我创建了子项输入的“部分”html(可以说是“可重用”模板)。它看起来像这样:_SubItem.html(我决定重用 ASP.NET MVC 方法,即在部分前缀下划线)

    <div class="form-group">
      <label for="subinput1">subinput1</label>
      <input type="text" id="subinput1" ng-model="subitem.subinput1" class="form-control">
    </div>
    <div class="form-group">
      <label for="subinput2">subinput2</label>
      <input type="text" id="subinput2" ng-model="subitem.subinput2" class="form-control">
    </div>
    <div class="form-group">
      <label for="subinput3">subinput3</label>
      <input type="text" id="subinput3" ng-model="subitem.subinput3" class="form-control">
    </div>
    <button class="btn btn-danger btn-block" ng-click="removeSubItemAt($index)">Remove SubItem</button>
    
  2. 然后我用它创建了一个指令。目前我对该指令非常不满意,因为它没有像上面 craigb 的回答那样使用孤立的范围。它不使用隔离范围的原因是因为我需要对 ng-repeat 的 $index 的引用,最重要的是对拥有项目控制器的控制器范围的引用,以便我可以使用 removeSubItemAt(index) 方法。就我而言,这本身就是一个糟糕的设计。理想情况下,我想创建一个链接到指令本身的特殊控制器,并让它引用子项所有者,以便指令控制器具有在所有者中操作其自己的集合的必要方法。或者只是传递索引和删除函数。但是,我目前还没有足够的知识来完成类似的事情。以下是指令当前的外观:

    .directive('subItemPartial', function(){
        return {
          restrict: "E",
          templateUrl: "_SubItem.html"
        };
    });
    

    看起来很简单。下面是它在index.html 中的调用方式

    <div class="col-xs-4" ng-repeat="subitem in item.subitems track by $index">
      <sub-item-partial></sub-item-partial>
    </div>
    

这基本上就是部分指令。项目控制器包含用于创建和删除子项目的方法。此外,您可能会注意到 ng-repeat 是 tracked by $index。这样做的原因是我希望能够一次创建 n 个子项,填写它们并将它们传递给服务器。由于ng-repeat 默认使用对象本身而不是它们的索引,因此它不允许重复条目。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-01-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-06-27
    • 2013-09-12
    • 1970-01-01
    相关资源
    最近更新 更多