AngularJS: ¿cómo puedo crear un alcance nuevo y aislado mediante programación?

81

Quiero crear una AlertFactory con Angular.factory. Definí una plantilla html como seguir

var template = "<h1>{{title}}</h1>";

El título se proporciona llamando al controlador y se aplica de la siguiente manera

var compiled = $compile(template)(scope);
body.append(compiled);

Entonces, ¿cómo puedo pasar el alcance aislado del controlador a la fábrica? Estoy usando el código de seguimiento del controlador

AlertFactory.open($scope);

Pero $ scope es la variable de alcance del controlador global. Solo quiero pasar un pequeño alcance para la fábrica con solo propiedad de título.

Gracias.

Primer ministro
fuente

Respuestas:

107

Puede crear un nuevo alcance manualmente.

Puede crear un nuevo alcance desde $rootScopesi lo inyecta, o simplemente desde el alcance de su controlador; esto no debería importar, ya que lo aislará.

var alertScope = $scope.$new(true);
alertScope.title = 'Hello';

AlertFactory.open(alertScope);

La clave aquí es pasar truea $new, que acepta un parámetro para isolate, lo que evita heredar el alcance del padre.

Puede encontrar más información en: http://docs.angularjs.org/api/ng.$rootScope.Scope#$new

Alex Osborn
fuente
10
Si crea un nuevo alcance manualmente, probablemente también tendrá que destruirlo manualmente. Normalmente, es mejor no crear ámbitos manualmente.
Mark Rajcok
Hice una prueba, pero no funciona. Consulte stackoverflow.com/questions/15565462/…
Premier
3
@MarkRajcok mencionas que normalmente es mejor no crear ámbitos manualmente. Sin embargo, ¿cuál es la alternativa si necesita crear html dinámicamente y desea usar una directiva angular dentro de ese html?
lostintranslation
Salvavidas! Tuve que hacer esto, ya que estaba escribiendo un contenedor para Angular Bootstrap Modal .
Jonathan
9
Cuando crea un alcance secundario (aislar o no aislar), Angular lo destruye automáticamente cuando se destruye su principal. Entonces, en este ejemplo, cuando $scopese destruye también alertScopese destruye automáticamente. Entonces @MarkRajcok, este es un caso de uso perfectamente válido y perfectamente seguro.
jkjustjoshing
22

Si solo necesita interpolar cosas, use el servicio $ interpolate en lugar de $ compile, y luego no necesitará un alcance:

myApp.factory('myService', function($interpolate) {
    var template = "<h1>{{title}}</h1>";
    var interpolateFn = $interpolate(template);
    return {
        open: function(title) {
            var html = interpolateFn({ title: title });
            console.log(html);
            // append the html somewhere
        }
    }
});

Controlador de prueba:

function MyCtrl($scope, myService) {
    myService.open('The Title');
}

Violín

Mark Rajcok
fuente
2
¿Cuál es la diferencia entre $ compile y $ interpolate? $ interpolate hace que solo el texto reemplace?
Premier
3
@Premier, eso es lo que tengo entendido. Consulte también stackoverflow.com/a/13460295/215945 Si su plantilla contiene directivas, $ interpolate no funcionará; necesitaría usar $ compile para eso.
Mark Rajcok
Ok, gracias. No es suficiente para mi tarea, necesito un controlador con alcance completo.
Premier
1
@ Premier, le sugiero que intente crear un violín o un plunker de lo que está intentando. No está claro de dónde proviene su alcance aislado.
Mark Rajcok
Oh, sí, hice una stackoverflow.com/questions/15565462/…
Premier
2

Los siguientes son los pasos:

  1. Agregue su HTML al DOM usando var comiledHTML = angular.element(yourHTML);
  2. Cree un nuevo alcance si lo desea var newScope = $rootScope.$new();
  3. Llame a $ comile (); función que devuelve la función de enlacevar linkFun = $compile(comiledHTML);
  4. Vincular el nuevo alcance llamando a linkFun var finalTemplate = linkFun(newScope);
  5. Agregue finalTemplate a su DOM YourHTMLElemet.append(finalTemplate);
Amit Prabhu Parrikar
fuente
1
Desde var linkFun = $compile(comiledHTML);el paso 2
iamdevlinph
2

mira mi plunkr. Estoy generando programáticamente una directiva de widget con una directiva de representación.

https://plnkr.co/edit/5T642U9AiPr6fJthbVpD?p=preview

angular
  .module('app', [])
  .controller('mainCtrl', $scope => $scope.x = 'test')
  .directive('widget', widget)
  .directive('render', render)

function widget() {
  return {
    template: '<div><input ng-model="stuff"/>I say {{stuff}}</div>'
  }
}

function render($compile) {
  return {
    template: '<button ng-click="add()">{{name}}</button><hr/>',
    link: linkFn
  }

  function linkFn(scope, elem, attr) {
    scope.name = 'Add Widget';
    scope.add = () => {
      const newScope = scope.$new(true);
      newScope.export = (data) => alert(data);
      const templ = '<div>' +
                      '<widget></widget>' +
                      '<button ng-click="export(this.stuff)">Export</button>' +
                    '</div>';
      const compiledTempl = $compile(templ)(newScope);
      elem.append(compiledTempl);
    }
  }
}
Richard Lin
fuente
1

Supongo que cuando se habla de un ámbito aislado se está hablando de una directiva.

A continuación, se muestra un ejemplo de cómo hacerlo. http://jsfiddle.net/rgaskill/PYhGb/

var app = angular.module('test',[]);

app.controller('TestCtrl', function ($scope) {
    $scope.val = 'World';
});

app.factory('AlertFactory', function () {

    return {
        doWork: function(scope) {
            scope.title = 'Fun';    
            //scope.title = scope.val;  //notice val doesn't exist in this scope
        }
    };

});

app.controller('DirCtrl', function ($scope, AlertFactory) {
    AlertFactory.doWork($scope);  
});

app.directive('titleVal',function () {
    return {
        template: '<h1>Hello {{title}}</h1>',
        restrict: 'E',
        controller: 'DirCtrl',
        scope: {
            title: '='
        },
        link: function() {

        }
    };

});

Básicamente, adjunte un controlador a una directiva que haya definido un alcance aislado. El alcance inyectado en el controlador de la directiva será un alcance aislado. En el controlador de la directiva puede inyectar su AlertFactory con el que puede pasar el alcance aislado.

rgaskill
fuente
grrr..plnkr.co parece estar desconectado en este momento. Es de esperar que el enlace siga funcionando cuando vuelva.
rgaskill
Mmmm, creo que no es una buena solución para mí, no necesito una directiva. Necesito una capacidad de fábrica para mostrar un diálogo de alerta modal. Entonces, necesito adjuntar la plantilla html en la superposición de dom y usar un controlador para administrar los datos en la vista de alerta.
Premier
2
Yo diría que no deberías manipular el dom en una fábrica. Está mezclando su lógica de vista con su lógica de negocios. Podrías hacer exactamente lo que describiste en una directiva donde debería tener lugar la manipulación de dom.
rgaskill
Vea la respuesta de Brad Green a Andy sobre el uso de un servicio para un modal, en lugar de una directiva: blog.angularjs.org/2012/11/about-those-directives.html
Mark Rajcok
... Pero para este caso específico de bootstrap, creo que es mejor resolverlo con una directiva. plnkr.co/edit/z4J8jH?p=preview
rgaskill